ソースを参照

initial checkin light-java

Steve Hu 8 年 前
コミット
2875de854d
23 ファイル変更1107 行追加0 行削除
  1. 15 0
      frameworks/Java/light-java/README.md
  2. 92 0
      frameworks/Java/light-java/benchmark_config.json
  3. 121 0
      frameworks/Java/light-java/pom.xml
  4. 9 0
      frameworks/Java/light-java/setup.sh
  5. 5 0
      frameworks/Java/light-java/setup_mongodb.sh
  6. 5 0
      frameworks/Java/light-java/setup_mysql.sh
  7. 5 0
      frameworks/Java/light-java/setup_postgresql.sh
  8. 4 0
      frameworks/Java/light-java/source_code
  9. 37 0
      frameworks/Java/light-java/src/main/java/hello/CacheHandler.java
  10. 68 0
      frameworks/Java/light-java/src/main/java/hello/DbMongoHandler.java
  11. 89 0
      frameworks/Java/light-java/src/main/java/hello/DbSqlHandler.java
  12. 25 0
      frameworks/Java/light-java/src/main/java/hello/Fortune.java
  13. 55 0
      frameworks/Java/light-java/src/main/java/hello/FortunesMongoHandler.java
  14. 61 0
      frameworks/Java/light-java/src/main/java/hello/FortunesSqlHandler.java
  15. 164 0
      frameworks/Java/light-java/src/main/java/hello/HelloWebServer.java
  16. 96 0
      frameworks/Java/light-java/src/main/java/hello/Helper.java
  17. 32 0
      frameworks/Java/light-java/src/main/java/hello/JsonHandler.java
  18. 33 0
      frameworks/Java/light-java/src/main/java/hello/PlaintextHandler.java
  19. 60 0
      frameworks/Java/light-java/src/main/java/hello/UpdatesMongoHandler.java
  20. 77 0
      frameworks/Java/light-java/src/main/java/hello/UpdatesSqlHandler.java
  21. 20 0
      frameworks/Java/light-java/src/main/java/hello/World.java
  22. 20 0
      frameworks/Java/light-java/src/main/resources/hello/fortunes.mustache
  23. 14 0
      frameworks/Java/light-java/src/main/resources/hello/server.properties

+ 15 - 0
frameworks/Java/light-java/README.md

