Browse Source

Updating to OfficeFloor 3.28.0 (#6097)

* Removing Rapidoid as seems dead project (no commit last year)

* Downloading from SourceForge

* Bump maven-compiler-plugin in /frameworks/Java/officefloor/src

Bumps [maven-compiler-plugin](https://github.com/apache/maven-compiler-plugin) from 3.8.0 to 3.8.1.
- [Release notes](https://github.com/apache/maven-compiler-plugin/releases)
- [Commits](https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.8.0...maven-compiler-plugin-3.8.1)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump maven-shade-plugin in /frameworks/Java/officefloor/src

Bumps [maven-shade-plugin](https://github.com/apache/maven-shade-plugin) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/apache/maven-shade-plugin/releases)
- [Commits](https://github.com/apache/maven-shade-plugin/compare/maven-shade-plugin-3.2.1...maven-shade-plugin-3.2.2)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Fixing Raw OfficeFloor

* Specifying Spring plugin version

* Bump to OfficeFloor 3.21.0

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.2.4.RELEASE to 2.2.5.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.2.4.RELEASE...v2.2.5.RELEASE)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.2.5.RELEASE to 2.2.6.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.2.5.RELEASE...v2.2.6.RELEASE)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump net.officefloor:bom in /frameworks/Java/officefloor/src

Bumps [net.officefloor:bom](https://github.com/officefloor/OfficeFloor) from 3.21.0 to 3.22.0.
- [Release notes](https://github.com/officefloor/OfficeFloor/releases)
- [Commits](https://github.com/officefloor/OfficeFloor/commits)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump maven-shade-plugin in /frameworks/Java/officefloor/src

Bumps [maven-shade-plugin](https://github.com/apache/maven-shade-plugin) from 3.2.2 to 3.2.3.
- [Release notes](https://github.com/apache/maven-shade-plugin/releases)
- [Commits](https://github.com/apache/maven-shade-plugin/compare/maven-shade-plugin-3.2.2...maven-shade-plugin-3.2.3)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump net.officefloor:bom in /frameworks/Java/officefloor/src

Bumps [net.officefloor:bom](https://github.com/officefloor/OfficeFloor) from 3.22.0 to 3.23.0.
- [Release notes](https://github.com/officefloor/OfficeFloor/releases)
- [Commits](https://github.com/officefloor/OfficeFloor/commits)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump net.officefloor:bom in /frameworks/Java/officefloor/src

Bumps [net.officefloor:bom](https://github.com/officefloor/OfficeFloor) from 3.23.0 to 3.24.0.
- [Release notes](https://github.com/officefloor/OfficeFloor/releases)
- [Commits](https://github.com/officefloor/OfficeFloor/commits)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.2.6.RELEASE to 2.2.7.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.2.6.RELEASE...v2.2.7.RELEASE)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.2.7.RELEASE to 2.3.0.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.2.7.RELEASE...v2.3.0.RELEASE)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump net.officefloor:bom in /frameworks/Java/officefloor/src

Bumps [net.officefloor:bom](https://github.com/officefloor/OfficeFloor) from 3.24.0 to 3.25.0.
- [Release notes](https://github.com/officefloor/OfficeFloor/releases)
- [Commits](https://github.com/officefloor/OfficeFloor/commits)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump maven-shade-plugin in /frameworks/Java/officefloor/src

Bumps [maven-shade-plugin](https://github.com/apache/maven-shade-plugin) from 3.2.3 to 3.2.4.
- [Release notes](https://github.com/apache/maven-shade-plugin/releases)
- [Commits](https://github.com/apache/maven-shade-plugin/compare/maven-shade-plugin-3.2.3...maven-shade-plugin-3.2.4)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.3.0.RELEASE to 2.3.1.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.3.0.RELEASE...v2.3.1.RELEASE)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.3.1.RELEASE to 2.3.2.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.3.1.RELEASE...v2.3.2.RELEASE)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump net.officefloor:bom in /frameworks/Java/officefloor/src

Bumps [net.officefloor:bom](https://github.com/officefloor/OfficeFloor) from 3.25.0 to 3.26.0.
- [Release notes](https://github.com/officefloor/OfficeFloor/releases)
- [Commits](https://github.com/officefloor/OfficeFloor/compare/release-3.25.0...release-3.26.0)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.3.2.RELEASE to 2.3.3.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.3.2.RELEASE...v2.3.3.RELEASE)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump net.officefloor:bom in /frameworks/Java/officefloor/src

Bumps [net.officefloor:bom](https://github.com/officefloor/OfficeFloor) from 3.26.0 to 3.27.0.
- [Release notes](https://github.com/officefloor/OfficeFloor/releases)
- [Commits](https://github.com/officefloor/OfficeFloor/commits)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.3.3.RELEASE to 2.3.4.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.3.3.RELEASE...v2.3.4.RELEASE)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump net.officefloor:bom in /frameworks/Java/officefloor/src

Bumps [net.officefloor:bom](https://github.com/officefloor/OfficeFloor) from 3.27.0 to 3.28.0.
- [Release notes](https://github.com/officefloor/OfficeFloor/releases)
- [Commits](https://github.com/officefloor/OfficeFloor/commits)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Fixing to run with v3.28.0

* Including Undertow

* Using slim docker images

* Fixing to add all supported tests

* Increasing connection pool size

* Write up the 503 errors and why

* Fix grammer

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.3.4.RELEASE to 2.3.5.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.3.4.RELEASE...v2.3.5.RELEASE)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Improving chances of full processing

Increasing max thread counts to have thread per client.  Also, shading
jars correctly.

Plus adding in DB test for raw

* Fixing raw test to have database

* Ensure random numbers to avoid cached entities

* Ensuring spring random numbers to avoid entity caching

* Adding queries for raw

* officefloor-raw queries passing

* office-raw supporting all requests

* Fixed update test to ensure updates occur before responding

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Daniel 4 years ago
parent
commit
eaa5cb8c02
40 changed files with 605 additions and 481 deletions
  1. 19 5
      frameworks/Java/officefloor/README.md
  2. 22 12
      frameworks/Java/officefloor/benchmark_config.json
  3. 2 2
      frameworks/Java/officefloor/officefloor-micro.dockerfile
  4. 2 2
      frameworks/Java/officefloor/officefloor-netty.dockerfile
  5. 2 2
      frameworks/Java/officefloor/officefloor-raw.dockerfile
  6. 2 2
      frameworks/Java/officefloor/officefloor-spring_data.dockerfile
  7. 2 2
      frameworks/Java/officefloor/officefloor-thread_affinity.dockerfile
  8. 0 10
      frameworks/Java/officefloor/officefloor-tpr.dockerfile
  9. 14 0
      frameworks/Java/officefloor/officefloor-undertow.dockerfile
  10. 2 2
      frameworks/Java/officefloor/officefloor.dockerfile
  11. 9 4
      frameworks/Java/officefloor/src/pom.xml
  12. 9 6
      frameworks/Java/officefloor/src/woof_benchmark/pom.xml
  13. 54 12
      frameworks/Java/officefloor/src/woof_benchmark/src/main/java/net/officefloor/benchmark/Logic.java
  14. 5 7
      frameworks/Java/officefloor/src/woof_benchmark/src/main/resources/application.objects
  15. 1 1
      frameworks/Java/officefloor/src/woof_benchmark/src/main/resources/application.teams
  16. 2 1
      frameworks/Java/officefloor/src/woof_benchmark/src/main/resources/datasource.properties
  17. 2 2
      frameworks/Java/officefloor/src/woof_benchmark/src/main/resources/entitymanager.properties
  18. 7 4
      frameworks/Java/officefloor/src/woof_benchmark_micro/pom.xml
  19. 76 66
      frameworks/Java/officefloor/src/woof_benchmark_micro/src/main/java/net/officefloor/benchmark/Logic.java
  20. 1 4
      frameworks/Java/officefloor/src/woof_benchmark_micro/src/main/resources/application.objects
  21. 1 1
      frameworks/Java/officefloor/src/woof_benchmark_micro/src/main/resources/application.teams
  22. 2 1
      frameworks/Java/officefloor/src/woof_benchmark_micro/src/main/resources/datasource.properties
  23. 2 2
      frameworks/Java/officefloor/src/woof_benchmark_netty/pom.xml
  24. 16 4
      frameworks/Java/officefloor/src/woof_benchmark_raw/pom.xml
  25. 281 15
      frameworks/Java/officefloor/src/woof_benchmark_raw/src/main/java/net/officefloor/benchmark/RawOfficeFloorMain.java
  26. 1 0
      frameworks/Java/officefloor/src/woof_benchmark_raw/src/main/resources/fortunes.mustache
  27. 1 1
      frameworks/Java/officefloor/src/woof_benchmark_spring/pom.xml
  28. 55 13
      frameworks/Java/officefloor/src/woof_benchmark_spring/src/main/java/net/officefloor/benchmark/Logic.java
  29. 1 0
      frameworks/Java/officefloor/src/woof_benchmark_spring/src/main/resources/application.properties
  30. 1 1
      frameworks/Java/officefloor/src/woof_benchmark_spring/src/main/resources/application.teams
  31. 3 4
      frameworks/Java/officefloor/src/woof_benchmark_thread_affinity/pom.xml
  32. 0 24
      frameworks/Java/officefloor/src/woof_benchmark_tpr/src/main/java/net/officefloor/benchmark/Fortune.java
  33. 0 167
      frameworks/Java/officefloor/src/woof_benchmark_tpr/src/main/java/net/officefloor/benchmark/Logic.java
  34. 0 15
      frameworks/Java/officefloor/src/woof_benchmark_tpr/src/main/java/net/officefloor/benchmark/World.java
  35. 0 7
      frameworks/Java/officefloor/src/woof_benchmark_tpr/src/main/resources/application.objects
  36. 0 5
      frameworks/Java/officefloor/src/woof_benchmark_tpr/src/main/resources/application.teams
  37. 0 46
      frameworks/Java/officefloor/src/woof_benchmark_tpr/src/main/resources/application.woof
  38. 0 5
      frameworks/Java/officefloor/src/woof_benchmark_tpr/src/main/resources/datasource.properties
  39. 0 1
      frameworks/Java/officefloor/src/woof_benchmark_tpr/src/main/resources/plaintext.html
  40. 8 23
      frameworks/Java/officefloor/src/woof_benchmark_undertow/pom.xml

+ 19 - 5
frameworks/Java/officefloor/README.md

@@ -9,6 +9,23 @@ OfficeFloor provides true inversion of control.
 More information can be found at [http://officefloor.net](http://officefloor.net)
 
 
+# 503 Responses
+
+ Within the performance tests (particularly the query and update tests), there are 503 responses from OfficeFloor.  These are deliberate and make OfficeFloor significantly more responsive than typical web servers.  To understand why, we need to explain the servicing change introduced by OfficeFloor.
+ 
+ **Typical web servers** manage load by only processing one request at a time per socket.  This means on pipelined requests over a single connection, the first request needs to be fully processed before the next request is started.  As requests then queue on the network buffer, it allows the network's natural TCP throttling to manage load.
+ 
+ **Web browser request profiles have changed** to an increased number of asynchronous requests pipelined over a few number of connections.  Days of old (possibly very old), web pages loaded and then did a post to reload the page again.  There would be one page submission with a few resource requests (e.g. images).  These days, single page applications are a lot more prevalent.  The nature of single page applications is to avoid page reloads and run many asynchronous requests.  This means more logic submission requests in parallel.  As browsers re-use connections to multiplex these requests to the server, the result is pipelining requests over a few number of connections.
+ 
+ **OfficeFloor processes pipelined requests in parallel**.  To cater to this situation and allow more responsive single page web applications, OfficeFloor processes the pipeline requests in parallel.  This means the second request in the pipeline no longer has to wait for the first one to complete.  Both the first, second, third, etc will be processed immediately by OfficeFloor.  This means better responsiveness to the single page applications, as there is no queuing of requests in the pipelines.
+ 
+ **Load throttling managed by 503 responses.**  As requests are no longer queued on the network buffer, there is no TCP throttling.  Requests will be accepted in parallel into OfficeFloor.  To avoid denial of service attacks, OfficeFloor has built in load throttling.  It is beyond this overview discussion to explain how this works, but basically when thread pools are overloaded they reject new jobs.  OfficeFloor captures these rejections and translates them into 503 HTTP responses (indicating temporarily unavailable).
+ 
+ **This improves responsiveness of single page applications**.  The problem with TCP throttling is that requests start to slow down before you get rejections.  This means your single page application ends up waiting for responses.  The wait time is noticeable by the user, as can be upwards of 10's of seconds to minutes.  This then results in the user's perception of the single page application being slow.  OfficeFloor by parallel processing everything and responding immediately with 503 responses when overloaded, reduces these long wait times.  Rather than than hanging, the single page application can deal with the 503 by calling another server or providing user friendly messages back to the user that system is under load and try back in a few moments.  This provides an overall more responsive and what we find is a better experience for the user.
+ 
+ However, this does mean in some of the heavier load tests, you will see errors for OfficeFloor.  But we here at OfficeFloor are proud of these errors, as they avoid hanging applications and provide better user experience under extreme loads.
+
+ 
 # OfficeFloor Background
 
 OfficeFloor completes inversion of control by adding two new paradigms:
@@ -43,7 +60,7 @@ Further to this, graphical configuration is used.  The configuration for the *of
 OfficeFloor can use different HTTP server components:
 
 * **officefloor-netty** : incorporating Netty to service requests passing to OfficeFloor inversion of control.  Benchmark test with mature HTTP solution.
-* **officefloor-rapidoid** : similar to Netty but using Rapidoid.  Benchmark test with highly optimised HTTP solution.
+* **officefloor-undertow** : incorporating Undertow to service requests passing to OfficeFloor inversion of control.  Benchmark test with mature HTTP solution.
 * **officefloor-raw** : default HTTP server component provided by OfficeFloor.  This allows comparing OfficeFloor's default HTTP implementation with other solutions focused on HTTP optimisation.  It is also able to process requests concurrently on the same connection for improved responsiveness.
 
 Having these comparisons allows developers to see the trade-offs in using different HTTP components to handle HTTP request servicing.
@@ -52,12 +69,9 @@ Note: OfficeFloor's web plugins are called WoOF (Web on OfficeFloor).
 
 As mentioned, OfficeFloor uses different threading models.  It does not inherit the threading model imposed by the HTTP component.  Hence, there are various threading models tested to see trade-offs:
 
-* **officefloor-tpr** : typical synchronous multi-threaded model with connections retrieved from connection pool
-* **officefloor-micro** : synchronous multi-threaded model with connections bound to threads (avoids connection pool bottleneck)
+* **officefloor-micro** : typical synchronous multi-threaded model with connections retrieved from connection pool
 * **officefloor-thread_affinity** : similar to micro, except thread pools are localised onto a particular CPU.  Hence all processing for a request is done on the same CPU (allowing much better instruction cache hits).  This effectively allows running without any synchronising of threads for potentially increased throughput.
 
-Note: the OfficeFloor team are working with [PostgreSql ADBA](https://github.com/pgjdbc/pgadba) for asynchronous database interaction via asynchronous threading model.
-
 While much focus is on HTTP handling, performance also is impacted by database interaction.  The above tests use raw SQL queries to provide optimised through put.  However, in "real world" applications ORMs are typically used:
 
 * **officefloor** : WoOF implementation using EntityManager

+ 22 - 12
frameworks/Java/officefloor/benchmark_config.json

@@ -6,7 +6,9 @@
 				"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",
@@ -26,19 +28,26 @@
 			"raw": {
 				"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": "Platform",
+				"database": "Postgres",
 				"framework": "OfficeFloor",
 				"language": "Java",
 				"flavor": "None",
+				"orm": "raw",
 				"platform": "OfficeFloor",
 				"webserver": "WoOF",
 				"os": "Linux",
+				"database_os": "Linux",
 				"display_name": "OfficeFloor-raw",
 				"notes": ""
 			},
-			"tpr": {
+			"micro": {
 				"json_url": "/json",
 				"plaintext_url": "/plaintext",
 				"db_url": "/db",
@@ -57,11 +66,11 @@
 				"webserver": "WoOF",
 				"os": "Linux",
 				"database_os": "Linux",
-				"display_name": "OfficeFloor-tpr",
+				"display_name": "OfficeFloor-micro",
 				"notes": "",
 				"versus": "OfficeFloor-raw"
 			},
-			"micro": {
+			"thread_affinity": {
 				"json_url": "/json",
 				"plaintext_url": "/plaintext",
 				"db_url": "/db",
@@ -80,11 +89,11 @@
 				"webserver": "WoOF",
 				"os": "Linux",
 				"database_os": "Linux",
-				"display_name": "OfficeFloor-micro",
+				"display_name": "OfficeFloor-thread_affinity",
 				"notes": "",
 				"versus": "OfficeFloor-raw"
 			},
-			"thread_affinity": {
+			"netty": {
 				"json_url": "/json",
 				"plaintext_url": "/plaintext",
 				"db_url": "/db",
@@ -98,20 +107,22 @@
 				"framework": "OfficeFloor",
 				"language": "Java",
 				"flavor": "None",
-				"orm": "raw",
+				"orm": "Full",
 				"platform": "OfficeFloor",
 				"webserver": "WoOF",
 				"os": "Linux",
 				"database_os": "Linux",
-				"display_name": "OfficeFloor-thread_affinity",
+				"display_name": "OfficeFloor-netty",
 				"notes": "",
-				"versus": "OfficeFloor-raw"
+				"versus": "netty"
 			},
-			"netty": {
+			"undertow": {
 				"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",
@@ -124,9 +135,8 @@
 				"webserver": "WoOF",
 				"os": "Linux",
 				"database_os": "Linux",
-				"display_name": "OfficeFloor-netty",
-				"notes": "",
-				"versus": "netty"
+				"display_name": "OfficeFloor-undertow",
+				"notes": ""
 			},
 			"spring_data": {
 				"json_url": "/json",

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

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

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

@@ -1,4 +1,4 @@
-FROM maven:3.6.1-jdk-11-slim as maven
+FROM maven:slim as maven
 WORKDIR /officefloor
 COPY src src
 WORKDIR /officefloor/src
@@ -8,7 +8,7 @@ RUN mvn -q clean install
 WORKDIR /officefloor/src/woof_benchmark_netty
 RUN mvn -q clean package
 
-FROM openjdk:11.0.3-jdk-slim
+FROM openjdk:slim
 WORKDIR /officefloor
 COPY --from=maven /officefloor/src/woof_benchmark_netty/target/woof_benchmark_netty-1.0.0.jar server.jar
 CMD ["java", "-server", "-Xms2g", "-Xmx2g", "-XX:+UseNUMA", "-Dhttp.port=8080", "-Dhttp.server.name=OF", "-Dhttp.date.header=true", "-jar", "server.jar"]

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

@@ -1,10 +1,10 @@
-FROM maven:3.6.1-jdk-11-slim as maven
+FROM maven:slim as maven
 WORKDIR /officefloor
 COPY src src
 WORKDIR /officefloor/src/woof_benchmark_raw
 RUN mvn -q clean package
 
-FROM openjdk:11.0.3-jdk-slim
+FROM openjdk:slim
 RUN apt-get update && apt-get install -y libjna-java
 WORKDIR /officefloor
 COPY --from=maven /officefloor/src/woof_benchmark_raw/target/woof_benchmark_raw-1.0.0.jar server.jar

+ 2 - 2
frameworks/Java/officefloor/officefloor-spring_data.dockerfile

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

+ 2 - 2
frameworks/Java/officefloor/officefloor-thread_affinity.dockerfile

@@ -1,4 +1,4 @@
-FROM maven:3.6.1-jdk-11-slim as maven
+FROM maven:slim as maven
 WORKDIR /officefloor
 COPY src src
 WORKDIR /officefloor/src
@@ -8,7 +8,7 @@ RUN mvn -q clean install
 WORKDIR /officefloor/src/woof_benchmark_thread_affinity
 RUN mvn -q clean package
 
-FROM openjdk:11.0.3-jdk-slim
+FROM openjdk:slim
 RUN apt-get update && apt-get install -y libjna-java
 WORKDIR /officefloor
 COPY --from=maven /officefloor/src/woof_benchmark_thread_affinity/target/woof_benchmark_thread_affinity-1.0.0.jar server.jar

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

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

+ 14 - 0
frameworks/Java/officefloor/officefloor-undertow.dockerfile

@@ -0,0 +1,14 @@
+FROM maven:slim as maven
+WORKDIR /officefloor
+COPY src src
+WORKDIR /officefloor/src
+RUN mvn -q -N clean install
+WORKDIR /officefloor/src/woof_benchmark
+RUN mvn -q clean install
+WORKDIR /officefloor/src/woof_benchmark_undertow
+RUN mvn -q clean package
+
+FROM openjdk:slim
+WORKDIR /officefloor
+COPY --from=maven /officefloor/src/woof_benchmark_undertow/target/woof_benchmark_undertow-1.0.0.jar server.jar
+CMD ["java", "-server", "-Xms2g", "-Xmx2g", "-XX:+UseNUMA", "-Dhttp.port=8080", "-Dhttp.server.name=OF", "-Dhttp.date.header=true", "-jar", "server.jar"]

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

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

+ 9 - 4
frameworks/Java/officefloor/src/pom.xml

@@ -15,18 +15,23 @@
 	</properties>
 	<modules>
 		<module>woof_benchmark</module>
-		<module>woof_benchmark_tpr</module>
 		<module>woof_benchmark_micro</module>
 		<module>woof_benchmark_thread_affinity</module>
 		<module>woof_benchmark_raw</module>
 		<module>woof_benchmark_netty</module>
+		<module>woof_benchmark_undertow</module>
 		<module>woof_benchmark_spring</module>
 	</modules>
 	<repositories>
+		<repository>
+			<id>Central first</id>
+			<name>List central first so is used first</name>
+			<url>https://repo.maven.apache.org/maven2/</url>
+		</repository>
 		<repository>
 			<id>maven2-repository.officefloor.net</id>
 			<name>OfficeFloor staging repository for Maven</name>
-			<url>http://www.officefloor.net/maven2/</url>
+			<url>https://github.com/officefloor/maven/raw/repository/</url>
 		</repository>
 	</repositories>
 	<dependencyManagement>
@@ -34,7 +39,7 @@
 			<dependency>
 				<groupId>net.officefloor</groupId>
 				<artifactId>bom</artifactId>
-				<version>3.22.0</version>
+				<version>3.28.0</version>
 				<type>pom</type>
 				<scope>import</scope>
 			</dependency>
@@ -64,7 +69,7 @@
 				<plugin>
 					<groupId>org.apache.maven.plugins</groupId>
 					<artifactId>maven-shade-plugin</artifactId>
-					<version>3.2.2</version>
+					<version>3.2.4</version>
 				</plugin>
 			</plugins>
 		</pluginManagement>

+ 9 - 6
frameworks/Java/officefloor/src/woof_benchmark/pom.xml

@@ -1,6 +1,5 @@
 <?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">
 	<modelVersion>4.0.0</modelVersion>
 	<parent>
@@ -20,11 +19,15 @@
 		</dependency>
 		<dependency>
 			<groupId>net.officefloor.persistence</groupId>
-			<artifactId>officejpa_hibernate</artifactId>
+			<artifactId>officejdbc_postgresql</artifactId>
 		</dependency>
 		<dependency>
 			<groupId>net.officefloor.persistence</groupId>
-			<artifactId>officejdbc_postgresql</artifactId>
+			<artifactId>officejdbc_hikari</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>net.officefloor.persistence</groupId>
+			<artifactId>officejpa_hibernate</artifactId>
 		</dependency>
 		<dependency>
 			<groupId>org.projectlombok</groupId>
@@ -45,10 +48,10 @@
 						</goals>
 						<configuration>
 							<transformers>
-								<transformer
-									implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+								<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
 									<mainClass>net.officefloor.OfficeFloorMain</mainClass>
 								</transformer>
+								<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
 							</transformers>
 						</configuration>
 					</execution>

+ 54 - 12
frameworks/Java/officefloor/src/woof_benchmark/src/main/java/net/officefloor/benchmark/Logic.java

@@ -1,6 +1,7 @@
 package net.officefloor.benchmark;
 
-import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.concurrent.ThreadLocalRandom;
 
 import javax.persistence.EntityManager;
@@ -14,6 +15,13 @@ import net.officefloor.web.ObjectResponse;
  */
 public class Logic {
 
+	private static ThreadLocal<Set<Integer>> uniqueIdentifiers = new ThreadLocal<>() {
+		@Override
+		protected Set<Integer> initialValue() {
+			return new HashSet<>(100);
+		}
+	};
+
 	// =========== JSON ===================
 
 	@Data
@@ -35,11 +43,26 @@ public class Logic {
 
 	public void queries(@HttpQueryParameter("queries") String queries, EntityManager entityManager,
 			ObjectResponse<World[]> response) {
-		ThreadLocalRandom random = ThreadLocalRandom.current();
 		int count = getQueryCount(queries);
+
+		// Set up for unique numbers
+		ThreadLocalRandom random = ThreadLocalRandom.current();
+		Set<Integer> uniqueSet = uniqueIdentifiers.get();
+		uniqueSet.clear();
+
+		// Obtain the list of worlds
 		World[] worlds = new World[count];
 		for (int i = 0; i < worlds.length; i++) {
-			worlds[i] = entityManager.find(World.class, random.nextInt(1, 10001));
+
+			// Obtain unique identifier
+			int randomNumber;
+			do {
+				randomNumber = random.nextInt(1, 10001);
+			} while (uniqueSet.contains(randomNumber));
+			uniqueSet.add(randomNumber);
+
+			// Obtain the world (unique id so always queries)
+			worlds[i] = entityManager.find(World.class, randomNumber);
 		}
 		response.send(worlds);
 	}
@@ -48,17 +71,36 @@ public class Logic {
 
 	public void update(@HttpQueryParameter("queries") String queries, EntityManager entityManager,
 			ObjectResponse<World[]> response) {
-		ThreadLocalRandom random = ThreadLocalRandom.current();
 		int count = getQueryCount(queries);
-		int[] ids = new int[count];
-		for (int i = 0; i < ids.length; i++) {
-			ids[i] = random.nextInt(1, 10001);
-		}
-		Arrays.sort(ids);
+
+		// Set up for unique numbers
+		ThreadLocalRandom random = ThreadLocalRandom.current();
+		Set<Integer> uniqueSet = uniqueIdentifiers.get();
+		uniqueSet.clear();
+
+		// Create list of worlds
 		World[] worlds = new World[count];
-		for (int i = 0; i < worlds.length; i++) {
-			worlds[i] = entityManager.find(World.class, ids[i]);
-			worlds[i].setRandomNumber(random.nextInt(1, 10001));
+		for (int i = 0; i < count; i++) {
+
+			// Obtain unique identifier
+			int randomNumber;
+			do {
+				randomNumber = random.nextInt(1, 10001);
+			} while (uniqueSet.contains(randomNumber));
+			uniqueSet.add(randomNumber);
+
+			// Obtain the world
+			World world = entityManager.find(World.class, randomNumber);
+			worlds[i] = world;
+
+			// Ensure change to different random number
+			int existing = world.getRandomNumber();
+			do {
+				randomNumber = random.nextInt(1, 10001);
+			} while (randomNumber == existing);
+
+			// Update to different number to cause update
+			world.setRandomNumber(randomNumber);
 		}
 		response.send(worlds);
 	}

+ 5 - 7
frameworks/Java/officefloor/src/woof_benchmark/src/main/resources/application.objects

@@ -1,14 +1,12 @@
 <objects>
 
-	<managed-object source="net.officefloor.jdbc.ConnectionManagedObjectSource" type="java.sql.Connection">
-		<property-file path="datasource.properties" />
-		<pool source="net.officefloor.jdbc.pool.ThreadLocalJdbcConnectionPoolSource">
-			<property name="maximum.connections" value="200" />
-		</pool>
-	</managed-object>
-
 	<managed-object source="net.officefloor.jpa.hibernate.HibernateJpaManagedObjectSource" type="javax.persistence.EntityManager">
 		<property-file path="entitymanager.properties" />
+		<property name="persistence.dependency" value="datasource" />
+	</managed-object>
+
+	<managed-object source="net.officefloor.jdbc.DataSourceManagedObjectSource">
+		<property-file path="datasource.properties" />
 	</managed-object>
 
 </objects>

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

@@ -1,5 +1,5 @@
 <teams>
 
-	<team size="200" source="net.officefloor.frame.impl.spi.team.ExecutorCachedTeamSource" type="javax.persistence.EntityManager" />
+	<team size="512" source="net.officefloor.frame.impl.spi.team.ExecutorCachedTeamSource" type="javax.persistence.EntityManager" />
 
 </teams>

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

@@ -1,6 +1,7 @@
-datasource.factory=net.officefloor.jdbc.postgresql.PostgreSqlConnectionManagedObjectSource
+datasource.factory=net.officefloor.jdbc.postgresql.PostgreSqlDataSourceManagedObjectSource
 server=tfb-database
 port=5432
 database=hello_world
 user=benchmarkdbuser
 password=benchmarkdbpass
+maximumPoolSize=512

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

@@ -1,4 +1,4 @@
 persistence.unit.name=benchmarks
-persistence.dependency=connection
 hibernate.order_updates=true
-hibernate.dialect=org.hibernate.dialect.PostgreSQL9Dialect
+hibernate.dialect=org.hibernate.dialect.PostgreSQL9Dialect
+hibernate.archive.scanner=org.hibernate.boot.archive.scan.internal.DisabledScanner

+ 7 - 4
frameworks/Java/officefloor/src/woof_benchmark_micro/pom.xml

@@ -1,6 +1,5 @@
 <?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">
 	<modelVersion>4.0.0</modelVersion>
 	<parent>
@@ -22,6 +21,10 @@
 			<groupId>net.officefloor.persistence</groupId>
 			<artifactId>officejdbc_postgresql</artifactId>
 		</dependency>
+		<dependency>
+			<groupId>net.officefloor.persistence</groupId>
+			<artifactId>officejdbc_hikari</artifactId>
+		</dependency>
 		<dependency>
 			<groupId>org.projectlombok</groupId>
 			<artifactId>lombok</artifactId>
@@ -41,10 +44,10 @@
 						</goals>
 						<configuration>
 							<transformers>
-								<transformer
-									implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+								<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
 									<mainClass>net.officefloor.OfficeFloorMain</mainClass>
 								</transformer>
+								<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
 							</transformers>
 						</configuration>
 					</execution>

+ 76 - 66
frameworks/Java/officefloor/src/woof_benchmark_micro/src/main/java/net/officefloor/benchmark/Logic.java

@@ -11,6 +11,8 @@ 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;
@@ -39,65 +41,71 @@ public class Logic {
 
 	// ============ DB ====================
 
-	public void db(Connection connection, ObjectResponse<World> response) throws SQLException {
-		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, 10001));
-			ResultSet resultSet = statement.executeQuery();
-			resultSet.next();
-			World world = new World(resultSet.getInt(1), resultSet.getInt(2));
-			response.send(world);
+	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, 10001));
+				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, Connection connection,
+	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 (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, 10001));
-				ResultSet resultSet = statement.executeQuery();
-				resultSet.next();
-				worlds[i] = new World(resultSet.getInt(1), resultSet.getInt(2));
+		try (Connection connection = dataSource.getConnection()) {
+			ThreadLocalRandom random = ThreadLocalRandom.current();
+			int count = getQueryCount(queries);
+			World[] worlds = new World[count];
+			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, 10001));
+					ResultSet resultSet = statement.executeQuery();
+					resultSet.next();
+					worlds[i] = new World(resultSet.getInt(1), resultSet.getInt(2));
+				}
+				response.send(worlds);
 			}
-			response.send(worlds);
 		}
 	}
 
 	// =========== UPDATES ===================
 
-	public void update(@HttpQueryParameter("queries") String queries, Connection connection,
+	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 (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, 10001));
-				ResultSet resultSet = statement.executeQuery();
-				resultSet.next();
-				worlds[i] = new World(resultSet.getInt(1), random.nextInt(1, 10001));
+		try (Connection connection = dataSource.getConnection()) {
+			ThreadLocalRandom random = ThreadLocalRandom.current();
+			int count = getQueryCount(queries);
+			World[] worlds = new World[count];
+			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, 10001));
+					ResultSet resultSet = statement.executeQuery();
+					resultSet.next();
+					worlds[i] = new World(resultSet.getInt(1), random.nextInt(1, 10001));
+				}
 			}
-		}
-		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();
+			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();
 			}
-			statement.executeBatch();
+			response.send(worlds);
 		}
-		response.send(worlds);
 	}
 
 	// =========== FORTUNES ==================
@@ -116,30 +124,32 @@ public class Logic {
 	private static final byte[] TEMPLATE_END = "</table></body></html>"
 			.getBytes(ServerHttpConnection.DEFAULT_HTTP_ENTITY_CHARSET);
 
-	public void fortunes(Connection connection, ServerHttpConnection httpConnection) throws IOException, SQLException {
-		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);
+	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);
 			}
-			writer.write(TEMPLATE_END);
 		}
 	}
 

+ 1 - 4
frameworks/Java/officefloor/src/woof_benchmark_micro/src/main/resources/application.objects

@@ -1,10 +1,7 @@
 <objects>
 
-	<managed-object source="net.officefloor.jdbc.ConnectionManagedObjectSource" type="java.sql.Connection">
+	<managed-object source="net.officefloor.jdbc.DataSourceManagedObjectSource">
 		<property-file path="datasource.properties" />
-		<pool source="net.officefloor.jdbc.pool.ThreadLocalJdbcConnectionPoolSource">
-			<property name="maximum.connections" value="200" />
-		</pool>
 	</managed-object>
 
 </objects>

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

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

+ 2 - 1
frameworks/Java/officefloor/src/woof_benchmark_micro/src/main/resources/datasource.properties

@@ -1,6 +1,7 @@
-datasource.factory=net.officefloor.jdbc.postgresql.PostgreSqlConnectionManagedObjectSource
+datasource.factory=net.officefloor.jdbc.postgresql.PostgreSqlDataSourceManagedObjectSource
 server=tfb-database
 port=5432
 database=hello_world
 user=benchmarkdbuser
 password=benchmarkdbpass
+maximumPoolSize=512

+ 2 - 2
frameworks/Java/officefloor/src/woof_benchmark_netty/pom.xml

@@ -31,10 +31,10 @@
 						</goals>
 						<configuration>
 							<transformers>
-								<transformer
-									implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+								<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
 									<mainClass>net.officefloor.OfficeFloorMain</mainClass>
 								</transformer>
+								<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
 							</transformers>
 						</configuration>
 					</execution>

+ 16 - 4
frameworks/Java/officefloor/src/woof_benchmark_raw/pom.xml

@@ -1,6 +1,5 @@
 <?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">
 	<modelVersion>4.0.0</modelVersion>
 	<parent>
@@ -22,6 +21,19 @@
 			<groupId>com.fasterxml.jackson.module</groupId>
 			<artifactId>jackson-module-afterburner</artifactId>
 		</dependency>
+		<dependency>
+			<groupId>io.r2dbc</groupId>
+			<artifactId>r2dbc-postgresql</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>io.r2dbc</groupId>
+			<artifactId>r2dbc-pool</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.github.spullara.mustache.java</groupId>
+			<artifactId>compiler</artifactId>
+			<version>0.9.7</version>
+		</dependency>
 		<dependency>
 			<groupId>org.projectlombok</groupId>
 			<artifactId>lombok</artifactId>
@@ -41,10 +53,10 @@
 						</goals>
 						<configuration>
 							<transformers>
-								<transformer
-									implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+								<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
 									<mainClass>net.officefloor.benchmark.RawOfficeFloorMain</mainClass>
 								</transformer>
+								<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
 							</transformers>
 						</configuration>
 					</execution>

+ 281 - 15
frameworks/Java/officefloor/src/woof_benchmark_raw/src/main/java/net/officefloor/benchmark/RawOfficeFloorMain.java

@@ -18,18 +18,32 @@
 package net.officefloor.benchmark;
 
 import java.io.IOException;
+import java.io.Writer;
 import java.nio.ByteBuffer;
 import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.Collections;
+import java.util.List;
 import java.util.Timer;
 import java.util.TimerTask;
 import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadLocalRandom;
 import java.util.logging.Logger;
 
+import org.apache.commons.text.StringEscapeUtils;
+
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
-
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.Mustache;
+import com.github.mustachejava.MustacheFactory;
+
+import io.r2dbc.spi.Connection;
+import io.r2dbc.spi.ConnectionFactories;
+import io.r2dbc.spi.ConnectionFactory;
+import io.r2dbc.spi.ConnectionFactoryOptions;
+import lombok.AllArgsConstructor;
 import lombok.Data;
 import net.officefloor.frame.api.manage.OfficeFloor;
 import net.officefloor.frame.api.manage.ProcessManager;
@@ -51,6 +65,8 @@ import net.officefloor.server.http.impl.ProcessAwareServerHttpConnectionManagedO
 import net.officefloor.server.http.parse.HttpRequestParser.HttpRequestParserMetaData;
 import net.officefloor.server.stream.StreamBufferPool;
 import net.officefloor.server.stream.impl.ThreadLocalStreamBufferPool;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
 
 /**
  * <p>
@@ -83,6 +99,23 @@ public class RawOfficeFloorMain {
 			socketManager.shutdown();
 		}
 
+		// Indicate details
+		String server = System.getProperty("OFFICE.net_officefloor_jdbc_DataSourceManagedObjectSource.server",
+				"tfb-database");
+		System.out.println("Starting server on port " + port + " talking to database " + server);
+
+		// Increase the buffer size
+		System.setProperty("reactor.bufferSize.small", String.valueOf(10000));
+
+		// Build the connection pool
+		ConnectionFactoryOptions factoryOptions = ConnectionFactoryOptions.builder()
+				.option(ConnectionFactoryOptions.DRIVER, "pool").option(ConnectionFactoryOptions.PROTOCOL, "postgresql")
+				.option(ConnectionFactoryOptions.HOST, server).option(ConnectionFactoryOptions.PORT, 5432)
+				.option(ConnectionFactoryOptions.DATABASE, "hello_world")
+				.option(ConnectionFactoryOptions.USER, "benchmarkdbuser")
+				.option(ConnectionFactoryOptions.PASSWORD, "benchmarkdbpass").build();
+		ConnectionFactory connectionFactory = ConnectionFactories.get(factoryOptions);
+
 		// Create the server location
 		HttpServerLocation serverLocation = new HttpServerLocationImpl("localhost", port, -1);
 
@@ -91,6 +124,7 @@ public class RawOfficeFloorMain {
 		for (int i = 0; i < executionStrategy.length; i++) {
 			executionStrategy[i] = (runnable) -> new Thread(runnable);
 		}
+		System.out.println("Using " + executionStrategy.length + " executors");
 
 		// Create the socket manager
 		socketManager = HttpServerSocketManagedObjectSource.createSocketManager(executionStrategy);
@@ -98,7 +132,8 @@ public class RawOfficeFloorMain {
 		// Create raw HTTP servicing
 		StreamBufferPool<ByteBuffer> serviceBufferPool = new ThreadLocalStreamBufferPool(
 				() -> ByteBuffer.allocateDirect(8046), Integer.MAX_VALUE, Integer.MAX_VALUE);
-		RawHttpServicerFactory serviceFactory = new RawHttpServicerFactory(serverLocation, serviceBufferPool);
+		RawHttpServicerFactory serviceFactory = new RawHttpServicerFactory(serverLocation, serviceBufferPool,
+				connectionFactory);
 		socketManager.bindServerSocket(serverLocation.getClusterHttpPort(), null, null, serviceFactory, serviceFactory);
 
 		// Setup Date
@@ -110,9 +145,10 @@ public class RawOfficeFloorMain {
 		for (int i = 0; i < runnables.length; i++) {
 			executionStrategy[i].newThread(runnables[i]).start();
 		}
+		Thread.sleep(1000); // allow threads to start up
 
 		// Indicate running
-		System.out.println("OfficeFloor raw running");
+		System.out.println("OfficeFloor raw running on port " + serverLocation.getClusterHttpPort());
 	}
 
 	/**
@@ -128,10 +164,16 @@ public class RawOfficeFloorMain {
 
 		private static byte[] HELLO_WORLD = "Hello, World!".getBytes(ServerHttpConnection.DEFAULT_HTTP_ENTITY_CHARSET);
 
-		private static HttpHeaderValue APPLICATION_JSON = new HttpHeaderValue("application/json");
+		private static final HttpHeaderValue APPLICATION_JSON = new HttpHeaderValue("application/json");
 
 		private static final HttpHeaderValue TEXT_PLAIN = new HttpHeaderValue("text/plain");
 
+		private static final HttpHeaderValue TEXT_HTML = new HttpHeaderValue("text/html;charset=utf-8");
+
+		private static final String QUERIES_PATH_PREFIX = "/queries?queries=";
+
+		private static final String UPDATE_PATH_PREFIX = "/update?queries=";
+
 		/**
 		 * <code>Date</code> {@link HttpHeaderValue}.
 		 */
@@ -171,17 +213,85 @@ public class RawOfficeFloorMain {
 			}
 		};
 
+		/**
+		 * {@link ConnectionFactory}.
+		 */
+		private final ConnectionFactory connectionFactory;
+
+		/**
+		 * {@link ThreadLocal} {@link Connection}.
+		 */
+		private final ThreadLocal<Connection> threadLocalConnection;
+
+		/**
+		 * {@link Mono} to service /db.
+		 */
+		private final Mono<World> db;
+
+		/**
+		 * {@link Mustache} for /fortunes.
+		 */
+		private final Mustache fortuneMustache;
+
+		/**
+		 * {@link Mono} to service /fortunes.
+		 */
+		private final Mono<List<Fortune>> fortunes;
+
 		/**
 		 * Instantiate.
 		 *
 		 * @param serverLocation    {@link HttpServerLocation}.
 		 * @param serviceBufferPool {@link StreamBufferPool}.
+		 * @param connectionFactory {@link ConnectionFactory}.
 		 */
-		public RawHttpServicerFactory(HttpServerLocation serverLocation,
-				StreamBufferPool<ByteBuffer> serviceBufferPool) {
+		public RawHttpServicerFactory(HttpServerLocation serverLocation, StreamBufferPool<ByteBuffer> serviceBufferPool,
+				ConnectionFactory connectionFactory) {
 			super(serverLocation, false, new HttpRequestParserMetaData(100, 1000, 1000000), serviceBufferPool, null,
 					null, true);
 			this.objectMapper.registerModule(new AfterburnerModule());
+			this.connectionFactory = connectionFactory;
+
+			// Create thread local connection
+			this.threadLocalConnection = new ThreadLocal<Connection>() {
+				@Override
+				protected Connection initialValue() {
+					return Mono.from(RawHttpServicerFactory.this.connectionFactory.create()).block();
+				}
+			};
+
+			// Create the db logic
+			this.db = Mono
+					.from(this.threadLocalConnection.get()
+							.createStatement("SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID = $1")
+							.bind(0, ThreadLocalRandom.current().nextInt(1, 10001)).execute())
+					.flatMap(result -> Mono.from(result.map((row, metadata) -> {
+						Integer id = row.get(0, Integer.class);
+						Integer number = row.get(1, Integer.class);
+						return new World(id, number);
+					})));
+
+			// Load the mustache fortunes template
+			MustacheFactory mustacheFactory = new DefaultMustacheFactory() {
+				@Override
+				public void encode(String value, Writer writer) {
+					try {
+						StringEscapeUtils.ESCAPE_HTML4.translate(value, writer);
+					} catch (IOException ex) {
+						ex.printStackTrace();
+					}
+				}
+			};
+			this.fortuneMustache = mustacheFactory.compile("fortunes.mustache");
+
+			// Create the fortunes logic
+			this.fortunes = Flux
+					.from(this.threadLocalConnection.get().createStatement("SELECT ID, MESSAGE FROM FORTUNE").execute())
+					.flatMap(result -> Flux.from(result.map((row, metadata) -> {
+						Integer id = row.get(0, Integer.class);
+						String message = row.get(1, String.class);
+						return new Fortune(id, message);
+					}))).collectList();
 		}
 
 		/**
@@ -223,27 +333,167 @@ public class RawOfficeFloorMain {
 			switch (requestUri) {
 
 			case "/plaintext":
-				response.setContentType(TEXT_PLAIN, null);
-				response.getEntity().write(HELLO_WORLD);
-				this.send(connection);
+				this.plaintext(response, connection);
 				break;
 
 			case "/json":
-				response.setContentType(APPLICATION_JSON, null);
-				this.objectMapper.writeValue(response.getEntityWriter(), new Message("Hello, World!"));
-				this.send(connection);
+				this.json(response, connection);
+				break;
+
+			case "/db":
+				this.db(response, connection);
+				break;
+
+			case "/fortunes":
+				this.fortunes(response, connection);
 				break;
 
 			default:
-				// Unknown request
-				response.setStatus(HttpStatus.NOT_FOUND);
-				this.send(connection);
+				// Provide redirect
+				if (requestUri.startsWith(QUERIES_PATH_PREFIX)) {
+					this.queries(requestUri, response, connection);
+
+				} else if (requestUri.startsWith(UPDATE_PATH_PREFIX)) {
+					this.update(requestUri, response, connection);
+
+				} else {
+					// Unknown request
+					response.setStatus(HttpStatus.NOT_FOUND);
+					this.send(connection);
+				}
 				break;
 			}
 
 			// No process management
 			return null;
 		}
+
+		private void plaintext(HttpResponse response,
+				ProcessAwareServerHttpConnectionManagedObject<ByteBuffer> connection) throws IOException {
+			response.setContentType(TEXT_PLAIN, null);
+			response.getEntity().write(HELLO_WORLD);
+			this.send(connection);
+		}
+
+		private void json(HttpResponse response, ProcessAwareServerHttpConnectionManagedObject<ByteBuffer> connection)
+				throws IOException {
+			response.setContentType(APPLICATION_JSON, null);
+			this.objectMapper.writeValue(response.getEntityWriter(), new Message("Hello, World!"));
+			this.send(connection);
+		}
+
+		private void db(HttpResponse response, ProcessAwareServerHttpConnectionManagedObject<ByteBuffer> connection) {
+			this.db.subscribe(world -> {
+				try {
+					response.setContentType(APPLICATION_JSON, null);
+					this.objectMapper.writeValue(response.getEntityWriter(), world);
+					this.send(connection);
+				} catch (IOException ex) {
+					ex.printStackTrace();
+				}
+			}, error -> {
+				this.sendError(connection, error);
+			});
+		}
+
+		private void queries(String requestUri, HttpResponse response,
+				ProcessAwareServerHttpConnectionManagedObject<ByteBuffer> connection) {
+			String queriesCountText = requestUri.substring(QUERIES_PATH_PREFIX.length());
+			int queryCount = getQueryCount(queriesCountText);
+			Flux.range(1, queryCount)
+					.flatMap(index -> this.threadLocalConnection.get()
+							.createStatement("SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID = $1")
+							.bind(0, ThreadLocalRandom.current().nextInt(1, 10001)).execute())
+					.flatMap(result -> Flux.from(result.map((row, metadata) -> {
+						Integer id = row.get(0, Integer.class);
+						Integer number = row.get(1, Integer.class);
+						return new World(id, number);
+					}))).collectList().subscribe(worlds -> {
+						try {
+							response.setContentType(APPLICATION_JSON, null);
+							this.objectMapper.writeValue(response.getEntityWriter(), worlds);
+							this.send(connection);
+						} catch (IOException ex) {
+							ex.printStackTrace();
+						}
+					}, error -> {
+						this.sendError(connection, error);
+					});
+		}
+
+		private void fortunes(HttpResponse response,
+				ProcessAwareServerHttpConnectionManagedObject<ByteBuffer> connection) {
+			this.fortunes.subscribe(fortunes -> {
+				try {
+					// Additional fortunes
+					fortunes.add(new Fortune(0, "Additional fortune added at request time."));
+					Collections.sort(fortunes, (a, b) -> a.message.compareTo(b.message));
+
+					// Send response
+					response.setContentType(TEXT_HTML, null);
+					this.fortuneMustache.execute(response.getEntityWriter(), fortunes);
+					this.send(connection);
+				} catch (IOException ex) {
+					ex.printStackTrace();
+				}
+			}, error -> {
+				this.sendError(connection, error);
+			});
+		}
+
+		private void update(String requestUri, HttpResponse response,
+				ProcessAwareServerHttpConnectionManagedObject<ByteBuffer> connection) {
+			String queriesCountText = requestUri.substring(UPDATE_PATH_PREFIX.length());
+			int queryCount = getQueryCount(queriesCountText);
+			Connection db = this.threadLocalConnection.get();
+			Flux.range(1, queryCount)
+					.flatMap(index -> db.createStatement("SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID = $1")
+							.bind(0, ThreadLocalRandom.current().nextInt(1, 10001)).execute())
+					.flatMap(result -> Flux.from(result.map((row, metadata) -> {
+						Integer id = row.get(0, Integer.class);
+						Integer number = row.get(1, Integer.class);
+						return new World(id, number);
+					}))).flatMap(world -> {
+						world.randomNumber = ThreadLocalRandom.current().nextInt(1, 10001);
+						return Flux
+								.from(db.createStatement("UPDATE WORLD SET RANDOMNUMBER = $1 WHERE ID = $2")
+										.bind(0, world.randomNumber).bind(1, world.id).execute())
+								.flatMap(result -> Flux.from(result.getRowsUpdated()).map(updated -> world));
+					}).collectList().subscribe(worlds -> {
+						try {
+							response.setContentType(APPLICATION_JSON, null);
+							this.objectMapper.writeValue(response.getEntityWriter(), worlds);
+							this.send(connection);
+						} catch (IOException ex) {
+							ex.printStackTrace();
+						}
+					}, error -> {
+						this.sendError(connection, error);
+					});
+		}
+
+		private void sendError(ProcessAwareServerHttpConnectionManagedObject<ByteBuffer> connection,
+				Throwable failure) {
+			try {
+				failure.printStackTrace();
+
+				HttpResponse response = connection.getResponse();
+				response.reset();
+				response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
+				this.send(connection);
+			} catch (IOException ex) {
+				ex.printStackTrace();
+			}
+		}
+
+		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;
+			}
+		}
 	}
 
 	@Data
@@ -251,4 +501,20 @@ public class RawOfficeFloorMain {
 		private final String message;
 	}
 
+	@Data
+	@AllArgsConstructor
+	public static class World {
+
+		private final int id;
+
+		private int randomNumber;
+	}
+
+	@Data
+	public static class Fortune {
+
+		private final int id;
+
+		private final String message;
+	}
 }

+ 1 - 0
frameworks/Java/officefloor/src/woof_benchmark_raw/src/main/resources/fortunes.mustache

@@ -0,0 +1 @@
+<!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>

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

@@ -39,7 +39,7 @@
 			<plugin>
 				<groupId>org.springframework.boot</groupId>
 				<artifactId>spring-boot-maven-plugin</artifactId>
-				<version>2.2.6.RELEASE</version>
+				<version>2.3.5.RELEASE</version>
 				<configuration>
 					<mainClass>net.officefloor.OfficeFloorMain</mainClass>
 					<classifier>exec</classifier>

+ 55 - 13
frameworks/Java/officefloor/src/woof_benchmark_spring/src/main/java/net/officefloor/benchmark/Logic.java

@@ -1,6 +1,7 @@
 package net.officefloor.benchmark;
 
-import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.concurrent.ThreadLocalRandom;
 
 import lombok.Data;
@@ -12,6 +13,13 @@ import net.officefloor.web.ObjectResponse;
  */
 public class Logic {
 
+	private static ThreadLocal<Set<Integer>> uniqueIdentifiers = new ThreadLocal<>() {
+		@Override
+		protected Set<Integer> initialValue() {
+			return new HashSet<>(100);
+		}
+	};
+
 	// =========== JSON ===================
 
 	@Data
@@ -33,11 +41,26 @@ public class Logic {
 
 	public void queries(@HttpQueryParameter("queries") String queries, WorldRepository repository,
 			ObjectResponse<World[]> response) {
-		ThreadLocalRandom random = ThreadLocalRandom.current();
 		int count = getQueryCount(queries);
+
+		// Set up for unique numbers
+		ThreadLocalRandom random = ThreadLocalRandom.current();
+		Set<Integer> uniqueSet = uniqueIdentifiers.get();
+		uniqueSet.clear();
+
+		// Obtain the list of worlds
 		World[] worlds = new World[count];
 		for (int i = 0; i < worlds.length; i++) {
-			worlds[i] = repository.findById(random.nextInt(1, 10001)).get();
+
+			// Obtain unique identifier
+			int randomNumber;
+			do {
+				randomNumber = random.nextInt(1, 10001);
+			} while (uniqueSet.contains(randomNumber));
+			uniqueSet.add(randomNumber);
+
+			// Obtain the world (unique id so always queries)
+			worlds[i] = repository.findById(randomNumber).get();
 		}
 		response.send(worlds);
 	}
@@ -46,18 +69,37 @@ public class Logic {
 
 	public void update(@HttpQueryParameter("queries") String queries, WorldRepository repository,
 			ObjectResponse<World[]> response) {
-		ThreadLocalRandom random = ThreadLocalRandom.current();
 		int count = getQueryCount(queries);
-		int[] ids = new int[count];
-		for (int i = 0; i < ids.length; i++) {
-			ids[i] = random.nextInt(1, 10001);
-		}
-		Arrays.sort(ids);
+
+		// Set up for unique numbers
+		ThreadLocalRandom random = ThreadLocalRandom.current();
+		Set<Integer> uniqueSet = uniqueIdentifiers.get();
+		uniqueSet.clear();
+
+		// Create list of worlds
 		World[] worlds = new World[count];
-		for (int i = 0; i < worlds.length; i++) {
-			worlds[i] = repository.findById(ids[i]).get();
-			worlds[i].setRandomNumber(random.nextInt(1, 10001));
-			repository.save(worlds[i]);
+		for (int i = 0; i < count; i++) {
+
+			// Obtain unique identifier
+			int randomNumber;
+			do {
+				randomNumber = random.nextInt(1, 10001);
+			} while (uniqueSet.contains(randomNumber));
+			uniqueSet.add(randomNumber);
+
+			// Obtain the world
+			World world = repository.findById(randomNumber).get();
+			worlds[i] = world;
+
+			// Ensure change to different random number
+			int existing = world.getRandomNumber();
+			do {
+				randomNumber = random.nextInt(1, 10001);
+			} while (randomNumber == existing);
+
+			// Update to different number to cause update
+			world.setRandomNumber(randomNumber);
+			repository.save(world);
 		}
 		response.send(worlds);
 	}

+ 1 - 0
frameworks/Java/officefloor/src/woof_benchmark_spring/src/main/resources/application.properties

@@ -4,6 +4,7 @@ spring.datasource.password=benchmarkdbpass
 
 spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
 spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
+spring.jpa.hibernate.archive.scanner=org.hibernate.boot.archive.scan.internal.DisabledScanner
 
 # avoid clob create issue
 spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true

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

@@ -1,6 +1,6 @@
 <teams>
 
-	<team size="200" source="net.officefloor.frame.impl.spi.team.ExecutorCachedTeamSource">	
+	<team size="512" source="net.officefloor.frame.impl.spi.team.ExecutorCachedTeamSource">	
 		<auto-wire type="net.officefloor.benchmark.WorldRepository" />
 		<auto-wire type="net.officefloor.benchmark.FortuneRepository" />
 	</team>

+ 3 - 4
frameworks/Java/officefloor/src/woof_benchmark_thread_affinity/pom.xml

@@ -1,6 +1,5 @@
 <?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">
 	<modelVersion>4.0.0</modelVersion>
 	<parent>
@@ -32,10 +31,10 @@
 						</goals>
 						<configuration>
 							<transformers>
-								<transformer
-									implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+								<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
 									<mainClass>net.officefloor.OfficeFloorMain</mainClass>
 								</transformer>
+								<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
 							</transformers>
 						</configuration>
 					</execution>

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

@@ -1,24 +0,0 @@
-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());
-	}
-}

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

@@ -1,167 +0,0 @@
-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, 10001));
-				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, 10001));
-					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, 10001));
-					ResultSet resultSet = statement.executeQuery();
-					resultSet.next();
-					worlds[i] = new World(resultSet.getInt(1), random.nextInt(1, 10001));
-				}
-			}
-			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;
-		}
-	}
-
-}

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

@@ -1,15 +0,0 @@
-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;
-}

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

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

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

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

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

@@ -1,46 +0,0 @@
-<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>

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

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

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

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

+ 8 - 23
frameworks/Java/officefloor/src/woof_benchmark_tpr/pom.xml → frameworks/Java/officefloor/src/woof_benchmark_undertow/pom.xml

@@ -1,6 +1,5 @@
 <?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">
 	<modelVersion>4.0.0</modelVersion>
 	<parent>
@@ -8,29 +7,15 @@
 		<artifactId>benchmarks</artifactId>
 		<version>1.0.0</version>
 	</parent>
-	<artifactId>woof_benchmark_tpr</artifactId>
-	<description>Thread per request model used by many frameworks</description>
+	<artifactId>woof_benchmark_undertow</artifactId>
 	<dependencies>
 		<dependency>
-			<groupId>net.officefloor.web</groupId>
-			<artifactId>woof</artifactId>
+			<groupId>net.officefloor.benchmarks</groupId>
+			<artifactId>woof_benchmark</artifactId>
 		</dependency>
 		<dependency>
-			<groupId>net.officefloor.web</groupId>
-			<artifactId>officejson_jackson</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>net.officefloor.persistence</groupId>
-			<artifactId>officejdbc_postgresql</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>com.zaxxer</groupId>
-			<artifactId>HikariCP</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.projectlombok</groupId>
-			<artifactId>lombok</artifactId>
-			<scope>provided</scope>
+			<groupId>net.officefloor.server</groupId>
+			<artifactId>officeserver_undertow</artifactId>
 		</dependency>
 	</dependencies>
 	<build>
@@ -46,10 +31,10 @@
 						</goals>
 						<configuration>
 							<transformers>
-								<transformer
-									implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+								<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
 									<mainClass>net.officefloor.OfficeFloorMain</mainClass>
 								</transformer>
+								<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
 							</transformers>
 						</configuration>
 					</execution>