Browse Source

Fixes for OfficeFloor (#3950)

* Plain text test implemented

* Providing a test class per Tech Empower test

* Providing abstract class for generic testing

* Adding JSON serialise test

* Initial work on Single Database Query test

* Single Database Query test written.

* Multiple Database Queries Test

* Added Fortunes test

* All tests 1-6 written

* Json Serialisation requires JsonResponseWriter to specify the
Content-Type header.

* Single Query passing tests

* Multiple database queries passing

* Initial work on Fortunes test

* Fortunes test passing

* Passing unit tests

* Providing connection pooling and not logging.

* Providing threading configuration for database interaction

* Fixing issues with Database Update Test

* Tests are passing

* Setup of OfficeFloor to run in TechEmpire environment.

* Including production data source file

* Tests passing

* Tests now running in Vagrant VM

* Cleaning up start now that working.

* Should not have empty directory

* Plain text test implemented

* Providing a test class per Tech Empower test

* Providing abstract class for generic testing

* Adding JSON serialise test

* Initial work on Single Database Query test

* Single Database Query test written.

* Multiple Database Queries Test

* Added Fortunes test

* All tests 1-6 written

* Json Serialisation requires JsonResponseWriter to specify the
Content-Type header.

* Single Query passing tests

* Multiple database queries passing

* Initial work on Fortunes test

* Fortunes test passing

* Passing unit tests

* Providing connection pooling and not logging.

* Providing threading configuration for database interaction

* Fixing issues with Database Update Test

* Tests are passing

* Setup of OfficeFloor to run in TechEmpire environment.

* Including production data source file

* Tests passing

* Tests now running in Vagrant VM

* Cleaning up start now that working.

* Should not have empty directory

* Passing latest rebase of FrameworkBenchmarks.

* Migrating to version 3.0.0

* Run own comparison with just top level frameworks

* Fix file name spelling

* Providing readme placeholder for OfficeFloor

* Fixing for TTY not available in Jenkins

* Removing OfficeFloor to run setup script

* Initial setup

* Initial project setup

* OfficeFloor implementation passing tests

* Building OfficeFloor

* Fixing path

* Raw OfficeFloor

* Specifying SourceForge repository for sourcing

* Passing build - new SQL fix

* Able to be called for verify for officefloor-raw

* Adding Server / Date headers

* Configuring Postgresql

* Threading access to multiple queries on database

* Running all tests

* Fixing tests to pass verification

* re-useable tests for different implementations

* Pooling Connections

* Adding micro tests

* Providing micro, netty and rapidoid implementations

* Providing throttle active request count value

* Fixing spelling causing failed run

* Initial work to attempt to cache for local runs

* Fixing to start postgres for micro

* Improving Updates and Fortunes performance

* Reducing possible contention in database for updates

* Providing raw with DB functionality

* Including additional raw tests

* Including database for raw

* Fixing logic

* Ready for submission

* Ready for submission

* Cleaning up based on Tech Empower feedback:

 - Removing tests
 - Removing source_files
 - Also bumping up to use Java 10

* Removing test resources

* Fixing netty / rapidoid builds

Simplified build process to only build the project.  However, netty and
rapidoid depend on the benchmark default project for the implementation
(and just change the HTTP server).  Therefore, installing benchmark
first to allow the dependency to be resolved.

* Fixing for not finding parent pom

* Reverting back to Java 8

Issue with running with Java 10.  Will run in Java 8 for time being and
work on resolving this issue when more time.

* Increasing time for checking server up

Travis seems to run many builds at the same time, and concerned that may
be starving for thread to check causing timeout...  and subsequently
causing false positives.

* Working with Java 10

* Allowing to run on different port

CI server running on 8080 (so unable to start in unit tests)

* Updating to 3.1.0

Also, avoiding memory resizing issues reducing performance.

* Register Afterburner

* Configuring for entity manager to use connection

* thread-per-request example

* Run in quiet mode so can see errors in travis logs

This should avoid filling build logs with download progress

* Using batch to show build

* Fix connection string

* quiet build

* Reducing connection pool sizes for improved performance
Daniel 7 years ago
parent
commit
86a08dce17
22 changed files with 420 additions and 91 deletions
  1. 24 2
      frameworks/Java/officefloor/benchmark_config.json
  2. 1 1
      frameworks/Java/officefloor/officefloor-micro.dockerfile
  3. 3 3
      frameworks/Java/officefloor/officefloor-netty.dockerfile
  4. 3 3
      frameworks/Java/officefloor/officefloor-rapidoid.dockerfile
  5. 1 1
      frameworks/Java/officefloor/officefloor-raw.dockerfile
  6. 10 0
      frameworks/Java/officefloor/officefloor-tpr.dockerfile
  7. 1 1
      frameworks/Java/officefloor/officefloor.dockerfile
  8. 8 1
      frameworks/Java/officefloor/src/pom.xml
  9. 3 1
      frameworks/Java/officefloor/src/woof_benchmark/src/main/resources/application.teams
  10. 2 1
      frameworks/Java/officefloor/src/woof_benchmark/src/main/resources/entitymanager.properties
  11. 17 76
      frameworks/Java/officefloor/src/woof_micro/src/main/java/net/officefloor/benchmark/Logic.java
  12. 3 1
      frameworks/Java/officefloor/src/woof_micro/src/main/resources/application.teams
  13. 2 0
      frameworks/Java/officefloor/src/woof_raw/src/main/java/net/officefloor/benchmark/RawOfficeFloorMain.java
  14. 72 0
      frameworks/Java/officefloor/src/woof_tpr/pom.xml
  15. 24 0
      frameworks/Java/officefloor/src/woof_tpr/src/main/java/net/officefloor/benchmark/Fortune.java
  16. 167 0
      frameworks/Java/officefloor/src/woof_tpr/src/main/java/net/officefloor/benchmark/Logic.java
  17. 15 0
      frameworks/Java/officefloor/src/woof_tpr/src/main/java/net/officefloor/benchmark/World.java
  18. 7 0
      frameworks/Java/officefloor/src/woof_tpr/src/main/resources/application.objects
  19. 5 0
      frameworks/Java/officefloor/src/woof_tpr/src/main/resources/application.teams
  20. 46 0
      frameworks/Java/officefloor/src/woof_tpr/src/main/resources/application.woof
  21. 5 0
      frameworks/Java/officefloor/src/woof_tpr/src/main/resources/datasource.properties
  22. 1 0
      frameworks/Java/officefloor/src/woof_tpr/src/main/resources/plaintext.html

+ 24 - 2
frameworks/Java/officefloor/benchmark_config.json

@@ -23,7 +23,7 @@
 				"database_os": "Linux",
 				"database_os": "Linux",
 				"display_name": "OfficeFloor",
 				"display_name": "OfficeFloor",
 				"notes": "",
 				"notes": "",
-				"versus": "officefloor-raw"
+				"versus": "officefloor-micro"
 			},
 			},
 			"raw": {
 			"raw": {
 				"json_url": "/json",
 				"json_url": "/json",
@@ -40,8 +40,30 @@
 				"os": "Linux",
 				"os": "Linux",
 				"database_os": "Linux",
 				"database_os": "Linux",
 				"display_name": "OfficeFloor-raw",
 				"display_name": "OfficeFloor-raw",
+				"notes": ""
+			},
+			"tpr": {
+				"json_url": "/json",
+				"plaintext_url": "/plaintext",
+				"db_url": "/db",
+				"query_url": "/queries?queries=",
+				"fortune_url": "/fortunes",
+				"update_url": "/update?queries=",
+				"port": 8080,
+				"approach": "Realistic",
+				"classification": "Fullstack",
+				"database": "Postgres",
+				"framework": "OfficeFloor",
+				"language": "Java",
+				"flavor": "None",
+				"orm": "raw",
+				"platform": "OfficeFloor",
+				"webserver": "WoOF",
+				"os": "Linux",
+				"database_os": "Linux",
+				"display_name": "OfficeFloor-tpr",
 				"notes": "",
 				"notes": "",
-				"versus": "None"
+				"versus": "OfficeFloor-raw"
 			},
 			},
 			"micro": {
 			"micro": {
 				"json_url": "/json",
 				"json_url": "/json",

+ 1 - 1
frameworks/Java/officefloor/officefloor-micro.dockerfile

@@ -2,7 +2,7 @@ FROM maven:3.5.4-jdk-10 as maven
 WORKDIR /officefloor
 WORKDIR /officefloor
 COPY src src
 COPY src src
 WORKDIR /officefloor/src/woof_micro
 WORKDIR /officefloor/src/woof_micro
-RUN mvn clean package
+RUN mvn -q clean package
 
 
 FROM openjdk:10
 FROM openjdk:10
 WORKDIR /officefloor
 WORKDIR /officefloor

+ 3 - 3
frameworks/Java/officefloor/officefloor-netty.dockerfile

@@ -2,11 +2,11 @@ FROM maven:3.5.4-jdk-10 as maven
 WORKDIR /officefloor
 WORKDIR /officefloor
 COPY src src
 COPY src src
 WORKDIR /officefloor/src
 WORKDIR /officefloor/src
-RUN mvn -N clean install
+RUN mvn -q -N clean install
 WORKDIR /officefloor/src/woof_benchmark
 WORKDIR /officefloor/src/woof_benchmark
-RUN mvn clean install
+RUN mvn -q clean install
 WORKDIR /officefloor/src/woof_netty
 WORKDIR /officefloor/src/woof_netty
-RUN mvn clean package
+RUN mvn -q clean package
 
 
 FROM openjdk:10
 FROM openjdk:10
 WORKDIR /officefloor
 WORKDIR /officefloor

+ 3 - 3
frameworks/Java/officefloor/officefloor-rapidoid.dockerfile

@@ -2,11 +2,11 @@ FROM maven:3.5.4-jdk-10 as maven
 WORKDIR /officefloor
 WORKDIR /officefloor
 COPY src src
 COPY src src
 WORKDIR /officefloor/src
 WORKDIR /officefloor/src
-RUN mvn -N clean install
+RUN mvn -q -N clean install
 WORKDIR /officefloor/src/woof_benchmark
 WORKDIR /officefloor/src/woof_benchmark
-RUN mvn clean install
+RUN mvn -q clean install
 WORKDIR /officefloor/src/woof_rapidoid
 WORKDIR /officefloor/src/woof_rapidoid
-RUN mvn clean package
+RUN mvn -q clean package
 
 
 FROM openjdk:10
 FROM openjdk:10
 WORKDIR /officefloor
 WORKDIR /officefloor

+ 1 - 1
frameworks/Java/officefloor/officefloor-raw.dockerfile

@@ -2,7 +2,7 @@ FROM maven:3.5.4-jdk-10 as maven
 WORKDIR /officefloor
 WORKDIR /officefloor
 COPY src src
 COPY src src
 WORKDIR /officefloor/src/woof_raw
 WORKDIR /officefloor/src/woof_raw
-RUN mvn clean package
+RUN mvn -q clean package
 
 
 FROM openjdk:10
 FROM openjdk:10
 WORKDIR /officefloor
 WORKDIR /officefloor

+ 10 - 0
frameworks/Java/officefloor/officefloor-tpr.dockerfile

@@ -0,0 +1,10 @@
+FROM maven:3.5.4-jdk-10 as maven
+WORKDIR /officefloor
+COPY src src
+WORKDIR /officefloor/src/woof_tpr
+RUN mvn -q clean package
+
+FROM openjdk:10
+WORKDIR /officefloor
+COPY --from=maven /officefloor/src/woof_tpr/target/woof_tpr-1.0.0.jar server.jar
+CMD ["java", "-server", "-Xms2g", "-Xmx2g", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AggressiveOpts", "-Dhttp.port=8080", "-Dhttp.server.name=OF", "-Dhttp.date.header=true", "-jar", "server.jar"]

+ 1 - 1
frameworks/Java/officefloor/officefloor.dockerfile

@@ -2,7 +2,7 @@ FROM maven:3.5.4-jdk-10 as maven
 WORKDIR /officefloor
 WORKDIR /officefloor
 COPY src src
 COPY src src
 WORKDIR /officefloor/src/woof_benchmark
 WORKDIR /officefloor/src/woof_benchmark
-RUN mvn clean package
+RUN mvn -q clean package
 
 
 FROM openjdk:10
 FROM openjdk:10
 WORKDIR /officefloor
 WORKDIR /officefloor

+ 8 - 1
frameworks/Java/officefloor/src/pom.xml

@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <?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"
+<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/maven-v4_0_0.xsd">
 	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 	<modelVersion>4.0.0</modelVersion>
 	<modelVersion>4.0.0</modelVersion>
 	<groupId>net.officefloor.benchmarks</groupId>
 	<groupId>net.officefloor.benchmarks</groupId>
@@ -15,6 +16,7 @@
 	</properties>
 	</properties>
 	<modules>
 	<modules>
 		<module>woof_benchmark</module>
 		<module>woof_benchmark</module>
+		<module>woof_tpr</module>
 		<module>woof_micro</module>
 		<module>woof_micro</module>
 		<module>woof_raw</module>
 		<module>woof_raw</module>
 		<module>woof_netty</module>
 		<module>woof_netty</module>
@@ -114,6 +116,11 @@
 				<artifactId>postgresql</artifactId>
 				<artifactId>postgresql</artifactId>
 				<version>42.2.2</version>
 				<version>42.2.2</version>
 			</dependency>
 			</dependency>
+			<dependency>
+				<groupId>com.zaxxer</groupId>
+				<artifactId>HikariCP</artifactId>
+				<version>3.2.0</version>
+			</dependency>
 			<dependency>
 			<dependency>
 				<groupId>org.projectlombok</groupId>
 				<groupId>org.projectlombok</groupId>
 				<artifactId>lombok</artifactId>
 				<artifactId>lombok</artifactId>

+ 3 - 1
frameworks/Java/officefloor/src/woof_benchmark/src/main/resources/application.teams

@@ -1,5 +1,7 @@
 <teams>
 <teams>
 
 
-	<team source="net.officefloor.frame.impl.spi.team.ExecutorCachedTeamSource" type="java.sql.Connection" />
+	<team source="net.officefloor.frame.impl.spi.team.ExecutorFixedTeamSource" type="java.sql.Connection">
+		<property name="team.size" value="28" />
+	</team>
 
 
 </teams>
 </teams>

+ 2 - 1
frameworks/Java/officefloor/src/woof_benchmark/src/main/resources/entitymanager.properties

@@ -1 +1,2 @@
-persistence.unit.name=benchmarks
+persistence.unit.name=benchmarks
+persistence.dependency=connection

+ 17 - 76
frameworks/Java/officefloor/src/woof_micro/src/main/java/net/officefloor/benchmark/Logic.java

@@ -14,10 +14,6 @@ import java.util.concurrent.ThreadLocalRandom;
 import org.apache.commons.text.StringEscapeUtils;
 import org.apache.commons.text.StringEscapeUtils;
 
 
 import lombok.Data;
 import lombok.Data;
-import net.officefloor.frame.api.function.FlowCallback;
-import net.officefloor.plugin.managedfunction.clazz.FlowInterface;
-import net.officefloor.plugin.section.clazz.Parameter;
-import net.officefloor.plugin.section.clazz.Spawn;
 import net.officefloor.server.http.HttpHeaderValue;
 import net.officefloor.server.http.HttpHeaderValue;
 import net.officefloor.server.http.HttpResponse;
 import net.officefloor.server.http.HttpResponse;
 import net.officefloor.server.http.ServerHttpConnection;
 import net.officefloor.server.http.ServerHttpConnection;
@@ -57,95 +53,40 @@ public class Logic {
 
 
 	// ========== QUERIES ==================
 	// ========== QUERIES ==================
 
 
-	public void queries(@HttpQueryParameter("queries") String queries, QueriesFlows flows,
-			ObjectResponse<World[]> response) {
+	public void queries(@HttpQueryParameter("queries") String queries, Connection connection,
+			ObjectResponse<World[]> response) throws SQLException {
 		ThreadLocalRandom random = ThreadLocalRandom.current();
 		ThreadLocalRandom random = ThreadLocalRandom.current();
-		int[] loaded = new int[] { 0 };
 		int count = getQueryCount(queries);
 		int count = getQueryCount(queries);
 		World[] worlds = new World[count];
 		World[] worlds = new World[count];
-		for (int i = 0; i < worlds.length; i++) {
-			int index = i;
-			GetEntry entry = new GetEntry(random.nextInt(1, 10000));
-			flows.getEntry(entry, (escalation) -> {
-				worlds[index] = entry.world;
-				loaded[0]++;
-				if (loaded[0] >= count) {
-					response.send(worlds);
-				}
-			});
-		}
-	}
-
-	@Data
-	public static class GetEntry {
-		private final int id;
-		private World world;
-	}
-
-	@FlowInterface
-	public static interface QueriesFlows {
-		@Spawn
-		void getEntry(GetEntry entry, FlowCallback callback);
-	}
-
-	public void getEntry(@Parameter GetEntry entry, Connection connection) throws SQLException {
 		try (PreparedStatement statement = connection.prepareStatement(
 		try (PreparedStatement statement = connection.prepareStatement(
 				"SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID = ?", ResultSet.TYPE_FORWARD_ONLY,
 				"SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID = ?", ResultSet.TYPE_FORWARD_ONLY,
 				ResultSet.CONCUR_READ_ONLY)) {
 				ResultSet.CONCUR_READ_ONLY)) {
-			statement.setInt(1, entry.id);
-			ResultSet resultSet = statement.executeQuery();
-			resultSet.next();
-			entry.world = new World(resultSet.getInt(1), resultSet.getInt(2));
+			for (int i = 0; i < worlds.length; i++) {
+				statement.setInt(1, random.nextInt(1, 10000));
+				ResultSet resultSet = statement.executeQuery();
+				resultSet.next();
+				worlds[i] = new World(resultSet.getInt(1), resultSet.getInt(2));
+			}
+			response.send(worlds);
 		}
 		}
 	}
 	}
 
 
 	// =========== UPDATES ===================
 	// =========== UPDATES ===================
 
 
-	public void update(@HttpQueryParameter("queries") String queries, UpdatesFlows flows) {
+	public void update(@HttpQueryParameter("queries") String queries, Connection connection,
+			ObjectResponse<World[]> response) throws SQLException {
 		ThreadLocalRandom random = ThreadLocalRandom.current();
 		ThreadLocalRandom random = ThreadLocalRandom.current();
-		int[] loaded = new int[] { 0 };
 		int count = getQueryCount(queries);
 		int count = getQueryCount(queries);
 		World[] worlds = new World[count];
 		World[] worlds = new World[count];
-		for (int i = 0; i < worlds.length; i++) {
-			int index = i;
-			UpdateEntry entry = new UpdateEntry(random.nextInt(1, 10000));
-			flows.updateEntry(entry, (escalation) -> {
-				worlds[index] = entry.world;
-				loaded[0]++;
-				if (loaded[0] >= count) {
-					flows.doUpdates(worlds);
-				}
-			});
-		}
-	}
-
-	@FlowInterface
-	public static interface UpdatesFlows {
-		@Spawn
-		void updateEntry(UpdateEntry entry, FlowCallback callback);
-
-		void doUpdates(World[] worlds);
-	}
-
-	@Data
-	public static class UpdateEntry {
-		private final int id;
-		private World world;
-	}
-
-	public void updateEntry(@Parameter UpdateEntry entry, Connection connection) throws SQLException {
-		ThreadLocalRandom random = ThreadLocalRandom.current();
 		try (PreparedStatement statement = connection.prepareStatement("SELECT ID FROM WORLD WHERE ID = ?",
 		try (PreparedStatement statement = connection.prepareStatement("SELECT ID FROM WORLD WHERE ID = ?",
 				ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
 				ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
-			statement.setInt(1, entry.id);
-			ResultSet resultSet = statement.executeQuery();
-			resultSet.next();
-			entry.world = new World(resultSet.getInt(1), random.nextInt(1, 10000));
+			for (int i = 0; i < worlds.length; i++) {
+				statement.setInt(1, random.nextInt(1, 10000));
+				ResultSet resultSet = statement.executeQuery();
+				resultSet.next();
+				worlds[i] = new World(resultSet.getInt(1), random.nextInt(1, 10000));
+			}
 		}
 		}
-	}
-
-	public void doUpdates(@Parameter World[] worlds, Connection connection, ObjectResponse<World[]> response)
-			throws SQLException {
 		Arrays.sort(worlds, (a, b) -> a.getId() - b.getId());
 		Arrays.sort(worlds, (a, b) -> a.getId() - b.getId());
 		try (PreparedStatement statement = connection
 		try (PreparedStatement statement = connection
 				.prepareStatement("UPDATE WORLD SET RANDOMNUMBER = ? WHERE ID = ?")) {
 				.prepareStatement("UPDATE WORLD SET RANDOMNUMBER = ? WHERE ID = ?")) {

+ 3 - 1
frameworks/Java/officefloor/src/woof_micro/src/main/resources/application.teams

@@ -1,5 +1,7 @@
 <teams>
 <teams>
 
 
-	<team source="net.officefloor.frame.impl.spi.team.ExecutorCachedTeamSource" type="java.sql.Connection" />
+	<team source="net.officefloor.frame.impl.spi.team.ExecutorFixedTeamSource" type="java.sql.Connection">
+		<property name="team.size" value="28" />
+	</team>
 
 
 </teams>
 </teams>

+ 2 - 0
frameworks/Java/officefloor/src/woof_raw/src/main/java/net/officefloor/benchmark/RawOfficeFloorMain.java

@@ -28,6 +28,7 @@ import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Executors;
 
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
 
 
 import lombok.Data;
 import lombok.Data;
 import net.officefloor.frame.api.manage.OfficeFloor;
 import net.officefloor.frame.api.manage.OfficeFloor;
@@ -157,6 +158,7 @@ public class RawOfficeFloorMain {
 				StreamBufferPool<ByteBuffer> serviceBufferPool) {
 				StreamBufferPool<ByteBuffer> serviceBufferPool) {
 			super(serverLocation, false, new HttpRequestParserMetaData(100, 1000, 1000000), serviceBufferPool, 1000,
 			super(serverLocation, false, new HttpRequestParserMetaData(100, 1000, 1000000), serviceBufferPool, 1000,
 					null, null, true);
 					null, null, true);
+			this.objectMapper.registerModule(new AfterburnerModule());
 		}
 		}
 
 
 		/*
 		/*

+ 72 - 0
frameworks/Java/officefloor/src/woof_tpr/pom.xml

@@ -0,0 +1,72 @@
+<?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/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.officefloor.benchmarks</groupId>
+		<artifactId>benchmarks</artifactId>
+		<version>1.0.0</version>
+	</parent>
+	<artifactId>woof_tpr</artifactId>
+	<description>Thread per request model used by many frameworks</description>
+	<dependencies>
+		<dependency>
+			<groupId>net.officefloor.web</groupId>
+			<artifactId>woof</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>net.officefloor.web</groupId>
+			<artifactId>officejson_jackson</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.core</groupId>
+			<artifactId>jackson-databind</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.module</groupId>
+			<artifactId>jackson-module-afterburner</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>net.officefloor.persistence</groupId>
+			<artifactId>officejdbc_postgresql</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.postgresql</groupId>
+			<artifactId>postgresql</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.zaxxer</groupId>
+			<artifactId>HikariCP</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.projectlombok</groupId>
+			<artifactId>lombok</artifactId>
+			<scope>provided</scope>
+		</dependency>
+	</dependencies>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-shade-plugin</artifactId>
+				<executions>
+					<execution>
+						<phase>package</phase>
+						<goals>
+							<goal>shade</goal>
+						</goals>
+						<configuration>
+							<transformers>
+								<transformer
+									implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+									<mainClass>net.officefloor.OfficeFloorMain</mainClass>
+								</transformer>
+							</transformers>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+</project>

+ 24 - 0
frameworks/Java/officefloor/src/woof_tpr/src/main/java/net/officefloor/benchmark/Fortune.java

@@ -0,0 +1,24 @@
+package net.officefloor.benchmark;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class Fortune implements Comparable<Fortune> {
+
+	private int id;
+
+	private String message;
+
+	public String getMessage() {
+		return this.message;
+	}
+
+	@Override
+	public int compareTo(Fortune o) {
+		return this.getMessage().compareTo(o.getMessage());
+	}
+}

+ 167 - 0
frameworks/Java/officefloor/src/woof_tpr/src/main/java/net/officefloor/benchmark/Logic.java

@@ -0,0 +1,167 @@
+package net.officefloor.benchmark;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.text.StringEscapeUtils;
+
+import lombok.Data;
+import net.officefloor.server.http.HttpHeaderValue;
+import net.officefloor.server.http.HttpResponse;
+import net.officefloor.server.http.ServerHttpConnection;
+import net.officefloor.server.stream.ServerWriter;
+import net.officefloor.web.HttpQueryParameter;
+import net.officefloor.web.ObjectResponse;
+
+/**
+ * Logic.
+ */
+public class Logic {
+
+	// =========== JSON ===================
+
+	@Data
+	public static class Message {
+		private final String message;
+	}
+
+	public void json(ObjectResponse<Message> response) {
+		response.send(new Message("Hello, World!"));
+	}
+
+	// ============ DB ====================
+
+	public void db(DataSource dataSource, ObjectResponse<World> response) throws SQLException {
+		try (Connection connection = dataSource.getConnection()) {
+			try (PreparedStatement statement = connection.prepareStatement(
+					"SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID = ?", ResultSet.TYPE_FORWARD_ONLY,
+					ResultSet.CONCUR_READ_ONLY)) {
+				statement.setInt(1, ThreadLocalRandom.current().nextInt(1, 10000));
+				ResultSet resultSet = statement.executeQuery();
+				resultSet.next();
+				World world = new World(resultSet.getInt(1), resultSet.getInt(2));
+				response.send(world);
+			}
+		}
+	}
+
+	// ========== QUERIES ==================
+
+	public void queries(@HttpQueryParameter("queries") String queries, DataSource dataSource,
+			ObjectResponse<World[]> response) throws SQLException {
+		ThreadLocalRandom random = ThreadLocalRandom.current();
+		int count = getQueryCount(queries);
+		World[] worlds = new World[count];
+		try (Connection connection = dataSource.getConnection()) {
+			try (PreparedStatement statement = connection.prepareStatement(
+					"SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID = ?", ResultSet.TYPE_FORWARD_ONLY,
+					ResultSet.CONCUR_READ_ONLY)) {
+				for (int i = 0; i < worlds.length; i++) {
+					statement.setInt(1, random.nextInt(1, 10000));
+					ResultSet resultSet = statement.executeQuery();
+					resultSet.next();
+					worlds[i] = new World(resultSet.getInt(1), resultSet.getInt(2));
+				}
+				response.send(worlds);
+			}
+		}
+	}
+
+	// =========== UPDATES ===================
+
+	public void update(@HttpQueryParameter("queries") String queries, DataSource dataSource,
+			ObjectResponse<World[]> response) throws SQLException {
+		ThreadLocalRandom random = ThreadLocalRandom.current();
+		int count = getQueryCount(queries);
+		World[] worlds = new World[count];
+		try (Connection connection = dataSource.getConnection()) {
+			try (PreparedStatement statement = connection.prepareStatement("SELECT ID FROM WORLD WHERE ID = ?",
+					ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
+				for (int i = 0; i < worlds.length; i++) {
+					statement.setInt(1, random.nextInt(1, 10000));
+					ResultSet resultSet = statement.executeQuery();
+					resultSet.next();
+					worlds[i] = new World(resultSet.getInt(1), random.nextInt(1, 10000));
+				}
+			}
+			Arrays.sort(worlds, (a, b) -> a.getId() - b.getId());
+			try (PreparedStatement statement = connection
+					.prepareStatement("UPDATE WORLD SET RANDOMNUMBER = ? WHERE ID = ?")) {
+				for (int u = 0; u < worlds.length; u++) {
+					statement.setInt(1, worlds[u].getRandomNumber());
+					statement.setInt(2, worlds[u].getId());
+					statement.addBatch();
+				}
+				statement.executeBatch();
+			}
+			response.send(worlds);
+		}
+	}
+
+	// =========== FORTUNES ==================
+
+	private static final HttpHeaderValue TEXT_HTML = new HttpHeaderValue("text/html;charset=utf-8");
+
+	private static final byte[] TEMPLATE_START = "<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>"
+			.getBytes(ServerHttpConnection.DEFAULT_HTTP_ENTITY_CHARSET);
+
+	private static final byte[] FORTUNE_START = "<tr><td>".getBytes(ServerHttpConnection.DEFAULT_HTTP_ENTITY_CHARSET);
+
+	private static final byte[] FORTUNE_MIDDLE = "</td><td>".getBytes(ServerHttpConnection.DEFAULT_HTTP_ENTITY_CHARSET);
+
+	private static final byte[] FORTUNE_END = "</td></tr>".getBytes(ServerHttpConnection.DEFAULT_HTTP_ENTITY_CHARSET);
+
+	private static final byte[] TEMPLATE_END = "</table></body></html>"
+			.getBytes(ServerHttpConnection.DEFAULT_HTTP_ENTITY_CHARSET);
+
+	public void fortunes(DataSource dataSource, ServerHttpConnection httpConnection) throws IOException, SQLException {
+		try (Connection connection = dataSource.getConnection()) {
+			try (PreparedStatement statement = connection.prepareStatement("SELECT ID, MESSAGE FROM FORTUNE",
+					ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
+				List<Fortune> fortunes = new ArrayList<>();
+				fortunes.add(new Fortune(0, "Additional fortune added at request time."));
+				ResultSet resultSet = statement.executeQuery();
+				while (resultSet.next()) {
+					fortunes.add(new Fortune(resultSet.getInt(1), resultSet.getString(2)));
+				}
+				HttpResponse response = httpConnection.getResponse();
+				response.setContentType(TEXT_HTML, null);
+				ServerWriter writer = response.getEntityWriter();
+				writer.write(TEMPLATE_START);
+				Collections.sort(fortunes);
+				for (int i = 0; i < fortunes.size(); i++) {
+					Fortune fortune = fortunes.get(i);
+					writer.write(FORTUNE_START);
+					int id = fortune.getId();
+					writer.write(Integer.valueOf(id).toString());
+					writer.write(FORTUNE_MIDDLE);
+					StringEscapeUtils.ESCAPE_HTML4.translate(fortune.getMessage(), writer);
+					writer.write(FORTUNE_END);
+				}
+				writer.write(TEMPLATE_END);
+			}
+		}
+	}
+
+	// =========== helper ===================
+
+	private static int getQueryCount(String queries) {
+		try {
+			int count = Integer.parseInt(queries);
+			return (count < 1) ? 1 : (count > 500) ? 500 : count;
+		} catch (NumberFormatException ex) {
+			return 1;
+		}
+	}
+
+}

+ 15 - 0
frameworks/Java/officefloor/src/woof_tpr/src/main/java/net/officefloor/benchmark/World.java

@@ -0,0 +1,15 @@
+package net.officefloor.benchmark;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class World {
+
+	private int id;
+
+	private int randomNumber;
+}

+ 7 - 0
frameworks/Java/officefloor/src/woof_tpr/src/main/resources/application.objects

@@ -0,0 +1,7 @@
+<objects>
+
+	<managed-object source="net.officefloor.jdbc.DataSourceManagedObjectSource" type="javax.sql.DataSource">
+		<property-file path="datasource.properties" />
+	</managed-object>
+
+</objects>

+ 5 - 0
frameworks/Java/officefloor/src/woof_tpr/src/main/resources/application.teams

@@ -0,0 +1,5 @@
+<teams>
+
+	<team source="net.officefloor.frame.impl.spi.team.ExecutorCachedTeamSource" type="javax.sql.DataSource" />
+
+</teams>

+ 46 - 0
frameworks/Java/officefloor/src/woof_tpr/src/main/resources/application.woof

@@ -0,0 +1,46 @@
+<woof>
+  <http-continuations>
+    <http-continuation path="/db" secure="false" x="87" y="172">
+      <section name="Logic" input="db"/>
+    </http-continuation>
+    <http-continuation path="/fortunes" secure="false" x="78" y="213">
+      <section name="Logic" input="fortunes"/>
+    </http-continuation>
+    <http-continuation path="/json" secure="false" x="84" y="123">
+      <section name="Logic" input="json"/>
+    </http-continuation>
+    <http-continuation path="/queries" secure="false" x="82" y="270">
+      <section name="Logic" input="queries"/>
+    </http-continuation>
+    <http-continuation path="/update" secure="false" x="82" y="323">
+      <section name="Logic" input="update"/>
+    </http-continuation>
+  </http-continuations>
+  <http-inputs>
+  </http-inputs>
+  <templates>
+    <template path="/plaintext" location="plaintext.html" class="" content-type="text/plain" charset="" secure="false" redirect-values-function="" link-separator-character="" x="64" y="46">
+    </template>
+  </templates>
+  <sections>
+    <section name="Logic" source="net.officefloor.plugin.section.clazz.ClassSectionSource" location="net.officefloor.benchmark.Logic" x="281" y="140">
+      <input name="db" parameter-type=""/>
+      <input name="fortunes" parameter-type=""/>
+      <input name="getEntry" parameter-type="net.officefloor.benchmark.Logic$GetEntry"/>
+      <input name="json" parameter-type=""/>
+      <input name="queries" parameter-type=""/>
+      <input name="update" parameter-type=""/>
+      <input name="updateEntry" parameter-type="net.officefloor.benchmark.Logic$UpdateEntry"/>
+    </section>
+  </sections>
+  <securities>
+  </securities>
+  <governances>
+  </governances>
+  <resources>
+  </resources>
+  <exceptions>
+  </exceptions>
+  <starting>
+  </starting>
+</woof>

+ 5 - 0
frameworks/Java/officefloor/src/woof_tpr/src/main/resources/datasource.properties

@@ -0,0 +1,5 @@
+datasource.class=com.zaxxer.hikari.HikariDataSource
+jdbcUrl=jdbc:postgresql://tfb-database:5432/hello_world
+username=benchmarkdbuser
+password=benchmarkdbpass
+maximumPoolSize=28

+ 1 - 0
frameworks/Java/officefloor/src/woof_tpr/src/main/resources/plaintext.html

@@ -0,0 +1 @@
+Hello, World!