@@ -0,0 +1,15 @@
+# Light-Java Benchmarking Test
+
+This is the light-java portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+### JSON Encoding Test
+* [JSON test source](src/main/java/hello/HelloWebServer.java)
+
+## Versions
+Light-Java 1.2.3 (https://github.com/networknt/light-java)
+
+## Test URLs
+
+### JSON Encoding Test
+
+    http://localhost:8080

+ 92 - 0
frameworks/Java/light-java/benchmark_config.json

@@ -0,0 +1,92 @@
+{
+  "framework": "light-java",
+  "tests": [{
+    "default": {
+      "setup_file": "setup",
+      "json_url": "/json",
+      "plaintext_url": "/plaintext",
+      "cache_url": "/cache",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "None",
+      "framework": "None",
+      "language": "Java",
+      "flavor": "None",
+      "orm": "Raw",
+      "platform": "Light-Java",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "light-java",
+      "notes": "",
+      "versus": ""
+    },
+    "mysql" : {
+      "setup_file": "setup_mysql",
+      "db_url": "/db/mysql",
+      "query_url": "/queries/mysql?queries=",
+      "fortune_url": "/fortunes/mysql",
+      "update_url": "/updates/mysql?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "MySQL",
+      "framework": "None",
+      "language": "Java",
+      "flavor": "None",
+      "orm": "Raw",
+      "platform": "Light-Java",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "light-java",
+      "notes": "",
+      "versus": ""
+    },
+    "postgresql" : {
+      "setup_file": "setup_postgresql",
+      "db_url": "/db/postgresql",
+      "query_url": "/queries/postgresql?queries=",
+      "fortune_url": "/fortunes/postgresql",
+      "update_url": "/updates/postgresql?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "Postgres",
+      "framework": "None",
+      "language": "Java",
+      "flavor": "None",
+      "orm": "Raw",
+      "platform": "Light-Java",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "light-java",
+      "notes": "",
+      "versus": ""
+    },
+    "mongodb" : {
+      "setup_file": "setup_mongodb",
+      "db_url": "/db/mongodb",
+      "query_url": "/queries/mongodb?queries=",
+      "fortune_url": "/fortunes/mongodb",
+      "update_url": "/updates/mongodb?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "MongoDB",
+      "framework": "None",
+      "language": "Java",
+      "flavor": "None",
+      "orm": "Raw",
+      "platform": "Light-Java",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "light-java",
+      "notes": "",
+      "versus": ""
+    }
+  }]
+}

+ 121 - 0
frameworks/Java/light-java/pom.xml

@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.techempower</groupId>
+    <artifactId>undertow-example</artifactId>
+    <version>0.1</version>
+     <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+    <dependencies>
+        <!-- Web server -->
+        <dependency>
+            <groupId>io.undertow</groupId>
+            <artifactId>undertow-core</artifactId>
+            <version>1.2.5.Final</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.xnio</groupId>
+            <artifactId>xnio-api</artifactId>
+            <version>3.3.1.Final</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.xnio</groupId>
+            <artifactId>xnio-nio</artifactId>
+            <version>3.3.1.Final</version>
+        </dependency>
+        <!-- Database drivers -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>5.1.38</version>
+        </dependency>
+        <dependency>
+            <groupId>org.postgresql</groupId>
+	    <artifactId>postgresql</artifactId>
+            <version>9.4.1208</version>
+	</dependency>
+        <dependency>
+            <groupId>org.mongodb</groupId>
+            <artifactId>mongo-java-driver</artifactId>
+            <version>2.11.2</version>
+        </dependency>
+        <!-- Database connection pooling -->
+        <dependency>
+            <groupId>commons-dbcp</groupId>
+            <artifactId>commons-dbcp</artifactId>
+            <version>1.4</version>
+        </dependency>
+        <!-- Caching (and misc. utilities) -->
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>18.0</version>
+        </dependency>
+        <!-- JSON encoding -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>2.5.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+            <version>2.5.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+            <version>2.5.3</version>
+        </dependency>
+        <!-- HTML templates -->
+        <dependency>
+            <groupId>com.github.spullara.mustache.java</groupId>
+            <artifactId>compiler</artifactId>
+            <version>0.9.0</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <inherited>true</inherited>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                    <optimize>true</optimize>
+                    <debug>false</debug>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>hello.HelloWebServer</mainClass>
+                        </manifest>
+                    </archive>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id> <!-- this is used for inheritance merges -->
+                        <phase>package</phase> <!-- bind to the packaging phase -->
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 9 - 0
frameworks/Java/light-java/setup.sh

@@ -0,0 +1,9 @@
+#!/bin/bash
+
+sed -i 's|DATABASE_HOST|'"${DBHOST}"'|g' src/main/resources/hello/server.properties
+
+fw_depends mongodb postgresql mysql java maven
+
+mvn clean compile assembly:single
+cd target
+java -jar undertow-example-0.1-jar-with-dependencies.jar &

+ 5 - 0
frameworks/Java/light-java/setup_mongodb.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+fw_depends mongodb
+
+source ./setup.sh

+ 5 - 0
frameworks/Java/light-java/setup_mysql.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+fw_depends mysql
+
+source ./setup.sh

+ 5 - 0
frameworks/Java/light-java/setup_postgresql.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+fw_depends postgresql
+
+source ./setup.sh

+ 4 - 0
frameworks/Java/light-java/source_code

@@ -0,0 +1,4 @@
+./undertow/src/main/java/hello/
+./undertow/src/main/java/hello/HelloWebServer.java
+./undertow/src/main/java/hello/World.java
+./undertow/src/main/java/hello/Fortune.java

+ 37 - 0
frameworks/Java/light-java/src/main/java/hello/CacheHandler.java

@@ -0,0 +1,37 @@
+package hello;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.cache.LoadingCache;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.Headers;
+
+import java.util.Objects;
+
+import static hello.HelloWebServer.JSON_UTF8;
+
+/**
+ * Handles the cache access test.
+ */
+final class CacheHandler implements HttpHandler {
+  private final ObjectMapper objectMapper;
+  private final LoadingCache<Integer, World> worldCache;
+
+  CacheHandler(ObjectMapper objectMapper,
+               LoadingCache<Integer, World> worldCache) {
+    this.objectMapper = Objects.requireNonNull(objectMapper);
+    this.worldCache = Objects.requireNonNull(worldCache);
+  }
+
+  @Override
+  public void handleRequest(HttpServerExchange exchange) throws Exception {
+    int queries = Helper.getQueries(exchange);
+    World[] worlds = new World[queries];
+    for (int i = 0; i < queries; i++) {
+      worlds[i] = worldCache.get(Helper.randomWorld());
+    }
+    exchange.getResponseHeaders().put(
+        Headers.CONTENT_TYPE, JSON_UTF8);
+    exchange.getResponseSender().send(objectMapper.writeValueAsString(worlds));
+  }
+}

+ 68 - 0
frameworks/Java/light-java/src/main/java/hello/DbMongoHandler.java

@@ -0,0 +1,68 @@
+package hello;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.mongodb.BasicDBObject;
+import com.mongodb.DB;
+import com.mongodb.DBObject;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.Headers;
+
+import java.util.Objects;
+
+import static hello.HelloWebServer.JSON_UTF8;
+
+/**
+ * Handles the single- and multiple-query database tests using MongoDB.
+ */
+final class DbMongoHandler implements HttpHandler {
+  private final ObjectMapper objectMapper;
+  private final DB database;
+  private final boolean multiple;
+
+  DbMongoHandler(ObjectMapper objectMapper, DB database, boolean multiple) {
+    this.objectMapper = Objects.requireNonNull(objectMapper);
+    this.database = Objects.requireNonNull(database);
+    this.multiple = multiple;
+  }
+
+  @Override
+  public void handleRequest(HttpServerExchange exchange) throws Exception {
+    if (exchange.isInIoThread()) {
+      exchange.dispatch(this);
+      return;
+    }
+    
+    int queries = 1;
+    if(multiple)
+    {
+      queries = Helper.getQueries(exchange);
+    }
+    
+    World[] worlds = new World[queries];
+    for (int i = 0; i < queries; i++) {
+      DBObject object = database.getCollection("world").findOne(
+          new BasicDBObject("_id", Helper.randomWorld()));
+      worlds[i] = new World(
+          //
+          // The creation script for the Mongo database inserts these numbers as
+          // JavaScript numbers, which resolve to Doubles in Java.
+          //
+          ((Number) object.get("_id")).intValue(),
+          ((Number) object.get("randomNumber")).intValue());
+    }
+    exchange.getResponseHeaders().put(
+        Headers.CONTENT_TYPE, JSON_UTF8);
+    
+    if (multiple)
+    {
+      // If a multiple query then response must be an array
+      exchange.getResponseSender().send(objectMapper.writeValueAsString(worlds));
+    }
+    else
+    {
+      // If a single query then response must be an object
+      exchange.getResponseSender().send(objectMapper.writeValueAsString(worlds[0]));
+    }
+  }
+}

+ 89 - 0
frameworks/Java/light-java/src/main/java/hello/DbSqlHandler.java

@@ -0,0 +1,89 @@
+package hello;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.Headers;
+
+import javax.sql.DataSource;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Future;
+
+import static hello.HelloWebServer.JSON_UTF8;
+
+/**
+ * Handles the single- and multiple-query database tests using a SQL database.
+ */
+final class DbSqlHandler implements HttpHandler {
+  private final ObjectMapper objectMapper;
+  private final DataSource database;
+  private final boolean multiple;
+
+  DbSqlHandler(ObjectMapper objectMapper, DataSource database, boolean multiple) {
+    this.objectMapper = Objects.requireNonNull(objectMapper);
+    this.database = Objects.requireNonNull(database);
+    this.multiple = multiple;
+  }
+
+  @Override
+  public void handleRequest(HttpServerExchange exchange) throws Exception {
+    if (exchange.isInIoThread()) {
+      exchange.dispatch(this);
+      return;
+    }
+    
+    int queries = 1;
+    if(multiple)
+    {
+      queries = Helper.getQueries(exchange);
+    }
+    
+    World[] worlds = new World[queries];
+    try (final Connection connection = database.getConnection()) {
+      Map<Integer, Future<World>> futureWorlds = new ConcurrentHashMap<>();
+      for (int i = 0; i < queries; i++) {
+        futureWorlds.put(i, Helper.EXECUTOR.submit(new Callable<World>(){
+          @Override
+          public World call() throws Exception {
+            try (PreparedStatement statement = connection.prepareStatement(
+                "SELECT * FROM World WHERE id = ?",
+                ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
+
+              statement.setInt(1, Helper.randomWorld());
+              ResultSet resultSet = statement.executeQuery();
+              resultSet.next();
+              return new World(
+                resultSet.getInt("id"),
+                resultSet.getInt("randomNumber"));
+            }
+          }
+        }));
+      }
+
+      for (int i = 0; i < queries; i++) {
+        worlds[i] = futureWorlds.get(i).get();
+      }
+    }
+    exchange.getResponseHeaders().put(
+        Headers.CONTENT_TYPE, JSON_UTF8);
+    
+    if (multiple)
+    {
+      // If a multiple query then response must be an array
+      exchange.getResponseSender().send(objectMapper.writeValueAsString(worlds));
+    }
+    else
+    {
+      // If a single query then response must be an object
+      exchange.getResponseSender().send(objectMapper.writeValueAsString(worlds[0]));
+    }
+  }
+}

+ 25 - 0
frameworks/Java/light-java/src/main/java/hello/Fortune.java

@@ -0,0 +1,25 @@
+package hello;
+
+/**
+ * The model for the "fortune" database table.
+ */
+public final class Fortune implements Comparable<Fortune> {
+  public int id;
+  public String message;
+
+  /**
+   * Constructs a new fortune object with the given parameters.
+   *
+   * @param id the ID of the fortune
+   * @param message the message of the fortune
+   */
+  public Fortune(int id, String message) {
+    this.id = id;
+    this.message = message;
+  }
+
+  @Override
+  public int compareTo(Fortune other) {
+    return message.compareTo(other.message);
+  }
+}

+ 55 - 0
frameworks/Java/light-java/src/main/java/hello/FortunesMongoHandler.java

@@ -0,0 +1,55 @@
+package hello;
+
+import com.github.mustachejava.Mustache;
+import com.github.mustachejava.MustacheFactory;
+import com.mongodb.DB;
+import com.mongodb.DBCursor;
+import com.mongodb.DBObject;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.Headers;
+
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import static hello.HelloWebServer.HTML_UTF8;
+
+/**
+ * Handles the fortunes test using MongoDB.
+ */
+final class FortunesMongoHandler implements HttpHandler {
+  private final MustacheFactory mustacheFactory;
+  private final DB database;
+
+  FortunesMongoHandler(MustacheFactory mustacheFactory, DB database) {
+    this.mustacheFactory = Objects.requireNonNull(mustacheFactory);
+    this.database = Objects.requireNonNull(database);
+  }
+
+  @Override
+  public void handleRequest(HttpServerExchange exchange) throws Exception {
+    if (exchange.isInIoThread()) {
+      exchange.dispatch(this);
+      return;
+    }
+    List<Fortune> fortunes = new ArrayList<>();
+    DBCursor cursor = database.getCollection("fortune").find();
+    while (cursor.hasNext()) {
+      DBObject object = cursor.next();
+      fortunes.add(new Fortune(
+          ((Number) object.get("_id")).intValue(),
+          (String) object.get("message")));
+    }
+    fortunes.add(new Fortune(0, "Additional fortune added at request time."));
+    Collections.sort(fortunes);
+    Mustache mustache = mustacheFactory.compile("hello/fortunes.mustache");
+    StringWriter writer = new StringWriter();
+    mustache.execute(writer, fortunes);
+    exchange.getResponseHeaders().put(
+        Headers.CONTENT_TYPE, HTML_UTF8);
+    exchange.getResponseSender().send(writer.toString());
+  }
+}

+ 61 - 0
frameworks/Java/light-java/src/main/java/hello/FortunesSqlHandler.java

@@ -0,0 +1,61 @@
+package hello;
+
+import com.github.mustachejava.Mustache;
+import com.github.mustachejava.MustacheFactory;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.Headers;
+
+import javax.sql.DataSource;
+import java.io.StringWriter;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import static hello.HelloWebServer.HTML_UTF8;
+
+/**
+ * Handles the fortunes test using a SQL database.
+ */
+final class FortunesSqlHandler implements HttpHandler {
+  private final MustacheFactory mustacheFactory;
+  private final DataSource database;
+
+  FortunesSqlHandler(MustacheFactory mustacheFactory, DataSource database) {
+    this.mustacheFactory = Objects.requireNonNull(mustacheFactory);
+    this.database = Objects.requireNonNull(database);
+  }
+
+  @Override
+  public void handleRequest(HttpServerExchange exchange) throws Exception {
+    if (exchange.isInIoThread()) {
+      exchange.dispatch(this);
+      return;
+    }
+    List<Fortune> fortunes = new ArrayList<>();
+    try (Connection connection = database.getConnection();
+         PreparedStatement statement = connection.prepareStatement(
+             "SELECT * FROM Fortune",
+             ResultSet.TYPE_FORWARD_ONLY,
+             ResultSet.CONCUR_READ_ONLY);
+         ResultSet resultSet = statement.executeQuery()) {
+      while (resultSet.next()) {
+        fortunes.add(new Fortune(
+            resultSet.getInt("id"),
+            resultSet.getString("message")));
+      }
+    }
+    fortunes.add(new Fortune(0, "Additional fortune added at request time."));
+    Collections.sort(fortunes);
+    Mustache mustache = mustacheFactory.compile("hello/fortunes.mustache");
+    StringWriter writer = new StringWriter();
+    mustache.execute(writer, fortunes);
+    exchange.getResponseHeaders().put(
+        Headers.CONTENT_TYPE, HTML_UTF8);
+    exchange.getResponseSender().send(writer.toString());
+  }
+}

+ 164 - 0
frameworks/Java/light-java/src/main/java/hello/HelloWebServer.java

@@ -0,0 +1,164 @@
+package hello;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.MustacheFactory;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.net.MediaType;
+import com.mongodb.DB;
+import com.mongodb.MongoClient;
+import io.undertow.Handlers;
+import io.undertow.Undertow;
+import io.undertow.UndertowOptions;
+import io.undertow.util.Headers;
+import org.xnio.Options;
+
+import javax.sql.DataSource;
+import java.io.IOException;
+import java.io.InputStream;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Properties;
+
+/**
+ * An implementation of the TechEmpower benchmark tests using the Undertow web
+ * server.  The only test that truly exercises Undertow in isolation is the
+ * plaintext test.  For the rest, it uses best-of-breed components that are
+ * expected to perform well.  The idea is that using these components enables
+ * these tests to serve as performance baselines for hypothetical, Undertow-based
+ * frameworks.  For instance, it is unlikely that such frameworks would complete
+ * the JSON test faster than this will, because this implementation uses
+ * Undertow and Jackson in the most direct way possible to fulfill the test
+ * requirements.
+ */
+public final class HelloWebServer {
+
+  //MediaType.toString() does non-trivial work and does not cache the result
+  //so we cache it here
+  public static final String JSON_UTF8 = MediaType.JSON_UTF_8.toString();
+
+  public static final String TEXT_PLAIN = MediaType.PLAIN_TEXT_UTF_8.toString();
+
+  public static final String HTML_UTF8 = MediaType.HTML_UTF_8.toString();
+
+  public static void main(String[] args) throws Exception {
+    new HelloWebServer();
+  }
+
+  /**
+   * Creates and starts a new web server whose configuration is specified in the
+   * {@code server.properties} file.
+   *
+   * @throws IOException if the application properties file cannot be read or
+   *                     the Mongo database hostname cannot be resolved
+   * @throws SQLException if reading from the SQL database (while priming the
+   *                      cache) fails
+   */
+  public HelloWebServer() throws ClassNotFoundException, IOException, SQLException {
+    Class.forName("org.postgresql.Driver");
+    Properties properties = new Properties();
+    try (InputStream in = HelloWebServer.class.getResourceAsStream(
+        "server.properties")) {
+      properties.load(in);
+    }
+    final ObjectMapper objectMapper = new ObjectMapper();
+    final MustacheFactory mustacheFactory = new DefaultMustacheFactory();
+    final DataSource mysql = Helper.newDataSource(
+        properties.getProperty("mysql.uri"),
+        properties.getProperty("mysql.user"),
+        properties.getProperty("mysql.password"));
+    final DataSource postgresql = Helper.newDataSource(
+        properties.getProperty("postgresql.uri"),
+        properties.getProperty("postgresql.user"),
+        properties.getProperty("postgresql.password"));
+    final DB mongodb = new MongoClient(properties.getProperty("mongodb.host"))
+        .getDB(properties.getProperty("mongodb.name"));
+    //
+    // The world cache is primed at startup with all values.  It doesn't
+    // matter which database backs it; they all contain the same information
+    // and the CacheLoader.load implementation below is never invoked.
+    //
+    final LoadingCache<Integer, World> worldCache = CacheBuilder.newBuilder()
+        .build(new CacheLoader<Integer, World>() {
+          @Override
+          public World load(Integer id) throws Exception {
+            try (Connection connection = mysql.getConnection();
+                 PreparedStatement statement = connection.prepareStatement(
+                     "SELECT * FROM World WHERE id = ?",
+                     ResultSet.TYPE_FORWARD_ONLY,
+                     ResultSet.CONCUR_READ_ONLY)) {
+              statement.setInt(1, id);
+              try (ResultSet resultSet = statement.executeQuery()) {
+                resultSet.next();
+                return new World(
+                    resultSet.getInt("id"),
+                    resultSet.getInt("randomNumber"));
+              }
+            }
+          }
+        });
+    try (Connection connection = mysql.getConnection();
+         PreparedStatement statement = connection.prepareStatement(
+             "SELECT * FROM World",
+             ResultSet.TYPE_FORWARD_ONLY,
+             ResultSet.CONCUR_READ_ONLY);
+         ResultSet resultSet = statement.executeQuery()) {
+      while (resultSet.next()) {
+        World world = new World(
+            resultSet.getInt("id"),
+            resultSet.getInt("randomNumber"));
+        worldCache.put(world.id, world);
+      }
+    }
+    Undertow.builder()
+        .addHttpListener(
+            Integer.parseInt(properties.getProperty("web.port")),
+            properties.getProperty("web.host"))
+        .setBufferSize(1024 * 16)
+        .setIoThreads(Runtime.getRuntime().availableProcessors() * 2) //this seems slightly faster in some configurations
+        .setSocketOption(Options.BACKLOG, 10000)
+        .setServerOption(UndertowOptions.ALWAYS_SET_KEEP_ALIVE, false) //don't send a keep-alive header for HTTP/1.1 requests, as it is not required
+        .setServerOption(UndertowOptions.ALWAYS_SET_DATE, true)
+        .setServerOption(UndertowOptions.ENABLE_CONNECTOR_STATISTICS, false)
+        .setServerOption(UndertowOptions.RECORD_REQUEST_START_TIME, false)
+        .setHandler(Handlers.header(Handlers.path()
+            .addPrefixPath("/json",
+                new JsonHandler(objectMapper))
+            .addPrefixPath("/db/mysql",
+                new DbSqlHandler(objectMapper, mysql, false))
+            .addPrefixPath("/queries/mysql",
+                new DbSqlHandler(objectMapper, mysql, true))
+            .addPrefixPath("/db/postgresql",
+                new DbSqlHandler(objectMapper, postgresql, false))
+            .addPrefixPath("/queries/postgresql",
+                new DbSqlHandler(objectMapper, postgresql, true))
+            .addPrefixPath("/db/mongodb",
+                new DbMongoHandler(objectMapper, mongodb, false))
+            .addPrefixPath("/queries/mongodb",
+                new DbMongoHandler(objectMapper, mongodb, true))
+            .addPrefixPath("/fortunes/mysql",
+                new FortunesSqlHandler(mustacheFactory, mysql))
+            .addPrefixPath("/fortunes/postgresql",
+                new FortunesSqlHandler(mustacheFactory, postgresql))
+            .addPrefixPath("/fortunes/mongodb",
+                new FortunesMongoHandler(mustacheFactory, mongodb))
+            .addPrefixPath("/updates/mysql",
+                new UpdatesSqlHandler(objectMapper, mysql))
+            .addPrefixPath("/updates/postgresql",
+                new UpdatesSqlHandler(objectMapper, postgresql))
+            .addPrefixPath("/updates/mongodb",
+                new UpdatesMongoHandler(objectMapper, mongodb))
+            .addPrefixPath("/plaintext",
+                new PlaintextHandler())
+            .addPrefixPath("/cache",
+                new CacheHandler(objectMapper, worldCache)),
+            Headers.SERVER_STRING, "U-tow"))
+        .setWorkerThreads(200)
+        .build()
+        .start();
+  }
+}

+ 96 - 0
frameworks/Java/light-java/src/main/java/hello/Helper.java

@@ -0,0 +1,96 @@
+package hello;
+
+import io.undertow.server.HttpServerExchange;
+import org.apache.commons.dbcp.ConnectionFactory;
+import org.apache.commons.dbcp.DriverManagerConnectionFactory;
+import org.apache.commons.dbcp.PoolableConnectionFactory;
+import org.apache.commons.dbcp.PoolingDataSource;
+import org.apache.commons.pool.impl.GenericObjectPool;
+
+import javax.sql.DataSource;
+import java.util.Deque;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Provides utility methods for the benchmark tests.
+ */
+final class Helper {
+  private Helper() {
+    throw new AssertionError();
+  }
+
+  /**
+   * Constructs a new SQL data source with the given parameters.  Connections
+   * to this data source are pooled.
+   *
+   * @param uri the URI for database connections
+   * @param user the username for the database
+   * @param password the password for the database
+   * @return a new SQL data source
+   */
+  static DataSource newDataSource(String uri,
+                                  String user,
+                                  String password) {
+    GenericObjectPool connectionPool = new GenericObjectPool();
+    connectionPool.setMaxActive(256);
+    connectionPool.setMaxIdle(256);
+    ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(
+        uri, user, password);
+    //
+    // This constructor modifies the connection pool, setting its connection
+    // factory to this.  (So despite how it may appear, all of the objects
+    // declared in this method are incorporated into the returned result.)
+    //
+    new PoolableConnectionFactory(
+        connectionFactory, connectionPool, null, null, false, true);
+    return new PoolingDataSource(connectionPool);
+  }
+
+  /**
+   * Returns the value of the "queries" request parameter, which is an integer
+   * bound between 1 and 500 with a default value of 1.
+   *
+   * @param exchange the current HTTP exchange
+   * @return the value of the "queries" request parameter
+   */
+  static int getQueries(HttpServerExchange exchange) {
+    Deque<String> values = exchange.getQueryParameters().get("queries");
+    if (values == null) {
+      return 1;
+    }
+    String textValue = values.peekFirst();
+    if (textValue == null) {
+      return 1;
+    }
+    try {
+      int parsedValue = Integer.parseInt(textValue);
+      return Math.min(500, Math.max(1, parsedValue));
+    } catch (NumberFormatException e) {
+      return 1;
+    }
+  }
+
+  /**
+   * Returns a random integer that is a suitable value for both the {@code id}
+   * and {@code randomNumber} properties of a world object.
+   *
+   * @return a random world number
+   */
+  static int randomWorld() {
+    return 1 + ThreadLocalRandom.current().nextInt(10000);
+  }
+
+  private static final int cpuCount = Runtime.getRuntime().availableProcessors();
+
+  // todo: parameterize multipliers
+  public static ExecutorService EXECUTOR =
+    new ThreadPoolExecutor(
+      cpuCount * 2, cpuCount * 25, 200, TimeUnit.MILLISECONDS,
+      new LinkedBlockingQueue<Runnable>(cpuCount * 100),
+      new ThreadPoolExecutor.CallerRunsPolicy());
+
+}

+ 32 - 0
frameworks/Java/light-java/src/main/java/hello/JsonHandler.java

@@ -0,0 +1,32 @@
+package hello;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.Headers;
+
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.Objects;
+
+import static hello.HelloWebServer.JSON_UTF8;
+
+/**
+ * Handles the JSON test.
+ */
+final class JsonHandler implements HttpHandler {
+  private final ObjectMapper objectMapper;
+
+  public JsonHandler(ObjectMapper objectMapper) {
+    this.objectMapper = Objects.requireNonNull(objectMapper);
+  }
+
+  @Override
+  public void handleRequest(HttpServerExchange exchange) throws Exception {
+    exchange.getResponseHeaders().put(
+        Headers.CONTENT_TYPE, JSON_UTF8);
+    exchange.getResponseSender().send(ByteBuffer.wrap(
+            objectMapper.writeValueAsBytes(
+                    Collections.singletonMap("message", "Hello, World!"))));
+  }
+}

+ 33 - 0
frameworks/Java/light-java/src/main/java/hello/PlaintextHandler.java

@@ -0,0 +1,33 @@
+package hello;
+
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.Headers;
+import java.nio.ByteBuffer;
+
+import static hello.HelloWebServer.TEXT_PLAIN;
+
+/**
+ * Handles the plaintext test.
+ */
+final class PlaintextHandler implements HttpHandler {
+  private static final ByteBuffer buffer;
+  private static final String MESSAGE = "Hello, World!";
+
+  static {
+      buffer = ByteBuffer.allocateDirect(MESSAGE.length());   
+      try {
+          buffer.put(MESSAGE.getBytes("US-ASCII"));
+      } catch (Exception e) {
+          throw new RuntimeException(e);
+      }
+      buffer.flip();
+  }
+     
+  @Override
+  public void handleRequest(HttpServerExchange exchange) throws Exception {
+    exchange.getResponseHeaders().put(
+        Headers.CONTENT_TYPE, TEXT_PLAIN);
+    exchange.getResponseSender().send(buffer.duplicate());
+  }
+}

+ 60 - 0
frameworks/Java/light-java/src/main/java/hello/UpdatesMongoHandler.java

@@ -0,0 +1,60 @@
+package hello;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.mongodb.BasicDBObject;
+import com.mongodb.DB;
+import com.mongodb.DBObject;
+
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.Headers;
+
+import java.util.Objects;
+
+import static hello.HelloWebServer.JSON_UTF8;
+
+/**
+ * Handles the updates test using MongoDB.
+ */
+final class UpdatesMongoHandler implements HttpHandler {
+  private final ObjectMapper objectMapper;
+  private final DB database;
+
+  UpdatesMongoHandler(ObjectMapper objectMapper, DB database) {
+    this.objectMapper = Objects.requireNonNull(objectMapper);
+    this.database = Objects.requireNonNull(database);
+  }
+
+  @Override
+  public void handleRequest(HttpServerExchange exchange) throws Exception {
+    if (exchange.isInIoThread()) {
+      exchange.dispatch(this);
+      return;
+    }
+    int queries = Helper.getQueries(exchange);
+    World[] worlds = new World[queries];
+    for (int i = 0; i < queries; i++) {
+      int id = Helper.randomWorld();
+      DBObject key = new BasicDBObject("_id", id);
+      //
+      // The requirements for the test dictate that we must fetch the World
+      // object from the data store and read its randomNumber field, even though
+      // we could technically avoid doing either of those things and still
+      // produce the correct output and side effects.
+      //
+      DBObject object = database.getCollection("world").findOne(key);
+      
+      @SuppressWarnings("unused")
+      // Per test requirement the old value must be read
+      int oldRandomNumber = ((Number) object.get("randomNumber")).intValue(); 
+      
+      int newRandomNumber = Helper.randomWorld();
+      object.put("randomNumber", newRandomNumber);
+      database.getCollection("world").update(key, object);
+      worlds[i] = new World(id, newRandomNumber);
+    }
+    exchange.getResponseHeaders().put(
+        Headers.CONTENT_TYPE, JSON_UTF8);
+    exchange.getResponseSender().send(objectMapper.writeValueAsString(worlds));
+  }
+}

+ 77 - 0
frameworks/Java/light-java/src/main/java/hello/UpdatesSqlHandler.java

@@ -0,0 +1,77 @@
+package hello;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.Headers;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Future;
+
+import static hello.HelloWebServer.JSON_UTF8;
+
+/**
+ * Handles the updates test using a SQL database.
+ */
+final class UpdatesSqlHandler implements HttpHandler {
+  private final ObjectMapper objectMapper;
+  private final DataSource database;
+
+  UpdatesSqlHandler(ObjectMapper objectMapper, DataSource database) {
+    this.objectMapper = Objects.requireNonNull(objectMapper);
+    this.database = Objects.requireNonNull(database);
+  }
+
+  @Override
+  public void handleRequest(HttpServerExchange exchange) throws Exception {
+    if (exchange.isInIoThread()) {
+      exchange.dispatch(this);
+      return;
+    }
+    int queries = Helper.getQueries(exchange);
+    World[] worlds = new World[queries];
+    try (final Connection connection = database.getConnection()) {
+      Map<Integer, Future<World>> futureWorlds = new ConcurrentHashMap<>();
+      for (int i = 0; i < queries; i++) {
+        futureWorlds.put(i, Helper.EXECUTOR.submit(new Callable<World>() {
+          @Override
+          public World call() throws Exception {
+            World world;
+            try (PreparedStatement update = connection.prepareStatement(
+                "UPDATE World SET randomNumber = ? WHERE id= ?")) {
+              try (PreparedStatement query = connection.prepareStatement(
+                  "SELECT * FROM World WHERE id = ?",
+                  ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
+
+                query.setInt(1, Helper.randomWorld());
+                ResultSet resultSet = query.executeQuery();
+                resultSet.next();
+                world = new World(
+                  resultSet.getInt("id"),
+                  resultSet.getInt("randomNumber"));
+              }
+              world.randomNumber = Helper.randomWorld();
+              update.setInt(1, world.randomNumber);
+              update.setInt(2, world.id);
+              update.executeUpdate();
+              return world;
+            }
+          }
+        }));
+      }
+      for (int i = 0; i < queries; i++) {
+        worlds[i] = futureWorlds.get(i).get();
+      }
+    }
+    exchange.getResponseHeaders().put(
+        Headers.CONTENT_TYPE, JSON_UTF8);
+    exchange.getResponseSender().send(objectMapper.writeValueAsString(worlds));
+  }
+}

+ 20 - 0
frameworks/Java/light-java/src/main/java/hello/World.java

@@ -0,0 +1,20 @@
+package hello;
+
+/**
+ * The model for the "world" database table.
+ */
+public final class World {
+  public int id;
+  public int randomNumber;
+
+  /**
+   * Constructs a new world object with the given parameters.
+   *
+   * @param id the ID of the world
+   * @param randomNumber the random number of the world
+   */
+  public World(int id, int randomNumber) {
+    this.id = id;
+    this.randomNumber = randomNumber;
+  }
+}

+ 20 - 0
frameworks/Java/light-java/src/main/resources/hello/fortunes.mustache

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Fortunes</title>
+</head>
+<body>
+<table>
+    <tr>
+        <th>id</th>
+        <th>message</th>
+    </tr>
+    {{#.}}
+      <tr>
+          <td>{{id}}</td>
+          <td>{{message}}</td>
+      </tr>
+    {{/.}}
+</table>
+</body>
+</html>

+ 14 - 0
frameworks/Java/light-java/src/main/resources/hello/server.properties

@@ -0,0 +1,14 @@
+web.port = 8080
+web.host = 0.0.0.0
+mysql.uri = jdbc:mysql://DATABASE_HOST:3306/hello_world?jdbcCompliantTruncation=false&elideSetAutoCommits=true&useLocalSessionState=true&cachePrepStmts=true&cacheCallableStmts=true&alwaysSendSetIsolation=false&prepStmtCacheSize=4096&cacheServerConfiguration=true&prepStmtCacheSqlLimit=2048&zeroDateTimeBehavior=convertToNull&traceProtocol=false&useUnbufferedInput=false&useReadAheadInput=false&maintainTimeStats=false&useServerPrepStmts&cacheRSMetadata=true
+mysql.user = benchmarkdbuser
+mysql.password = benchmarkdbpass
+#
+# TODO: Audit this postgresql connection string.
+# It looks copy & pasted from MySQL.  Do these parameters even do anything?
+#
+postgresql.uri = jdbc:postgresql://DATABASE_HOST:5432/hello_world?jdbcCompliantTruncation=false&elideSetAutoCommits=true&useLocalSessionState=true&cachePrepStmts=true&cacheCallableStmts=true&alwaysSendSetIsolation=false&prepStmtCacheSize=4096&cacheServerConfiguration=true&prepStmtCacheSqlLimit=2048&zeroDateTimeBehavior=convertToNull&traceProtocol=false&useUnbufferedInput=false&useReadAheadInput=false&maintainTimeStats=false&useServerPrepStmts&cacheRSMetadata=true
+postgresql.user = benchmarkdbuser
+postgresql.password = benchmarkdbpass
+mongodb.host = DATABASE_HOST:27017
+mongodb.name = hello_world