Browse Source

Quarkus Vertx + PgClient and renaming (#8606)

Francesco Nigro 1 year ago
parent
commit
4895b0562b
51 changed files with 2006 additions and 2 deletions
  1. 49 2
      frameworks/Java/quarkus/benchmark_config.json
  2. 34 0
      frameworks/Java/quarkus/config.toml
  3. 2 0
      frameworks/Java/quarkus/pom.xml
  4. 2 0
      frameworks/Java/quarkus/quarkus-hibernate-reactive.dockerfile
  5. 45 0
      frameworks/Java/quarkus/quarkus-reactive-routes-pgclient.dockerfile
  6. 45 0
      frameworks/Java/quarkus/quarkus-vertx.dockerfile
  7. 2 0
      frameworks/Java/quarkus/quarkus.dockerfile
  8. 86 0
      frameworks/Java/quarkus/reactive-routes-pgclient/pom.xml
  9. 36 0
      frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/filter/ServerHeaderFilter.java
  10. 25 0
      frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/model/Fortune.java
  11. 29 0
      frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/model/World.java
  12. 29 0
      frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/repository/FortuneRepository.java
  13. 58 0
      frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/repository/PgClientFactory.java
  14. 33 0
      frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/repository/PgClients.java
  15. 59 0
      frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/repository/WorldRepository.java
  16. 32 0
      frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/resource/BaseResource.java
  17. 102 0
      frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/resource/DbResource.java
  18. 43 0
      frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/resource/FortuneResource.java
  19. 12 0
      frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/resource/JsonMessage.java
  20. 17 0
      frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/resource/JsonResource.java
  21. 24 0
      frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/resource/PlaintextResource.java
  22. 65 0
      frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/rocker/RawRockerOutput.java
  23. 15 0
      frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/rocker/VertxRawRockerOutput.java
  24. 29 0
      frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/rocker/VertxRawRockerOutputFactories.java
  25. 11 0
      frameworks/Java/quarkus/reactive-routes-pgclient/src/main/resources/application.properties
  26. 1 0
      frameworks/Java/quarkus/reactive-routes-pgclient/src/main/resources/import.sql
  27. 8 0
      frameworks/Java/quarkus/reactive-routes-pgclient/src/main/resources/views/Fortunes.rocker.html
  28. 1 0
      frameworks/Java/quarkus/run_quarkus.sh
  29. 83 0
      frameworks/Java/quarkus/vertx/pom.xml
  30. 40 0
      frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/filter/HttpResponseDecorator.java
  31. 24 0
      frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/model/Fortune.java
  32. 12 0
      frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/model/JsonMessage.java
  33. 24 0
      frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/model/World.java
  34. 44 0
      frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/repository/FortuneRepository.java
  35. 67 0
      frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/repository/PgClientFactory.java
  36. 208 0
      frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/repository/PgConnectionPool.java
  37. 187 0
      frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/repository/WorldRepository.java
  38. 30 0
      frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/resource/DbHttpHandler.java
  39. 34 0
      frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/resource/FortunesHttpHandler.java
  40. 20 0
      frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/resource/HttpQueryParameterUtils.java
  41. 109 0
      frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/resource/HttpRoutes.java
  42. 21 0
      frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/resource/JsonHttpHandler.java
  43. 24 0
      frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/resource/PlaintextHttpHandler.java
  44. 28 0
      frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/resource/QueriesHttpHandler.java
  45. 29 0
      frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/resource/UpdateHttpHandler.java
  46. 65 0
      frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/rocker/RawRockerOutput.java
  47. 15 0
      frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/rocker/VertxRawRockerOutput.java
  48. 29 0
      frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/rocker/VertxRawRockerOutputFactories.java
  49. 10 0
      frameworks/Java/quarkus/vertx/src/main/resources/application.properties
  50. 1 0
      frameworks/Java/quarkus/vertx/src/main/resources/import.sql
  51. 8 0
      frameworks/Java/quarkus/vertx/src/main/resources/views/Fortunes.rocker.html

+ 49 - 2
frameworks/Java/quarkus/benchmark_config.json

@@ -1,5 +1,6 @@
 {
   "framework": "quarkus",
+  "maintainers": ["franz1981", "Sanne", "geoand"],
   "tests": [
     {
       "default": {
@@ -21,7 +22,7 @@
         "webserver": "Vert.x",
         "os": "Linux",
         "database_os": "Linux",
-        "display_name": "Quarkus, Hibernate ORM",
+        "display_name": "Quarkus, Hibernate",
         "notes": "",
         "versus": "Netty"
       },
@@ -44,9 +45,55 @@
         "webserver": "Vert.x",
         "os": "Linux",
         "database_os": "Linux",
-        "display_name": "Quarkus, Hibernate Reactive",
+        "display_name": "Quarkus, Reactive",
         "notes": "",
         "versus": "Netty"
+      },
+      "vertx": {
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "fortune_url": "/fortunes",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "Quarkus",
+        "language": "Java",
+        "flavor": "None",
+        "orm": "Raw",
+        "platform": "Vert.x",
+        "webserver": "Vert.x",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Quarkus, Vert.x",
+        "notes": "",
+        "versus": "Vert.x"
+      },
+      "reactive-routes-pgclient": {
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "fortune_url": "/fortunes",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "fullstack",
+        "database": "Postgres",
+        "framework": "Quarkus",
+        "language": "Java",
+        "flavor": "None",
+        "orm": "Raw",
+        "platform": "Vert.x",
+        "webserver": "Vert.x",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Quarkus, PostgreSQL",
+        "notes": "",
+        "versus": "Vert.x"
       }
     }
   ]

+ 34 - 0
frameworks/Java/quarkus/config.toml

@@ -34,3 +34,37 @@ orm = "Full"
 platform = "RESTEasy Reactive"
 webserver = "Vert.x"
 versus = "Netty"
+
+[vertx]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?queries="
+urls.update = "/updates?queries="
+urls.fortune = "/fortunes"
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "Vert.x"
+webserver = "Vert.x"
+versus = "Vert.x"
+
+[reactive-routes-pgclient]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?queries="
+urls.update = "/updates?queries="
+urls.fortune = "/fortunes"
+approach = "Realistic"
+classification = "fullstack"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "Vert.x"
+webserver = "Vert.x"
+versus = "Vert.x"

+ 2 - 0
frameworks/Java/quarkus/pom.xml

@@ -28,6 +28,8 @@
         <module>quarkus-benchmark-common</module>
         <module>resteasy-reactive-hibernate</module>
         <module>resteasy-reactive-hibernate-reactive</module>
+        <module>vertx</module>
+        <module>reactive-routes-pgclient</module>
     </modules>
 
     <dependencyManagement>

+ 2 - 0
frameworks/Java/quarkus/quarkus-hibernate-reactive.dockerfile

@@ -8,6 +8,8 @@ COPY --chown=185 pom.xml pom.xml
 COPY --chown=185 quarkus-benchmark-common quarkus-benchmark-common/
 COPY --chown=185 resteasy-reactive-hibernate resteasy-reactive-hibernate/
 COPY --chown=185 resteasy-reactive-hibernate-reactive resteasy-reactive-hibernate-reactive/
+COPY --chown=185 vertx vertx/
+COPY --chown=185 reactive-routes-pgclient reactive-routes-pgclient/
 
 # Uncomment to test pre-release quarkus
 #RUN mkdir -p /root/.m2/repository/io

+ 45 - 0
frameworks/Java/quarkus/quarkus-reactive-routes-pgclient.dockerfile

@@ -0,0 +1,45 @@
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.15 as maven
+ENV LANGUAGE='en_US:en'
+
+WORKDIR /quarkus
+ENV MODULE=reactive-routes-pgclient
+
+COPY --chown=185 pom.xml pom.xml
+COPY --chown=185 quarkus-benchmark-common quarkus-benchmark-common/
+COPY --chown=185 resteasy-reactive-hibernate resteasy-reactive-hibernate/
+COPY --chown=185 resteasy-reactive-hibernate-reactive resteasy-reactive-hibernate-reactive/
+COPY --chown=185 vertx vertx/
+COPY --chown=185 reactive-routes-pgclient reactive-routes-pgclient/
+
+# Uncomment to test pre-release quarkus
+#RUN mkdir -p /root/.m2/repository/io
+#COPY m2-quarkus /root/.m2/repository/io/quarkus
+
+USER 185
+WORKDIR /quarkus
+RUN mvn -DskipTests install -pl :benchmark,:quarkus-benchmark-common -B -q
+
+WORKDIR /quarkus/$MODULE
+RUN mvn dependency:go-offline -B -q
+WORKDIR /quarkus
+
+COPY $MODULE/src $MODULE/src
+
+WORKDIR /quarkus/$MODULE
+RUN mvn package -B -q
+WORKDIR /quarkus
+
+FROM registry.access.redhat.com/ubi8/openjdk-17-runtime:1.15
+ENV LANGUAGE='en_US:en'
+WORKDIR /quarkus
+ENV MODULE=reactive-routes-pgclient
+
+COPY --chown=185 --from=maven /quarkus/$MODULE/target/quarkus-app/lib/ lib
+COPY --chown=185 --from=maven /quarkus/$MODULE/target/quarkus-app/app/ app
+COPY --chown=185 --from=maven /quarkus/$MODULE/target/quarkus-app/quarkus/ quarkus
+COPY --chown=185 --from=maven /quarkus/$MODULE/target/quarkus-app/quarkus-run.jar quarkus-run.jar
+COPY --chown=185 run_quarkus.sh run_quarkus.sh
+
+EXPOSE 8080
+USER 185
+ENTRYPOINT "./run_quarkus.sh"

+ 45 - 0
frameworks/Java/quarkus/quarkus-vertx.dockerfile

@@ -0,0 +1,45 @@
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.15 as maven
+ENV LANGUAGE='en_US:en'
+
+WORKDIR /quarkus
+ENV MODULE=vertx
+
+COPY --chown=185 pom.xml pom.xml
+COPY --chown=185 quarkus-benchmark-common quarkus-benchmark-common/
+COPY --chown=185 resteasy-reactive-hibernate resteasy-reactive-hibernate/
+COPY --chown=185 resteasy-reactive-hibernate-reactive resteasy-reactive-hibernate-reactive/
+COPY --chown=185 vertx vertx/
+COPY --chown=185 reactive-routes-pgclient reactive-routes-pgclient/
+
+# Uncomment to test pre-release quarkus
+#RUN mkdir -p /root/.m2/repository/io
+#COPY m2-quarkus /root/.m2/repository/io/quarkus
+
+USER 185
+WORKDIR /quarkus
+RUN mvn -DskipTests install -pl :benchmark,:quarkus-benchmark-common -B -q
+
+WORKDIR /quarkus/$MODULE
+RUN mvn dependency:go-offline -B -q
+WORKDIR /quarkus
+
+COPY $MODULE/src $MODULE/src
+
+WORKDIR /quarkus/$MODULE
+RUN mvn package -B -q
+WORKDIR /quarkus
+
+FROM registry.access.redhat.com/ubi8/openjdk-17-runtime:1.15
+ENV LANGUAGE='en_US:en'
+WORKDIR /quarkus
+ENV MODULE=vertx
+
+COPY --chown=185 --from=maven /quarkus/$MODULE/target/quarkus-app/lib/ lib
+COPY --chown=185 --from=maven /quarkus/$MODULE/target/quarkus-app/app/ app
+COPY --chown=185 --from=maven /quarkus/$MODULE/target/quarkus-app/quarkus/ quarkus
+COPY --chown=185 --from=maven /quarkus/$MODULE/target/quarkus-app/quarkus-run.jar quarkus-run.jar
+COPY --chown=185 run_quarkus.sh run_quarkus.sh
+
+EXPOSE 8080
+USER 185
+ENTRYPOINT "./run_quarkus.sh"

+ 2 - 0
frameworks/Java/quarkus/quarkus.dockerfile

@@ -8,6 +8,8 @@ COPY --chown=185 pom.xml pom.xml
 COPY --chown=185 quarkus-benchmark-common quarkus-benchmark-common/
 COPY --chown=185 resteasy-reactive-hibernate resteasy-reactive-hibernate/
 COPY --chown=185 resteasy-reactive-hibernate-reactive resteasy-reactive-hibernate-reactive/
+COPY --chown=185 vertx vertx/
+COPY --chown=185 reactive-routes-pgclient reactive-routes-pgclient/
 
 # Uncomment to test pre-release quarkus
 #RUN mkdir -p /root/.m2/repository/io

+ 86 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/pom.xml

@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>io.quarkus</groupId>
+        <artifactId>benchmark</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>reactive-routes-pgclient</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-benchmark-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-reactive-pg-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-scheduler</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-reactive-routes</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.vertx</groupId>
+            <artifactId>vertx-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.vertx</groupId>
+            <artifactId>vertx-web-templ-rocker</artifactId>
+        </dependency>
+        <!-- this is necessary to enable rocker to use GuavaHtmlStringify.java -->
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>32.0.0-jre</version>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-transport-native-epoll</artifactId>
+            <classifier>linux-x86_64</classifier>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>com.fizzed</groupId>
+                <artifactId>rocker-maven-plugin</artifactId>
+                <version>1.3.0</version>
+                <executions>
+                    <execution>
+                        <id>generate-rocker-templates</id>
+                        <phase>generate-sources</phase>
+                        <goals>
+                            <goal>generate</goal>
+                        </goals>
+                        <configuration>
+                            <templateDirectory>${project.basedir}/src/main/resources</templateDirectory>
+                            <optimize>true</optimize>
+                            <discardLogicWhitespace>true</discardLogicWhitespace>
+                            <markAsGenerated>false</markAsGenerated>
+                        </configuration>
+                    </execution>
+                </executions>
+                <dependencies>
+                    <!-- this is necessary to enable rocker to use GuavaHtmlStringify.java -->
+                    <dependency>
+                        <groupId>com.google.guava</groupId>
+                        <artifactId>guava</artifactId>
+                        <version>32.0.0-jre</version>
+                    </dependency>
+                </dependencies>
+            </plugin>
+        </plugins>
+
+    </build>
+
+</project>

+ 36 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/filter/ServerHeaderFilter.java

@@ -0,0 +1,36 @@
+package io.quarkus.benchmark.filter;
+
+import io.quarkus.scheduler.Scheduled;
+import io.quarkus.vertx.web.RouteFilter;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.ext.web.RoutingContext;
+import jakarta.annotation.PostConstruct;
+import jakarta.inject.Singleton;
+
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+@Singleton
+public class ServerHeaderFilter {
+
+    private static final CharSequence SERVER_HEADER_VALUE = HttpHeaders.createOptimized("Quarkus");
+    private CharSequence date;
+
+    @PostConstruct
+    public void init() {
+        updateDate();
+    }
+
+    @Scheduled(every = "1s")
+    void updateDate() {
+        date = HttpHeaders.createOptimized(DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now()));
+    }
+
+    @RouteFilter(100)
+    void addDefaultHeaders(final RoutingContext rc) {
+        final var headers = rc.response().headers();
+        headers.add(HttpHeaders.SERVER, SERVER_HEADER_VALUE);
+        headers.add(HttpHeaders.DATE, date);
+        rc.next();
+    }
+}

+ 25 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/model/Fortune.java

@@ -0,0 +1,25 @@
+package io.quarkus.benchmark.model;
+
+public class Fortune implements Comparable<Fortune> {
+
+    private final int id;
+    private final String message;
+
+    public Fortune(final int id, final String message) {
+        this.id = id;
+        this.message = message;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    @Override
+    public int compareTo(final Fortune other) {
+        return message.compareTo(other.message);
+    }
+}

+ 29 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/model/World.java

@@ -0,0 +1,29 @@
+package io.quarkus.benchmark.model;
+
+public class World implements Comparable<World> {
+
+    private final Integer id;
+    private Integer randomNumber;
+
+    public World(final Integer id, final Integer randomNumber) {
+        this.id = id;
+        this.randomNumber = randomNumber;
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public int getRandomNumber() {
+        return randomNumber;
+    }
+
+    public void setRandomNumber(final Integer randomNumber) {
+        this.randomNumber = randomNumber;
+    }
+
+    @Override
+    public int compareTo(final World o) {
+        return id.compareTo(o.id);
+    }
+}

+ 29 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/repository/FortuneRepository.java

@@ -0,0 +1,29 @@
+package io.quarkus.benchmark.repository;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.quarkus.benchmark.model.Fortune;
+import io.smallrye.mutiny.Uni;
+import io.vertx.mutiny.sqlclient.Row;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+
+@Singleton
+public class FortuneRepository {
+
+    @Inject
+    PgClients clients;
+
+    public Uni<List<Fortune>> findAll() {
+        return clients.getClient().preparedQuery("SELECT * FROM Fortune")
+                .execute()
+                .map(rowset -> {
+                    final List<Fortune> ret = new ArrayList<>(rowset.size() + 1);
+                    for (final Row r : rowset) {
+                        ret.add(new Fortune(r.getInteger(0), r.getString(1)));
+                    }
+                    return ret;
+                });
+    }
+}

+ 58 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/repository/PgClientFactory.java

@@ -0,0 +1,58 @@
+package io.quarkus.benchmark.repository;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import io.vertx.mutiny.sqlclient.SqlClient;
+import jakarta.enterprise.inject.Produces;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+import io.vertx.mutiny.core.Vertx;
+import io.vertx.mutiny.pgclient.PgPool;
+import io.vertx.pgclient.PgConnectOptions;
+import io.vertx.sqlclient.PoolOptions;
+
+@Singleton
+public class PgClientFactory {
+
+    // vertx-reactive:postgresql://tfb-database:5432/hello_world
+    private static final String PG_URI_MATCHER = "vertx-reactive:postgresql://([-a-zA-Z]+):([0-9]+)/(.*)";
+
+    @ConfigProperty(name = "quarkus.datasource.url")
+    String url;
+
+    @ConfigProperty(name = "quarkus.datasource.username")
+    String user;
+
+    @ConfigProperty(name = "quarkus.datasource.password")
+    String pass;
+
+    @Inject
+    Vertx vertx;
+
+    @Produces
+    @Singleton
+    public PgClients pgClients() {
+        return new PgClients(this);
+    }
+
+
+    SqlClient sqlClient(final int size) {
+        final PoolOptions options = new PoolOptions();
+        final PgConnectOptions connectOptions = new PgConnectOptions();
+        final Matcher matcher = Pattern.compile(PG_URI_MATCHER).matcher(url);
+        matcher.matches();
+        connectOptions.setDatabase(matcher.group(3));
+        connectOptions.setHost(matcher.group(1));
+        connectOptions.setPort(Integer.parseInt(matcher.group(2)));
+        connectOptions.setUser(user);
+        connectOptions.setPassword(pass);
+        connectOptions.setCachePreparedStatements(true);
+        // Large pipelining means less flushing and we use a single connection anyway
+        connectOptions.setPipeliningLimit(100_000);
+        options.setMaxSize(size);
+        return PgPool.client(vertx, connectOptions, options);
+    }
+}

+ 33 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/repository/PgClients.java

@@ -0,0 +1,33 @@
+package io.quarkus.benchmark.repository;
+
+import io.netty.util.concurrent.FastThreadLocal;
+import io.vertx.mutiny.sqlclient.SqlClient;
+
+class PgClients {
+    private final FastThreadLocal<SqlClient> sqlClient = new FastThreadLocal<>() {
+        @Override
+        protected void onRemoval(final SqlClient value) {
+            if (value != null) {
+                value.close();
+            }
+        }
+    };
+    private PgClientFactory pgClientFactory;
+
+    // for ArC
+    public PgClients() {
+    }
+
+    public PgClients(final PgClientFactory pgClientFactory) {
+        this.pgClientFactory = pgClientFactory;
+    }
+
+    SqlClient getClient() {
+        SqlClient ret = sqlClient.get();
+        if (ret == null) {
+            ret = pgClientFactory.sqlClient(1);
+            sqlClient.set(ret);
+        }
+        return ret;
+    }
+}

+ 59 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/repository/WorldRepository.java

@@ -0,0 +1,59 @@
+package io.quarkus.benchmark.repository;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import io.quarkus.benchmark.model.World;
+import io.smallrye.mutiny.Uni;
+import io.vertx.core.json.JsonObject;
+import io.vertx.mutiny.sqlclient.Row;
+import io.vertx.mutiny.sqlclient.Tuple;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+
+@Singleton
+public class WorldRepository {
+
+    //like muggle's JsonWorld
+    public static class JsonWorld extends JsonObject {
+        public JsonWorld(final Integer id, final Integer randomNumber) {
+            super(Map.of("id", id, "randomNumber", randomNumber));
+        }
+    }
+
+
+    @Inject
+    PgClients clients;
+
+
+    public Uni<JsonWorld> findAsJsonWorld(final Integer id) {
+        return clients.getClient().preparedQuery("SELECT id, randomNumber FROM World WHERE id = $1")
+                .execute(Tuple.of(id))
+                .map(rowset -> {
+                    final Row row = rowset.iterator().next();
+                    return new JsonWorld(row.getInteger(0), row.getInteger(1));
+                });
+    }
+
+    public Uni<World> find(final Integer id) {
+        return clients.getClient().preparedQuery("SELECT id, randomNumber FROM World WHERE id = $1")
+                .execute(Tuple.of(id))
+                .map(rowset -> {
+                    final Row row = rowset.iterator().next();
+                    return new World(row.getInteger(0), row.getInteger(1));
+                });
+    }
+
+    public Uni<Void> update(final World[] worlds) {
+        Arrays.sort(worlds);
+        final List<Tuple> args = new ArrayList<>(worlds.length);
+        for (final World world : worlds) {
+            args.add(Tuple.of(world.getId(), world.getRandomNumber()));
+        }
+        return clients.getClient().preparedQuery("UPDATE World SET randomNumber = $2 WHERE id = $1")
+                .executeBatch(args)
+                .map(v -> null);
+    }
+}

+ 32 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/resource/BaseResource.java

@@ -0,0 +1,32 @@
+package io.quarkus.benchmark.resource;
+
+import io.netty.handler.codec.http.HttpHeaderValues;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+import io.vertx.core.json.jackson.JacksonCodec;
+import io.vertx.ext.web.RoutingContext;
+
+public abstract class BaseResource {
+
+    // TODO verify how to override/replace io.quarkus.vertx.runtime.jackson.QuarkusJacksonFactory in io.vertx.core.spi.JsonFactory
+    private static final JacksonCodec JACKSON_CODEC = new JacksonCodec();
+
+    void sendJson(final RoutingContext rc, final JsonObject json) {
+        var response = rc.response();
+        response.headers().add(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
+        response.end(JACKSON_CODEC.toBuffer(json, false), null);
+    }
+
+    void sendJson(final RoutingContext rc, final JsonArray json) {
+        var response = rc.response();
+        response.headers().add(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
+        response.end(JACKSON_CODEC.toBuffer(json, false), null);
+    }
+
+    Void handleFail(final RoutingContext rc, final Throwable t) {
+        rc.response().setStatusCode(500).end(t.toString());
+        return null;
+    }
+
+}

+ 102 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/resource/DbResource.java

@@ -0,0 +1,102 @@
+package io.quarkus.benchmark.resource;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.IntStream;
+
+import io.quarkus.benchmark.model.World;
+import io.quarkus.benchmark.repository.WorldRepository;
+import io.quarkus.benchmark.repository.WorldRepository.JsonWorld;
+import io.quarkus.vertx.web.Route;
+import io.smallrye.mutiny.Uni;
+import io.vertx.core.json.JsonArray;
+import io.vertx.ext.web.RoutingContext;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+
+
+@Singleton
+public class DbResource extends BaseResource {
+
+    @Inject
+    WorldRepository worldRepository;
+
+    @Route(path = "db")
+    public void db(final RoutingContext rc) {
+        worldRepository.findAsJsonWorld(boxedRandomWorldNumber())
+                .subscribe().with(world -> sendJson(rc, world),
+                        t -> handleFail(rc, t));
+    }
+
+    @Route(path = "queries")
+    public void queries(final RoutingContext rc) {
+        final var queries = rc.request().getParam("queries");
+        final var worlds = new Uni[parseQueryCount(queries)];
+        final var ret = new JsonWorld[worlds.length];
+        // replace below with a for loop
+        Arrays.setAll(worlds, i -> {
+            return worldRepository.findAsJsonWorld(boxedRandomWorldNumber()).map(w -> ret[i] = w);
+        });
+
+        Uni.combine().all().unis(worlds)
+                .combinedWith(v -> Arrays.asList(ret))
+                .subscribe().with(list -> {
+                            sendJson(rc, new JsonArray(list));
+                        },
+                        t -> handleFail(rc, t));
+    }
+
+    @Route(path = "updates")
+    public void updates(final RoutingContext rc) {
+        final var queries = rc.request().getParam("queries");
+        final var worlds = new Uni[parseQueryCount(queries)];
+        final var wordsToUpdate = new World[worlds.length];
+        Arrays.setAll(worlds, i -> {
+            return randomWorld().map(w -> {
+                w.setRandomNumber(boxedRandomWorldNumber());
+                wordsToUpdate[i] = w;
+                return w;
+            });
+        });
+
+        Uni.combine().all().unis(worlds)
+                .combinedWith(v -> null)
+                .flatMap(v -> worldRepository.update(wordsToUpdate))
+                .map(updated -> wordsToUpdate)
+                .subscribe().with(updatedWordsOrderedById -> {
+                            final var jsonWorlds = new JsonArray(new ArrayList<>(updatedWordsOrderedById.length));
+                            for (final World world : updatedWordsOrderedById) {
+                                jsonWorlds.add(new JsonWorld(world.getId(), world.getRandomNumber()));
+                            }
+                            sendJson(rc, jsonWorlds);
+                        },
+                        t -> handleFail(rc, t));
+    }
+
+    private Uni<World> randomWorld() {
+        return worldRepository.find(boxedRandomWorldNumber());
+    }
+
+    private static final Integer[] BOXED_RND = IntStream.range(1, 10001).boxed().toArray(Integer[]::new);
+
+    private static Integer boxedRandomWorldNumber() {
+        final int rndValue = ThreadLocalRandom.current().nextInt(1, 10001);
+        final var boxedRnd = BOXED_RND[rndValue - 1];
+        assert boxedRnd.intValue() == rndValue;
+        return boxedRnd;
+    }
+
+    private static int parseQueryCount(final String textValue) {
+        if (textValue == null) {
+            return 1;
+        }
+        final int parsedValue;
+        try {
+            parsedValue = Integer.parseInt(textValue);
+        } catch (final NumberFormatException e) {
+            return 1;
+        }
+        return Math.min(500, Math.max(1, parsedValue));
+    }
+}

+ 43 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/resource/FortuneResource.java

@@ -0,0 +1,43 @@
+package io.quarkus.benchmark.resource;
+
+import java.util.Collections;
+
+import io.quarkus.benchmark.model.Fortune;
+import io.quarkus.benchmark.rocker.VertxRawRockerOutputFactories;
+import io.quarkus.benchmark.repository.FortuneRepository;
+import io.quarkus.vertx.web.Route;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.ext.web.RoutingContext;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import views.Fortunes;
+
+@Singleton
+public class FortuneResource extends BaseResource {
+
+    private static final CharSequence HTML_UTF8_CONTENT_TYPE = HttpHeaders.createOptimized("text/html; charset=UTF-8");
+
+    @Inject
+    FortuneRepository repository;
+    @Inject
+    VertxRawRockerOutputFactories factories;
+
+    public FortuneResource() {
+
+    }
+
+    @Route(path = "fortunes")
+    public void fortunes(final RoutingContext rc) {
+        repository.findAll()
+                .subscribe()
+                .with(fortunes -> {
+                            fortunes.add(new Fortune(0, "Additional fortune added at request time."));
+                            Collections.sort(fortunes);
+                            final var vertxRockerOutput = Fortunes.template(fortunes).render(factories.ioFactory());
+                            var res = rc.response();
+                            res.headers().add(HttpHeaders.CONTENT_TYPE, HTML_UTF8_CONTENT_TYPE);
+                            res.end(vertxRockerOutput.buffer(), null);
+                        },
+                        t -> handleFail(rc, t));
+    }
+}

+ 12 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/resource/JsonMessage.java

@@ -0,0 +1,12 @@
+package io.quarkus.benchmark.resource;
+
+import io.vertx.core.json.JsonObject;
+
+import java.util.Map;
+
+public class JsonMessage extends JsonObject {
+
+    public JsonMessage(final String message) {
+        super(Map.of("message", message));
+    }
+}

+ 17 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/resource/JsonResource.java

@@ -0,0 +1,17 @@
+package io.quarkus.benchmark.resource;
+
+
+import io.quarkus.vertx.web.Route;
+import io.vertx.ext.web.RoutingContext;
+import jakarta.inject.Singleton;
+
+@Singleton
+public class JsonResource extends BaseResource {
+
+    private static final String HELLO = "Hello, World!";
+
+    @Route(path = "json")
+    public void json(final RoutingContext rc) {
+        sendJson(rc, new JsonMessage(HELLO));
+    }
+}

+ 24 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/resource/PlaintextResource.java

@@ -0,0 +1,24 @@
+package io.quarkus.benchmark.resource;
+
+import io.netty.handler.codec.http.HttpHeaderValues;
+import io.quarkus.vertx.web.Route;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.ext.web.RoutingContext;
+import jakarta.inject.Singleton;
+
+@Singleton
+public class PlaintextResource {
+    private static final String HELLO_WORLD = "Hello, world!";
+    private static final Buffer HELLO_WORLD_BUFFER = Buffer.buffer(HELLO_WORLD, "UTF-8");
+    private static final CharSequence HELLO_WORLD_LENGTH = HttpHeaders.createOptimized("" + HELLO_WORLD.length());
+
+    @Route(path = "plaintext")
+    public void plaintext(final RoutingContext rc) {
+        final var response = rc.response();
+        final var headers = response.headers();
+        headers.add(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN);
+        headers.add(HttpHeaders.CONTENT_LENGTH, HELLO_WORLD_LENGTH);
+        response.end(HELLO_WORLD_BUFFER, null);
+    }
+}

+ 65 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/rocker/RawRockerOutput.java

@@ -0,0 +1,65 @@
+package io.quarkus.benchmark.rocker;
+
+import com.fizzed.rocker.ContentType;
+import com.fizzed.rocker.RockerOutputFactory;
+import io.netty.buffer.ByteBuf;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.buffer.impl.PartialPooledByteBufAllocator;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+final class RawRockerOutput implements VertxRawRockerOutput {
+
+    private final ByteBuf buff = PartialPooledByteBufAllocator.INSTANCE.directBuffer();
+    private final Buffer buffer = Buffer.buffer(buff);
+
+    RawRockerOutput() {
+    }
+
+    public static RockerOutputFactory<VertxRawRockerOutput> raw() {
+        final RawRockerOutput output = new RawRockerOutput();
+        return (_contentType, charsetName) -> {
+            output.reset();
+            return output;
+        };
+    }
+
+    private void reset() {
+        buff.resetReaderIndex();
+        buff.resetWriterIndex();
+    }
+
+    @Override
+    public RawRockerOutput w(final byte[] bytes) throws IOException {
+        buffer.appendBytes(bytes);
+        return this;
+    }
+
+    @Override
+    public RawRockerOutput w(final String s) throws IOException {
+        buffer.appendString(s);
+        return this;
+    }
+
+    @Override
+    public ContentType getContentType() {
+        return ContentType.RAW;
+    }
+
+    @Override
+    public Charset getCharset() {
+        return StandardCharsets.UTF_8;
+    }
+
+    @Override
+    public int getByteLength() {
+        return buffer.length();
+    }
+
+    @Override
+    public Buffer buffer() {
+        return buffer;
+    }
+}

+ 15 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/rocker/VertxRawRockerOutput.java

@@ -0,0 +1,15 @@
+package io.quarkus.benchmark.rocker;
+
+import com.fizzed.rocker.RockerOutputFactory;
+import io.vertx.core.buffer.Buffer;
+
+public interface VertxRawRockerOutput extends com.fizzed.rocker.RockerOutput<VertxRawRockerOutput> {
+
+    // factory
+    static RockerOutputFactory<VertxRawRockerOutput> factory() {
+        return RawRockerOutput.raw();
+    }
+
+    Buffer buffer();
+
+}

+ 29 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/src/main/java/io/quarkus/benchmark/rocker/VertxRawRockerOutputFactories.java

@@ -0,0 +1,29 @@
+package io.quarkus.benchmark.rocker;
+
+import com.fizzed.rocker.RockerOutputFactory;
+import io.netty.util.concurrent.FastThreadLocal;
+import io.vertx.core.Context;
+import jakarta.inject.Singleton;
+
+@Singleton
+public class VertxRawRockerOutputFactories {
+
+    private final FastThreadLocal<RockerOutputFactory<VertxRawRockerOutput>> ioPool;
+
+    VertxRawRockerOutputFactories() {
+        ioPool = new FastThreadLocal<>() {
+            @Override
+            protected RockerOutputFactory<VertxRawRockerOutput> initialValue() {
+                if (!Context.isOnEventLoopThread()) {
+                    return null;
+                }
+                return VertxRawRockerOutput.factory();
+            }
+        };
+    }
+
+    public RockerOutputFactory<VertxRawRockerOutput> ioFactory() {
+        return ioPool.get();
+    }
+
+}

+ 11 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/src/main/resources/application.properties

@@ -0,0 +1,11 @@
+quarkus.datasource.url=vertx-reactive:postgresql://tfb-database:5432/hello_world
+quarkus.datasource.username=benchmarkdbuser
+quarkus.datasource.password=benchmarkdbpass
+quarkus.datasource.reactive.max-size=64
+quarkus.log.console.enable=true
+quarkus.log.console.level=INFO
+quarkus.log.file.enable=false
+quarkus.log.level=INFO
+quarkus.vertx.prefer-native-transport=true
+
+quarkus.arc.context-propagation.enabled=false

+ 1 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/src/main/resources/import.sql

@@ -0,0 +1 @@
+INSERT INTO Fortune(id, message) VALUES (1, 'Test value One');

+ 8 - 0
frameworks/Java/quarkus/reactive-routes-pgclient/src/main/resources/views/Fortunes.rocker.html

@@ -0,0 +1,8 @@
+@import java.util.*
+@import io.quarkus.benchmark.model.*
+@args(List fortunes)
+<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>
+@for ((ForIterator i, Fortune fortune) : fortunes) {
+<tr><td>@fortune.getId()</td><td>@fortune.getMessage()</td></tr>
+}
+</table></body></html>

+ 1 - 0
frameworks/Java/quarkus/run_quarkus.sh

@@ -10,6 +10,7 @@
 # Consider using -Dquarkus.http.io-threads=$((`grep --count ^processor /proc/cpuinfo`)) \
 
 JAVA_OPTIONS="-server \
+  -Dquarkus.http.limits.max-body-size= \
   -Dquarkus.vertx.prefer-native-transport=true  \
   -XX:-StackTraceInThrowable \
   -Dquarkus.http.accept-backlog=-1 \

+ 83 - 0
frameworks/Java/quarkus/vertx/pom.xml

@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project>
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>io.quarkus</groupId>
+        <artifactId>benchmark</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <groupId>io.quarkus.benchmark</groupId>
+    <artifactId>vertx</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-benchmark-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-arc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.vertx</groupId>
+            <artifactId>vertx-pg-client</artifactId>
+            <version>${vertx.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-vertx</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.vertx</groupId>
+            <artifactId>vertx-web-templ-rocker</artifactId>
+        </dependency>
+        <!-- this is necessary to enable rocker to use GuavaHtmlStringify.java -->
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>32.0.0-jre</version>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-transport-native-epoll</artifactId>
+            <classifier>linux-x86_64</classifier>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>com.fizzed</groupId>
+                <artifactId>rocker-maven-plugin</artifactId>
+                <version>1.3.0</version>
+                <executions>
+                    <execution>
+                        <id>generate-rocker-templates</id>
+                        <phase>generate-sources</phase>
+                        <goals>
+                            <goal>generate</goal>
+                        </goals>
+                        <configuration>
+                            <templateDirectory>${project.basedir}/src/main/resources</templateDirectory>
+                            <optimize>true</optimize>
+                            <discardLogicWhitespace>true</discardLogicWhitespace>
+                            <markAsGenerated>false</markAsGenerated>
+                        </configuration>
+                    </execution>
+                </executions>
+                <dependencies>
+                    <!-- this is necessary to enable rocker to use GuavaHtmlStringify.java -->
+                    <dependency>
+                        <groupId>com.google.guava</groupId>
+                        <artifactId>guava</artifactId>
+                        <version>32.0.0-jre</version>
+                    </dependency>
+                </dependencies>
+            </plugin>
+        </plugins>
+
+    </build>
+
+</project>

+ 40 - 0
frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/filter/HttpResponseDecorator.java

@@ -0,0 +1,40 @@
+package io.quarkus.benchmark.filter;
+
+import io.vertx.core.Vertx;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.core.http.HttpServerResponse;
+import jakarta.annotation.PreDestroy;
+import jakarta.inject.Singleton;
+
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+@Singleton
+public class HttpResponseDecorator {
+    private static final CharSequence HEADER_DATE = HttpHeaders.createOptimized("date");
+    private static final CharSequence QUARKUS_SERVER = HttpHeaders.createOptimized("quarkus");
+    private final Vertx vertx;
+    private CharSequence date;
+    private final long timerId;
+
+    public HttpResponseDecorator(final Vertx vertx) {
+        this.vertx = vertx;
+        date = createDateHeader();
+        timerId = vertx.setPeriodic(1000, ignore -> date = createDateHeader());
+    }
+
+    public static CharSequence createDateHeader() {
+        return HttpHeaders.createOptimized(DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now()));
+    }
+
+    @PreDestroy
+    public void destroy() {
+        vertx.cancelTimer(timerId);
+    }
+
+    public void decorate(final HttpServerResponse response) {
+        final var headers = response.headers();
+        headers.add(HttpHeaders.SERVER, QUARKUS_SERVER);
+        headers.add(HEADER_DATE, date);
+    }
+}

+ 24 - 0
frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/model/Fortune.java

@@ -0,0 +1,24 @@
+package io.quarkus.benchmark.model;
+
+public class Fortune implements Comparable<Fortune> {
+    private final int id;
+    private final String message;
+
+    public Fortune(final int id, final String message) {
+        this.id = id;
+        this.message = message;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    @Override
+    public int compareTo(final Fortune other) {
+        return message.compareTo(other.message);
+    }
+}

+ 12 - 0
frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/model/JsonMessage.java

@@ -0,0 +1,12 @@
+package io.quarkus.benchmark.model;
+
+import io.vertx.core.json.JsonObject;
+
+import java.util.Map;
+
+public class JsonMessage extends JsonObject {
+
+    public JsonMessage(final String message) {
+        super(Map.of("message", message));
+    }
+}

+ 24 - 0
frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/model/World.java

@@ -0,0 +1,24 @@
+package io.quarkus.benchmark.model;
+
+public final class World implements Comparable<World> {
+    private final Integer id;
+    private final Integer randomNumber;
+
+    public World(final Integer id, final Integer randomNumber) {
+        this.id = id;
+        this.randomNumber = randomNumber;
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public Integer getRandomNumber() {
+        return randomNumber;
+    }
+
+    @Override
+    public int compareTo(final World o) {
+        return id.compareTo(o.id);
+    }
+}

+ 44 - 0
frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/repository/FortuneRepository.java

@@ -0,0 +1,44 @@
+package io.quarkus.benchmark.repository;
+
+import io.quarkus.benchmark.model.Fortune;
+import io.vertx.core.AsyncResult;
+import io.vertx.core.Future;
+import io.vertx.core.Handler;
+import io.vertx.sqlclient.Row;
+import io.vertx.sqlclient.RowIterator;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@Singleton
+public class FortuneRepository {
+
+    @Inject
+    PgConnectionPool pgConnectionPool;
+
+    public void findAllSortedFortunes(final Handler<AsyncResult<List<Fortune>>> resultHandler) {
+        pgConnectionPool.pgConnection().selectFortuneQuery()
+                .execute(fortuneRows -> {
+                    if (fortuneRows.succeeded()) {
+                        final List<Fortune> fortunes = new ArrayList<>(fortuneRows.result().size() + 1);
+                        final RowIterator<Row> resultSet = fortuneRows.result().iterator();
+                        if (!resultSet.hasNext()) {
+                            resultHandler.handle(Future.succeededFuture(List.of()));
+                            return;
+                        }
+                        while (resultSet.hasNext()) {
+                            final Row row = resultSet.next();
+                            fortunes.add(new Fortune(row.getInteger(0), row.getString(1)));
+                        }
+                        fortunes.add(new Fortune(0, "Additional fortune added at request time."));
+                        Collections.sort(fortunes);
+                        resultHandler.handle(Future.succeededFuture(fortunes));
+                    } else {
+                        resultHandler.handle(Future.failedFuture(fortuneRows.cause()));
+                    }
+                });
+    }
+}

+ 67 - 0
frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/repository/PgClientFactory.java

@@ -0,0 +1,67 @@
+package io.quarkus.benchmark.repository;
+
+import io.vertx.core.Vertx;
+import io.vertx.pgclient.PgConnectOptions;
+import jakarta.annotation.PreDestroy;
+import jakarta.enterprise.inject.Produces;
+import jakarta.inject.Singleton;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Singleton
+public class PgClientFactory {
+
+    // vertx-reactive:postgresql://tfb-database:5432/hello_world
+    private static final String PG_URI_MATCHER = "vertx-reactive:postgresql://([-a-zA-Z]+):([0-9]+)/(.*)";
+    @ConfigProperty(name = "quarkus.datasource.url")
+    String url;
+    @ConfigProperty(name = "quarkus.datasource.username")
+    String user;
+    @ConfigProperty(name = "quarkus.datasource.password")
+    String pass;
+
+    private final Vertx vertx;
+    private PgConnectionPool pgConnectionPool;
+
+    public PgClientFactory(final Vertx vertx) {
+        this.vertx = vertx;
+    }
+
+    @Produces
+    @Singleton
+    PgConnectionPool connectionPool() {
+        PgConnectionPool pgConnectionPool = null;
+        try {
+            pgConnectionPool = new PgConnectionPool(vertx, pgConnectOptions());
+        } catch (final Exception e) {
+            // TODO LOG ME: usually means inability to connect to the database
+        } finally {
+            this.pgConnectionPool = pgConnectionPool;
+            return pgConnectionPool;
+        }
+    }
+
+    @PreDestroy
+    public void closeConnectionPool() {
+        if (pgConnectionPool != null) {
+            pgConnectionPool.close();
+        }
+    }
+
+    private PgConnectOptions pgConnectOptions() {
+        final PgConnectOptions options = new PgConnectOptions();
+        final Matcher matcher = Pattern.compile(PG_URI_MATCHER).matcher(url);
+        matcher.matches();
+        options.setDatabase(matcher.group(3));
+        options.setHost(matcher.group(1));
+        options.setPort(Integer.parseInt(matcher.group(2)));
+        options.setUser(user);
+        options.setPassword(pass);
+        options.setCachePreparedStatements(true);
+        // Large pipelining means less flushing and we use a single connection anyway
+        options.setPipeliningLimit(100_000);
+        return options;
+    }
+}

+ 208 - 0
frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/repository/PgConnectionPool.java

@@ -0,0 +1,208 @@
+package io.quarkus.benchmark.repository;
+
+import io.netty.util.concurrent.EventExecutor;
+import io.netty.util.concurrent.FastThreadLocal;
+import io.vertx.core.AsyncResult;
+import io.vertx.core.Future;
+import io.vertx.core.Handler;
+import io.vertx.core.Promise;
+import io.vertx.core.Vertx;
+import io.vertx.pgclient.PgConnectOptions;
+import io.vertx.pgclient.PgConnection;
+import io.vertx.sqlclient.PreparedQuery;
+import io.vertx.sqlclient.PreparedStatement;
+import io.vertx.sqlclient.Row;
+import io.vertx.sqlclient.RowSet;
+import io.vertx.sqlclient.impl.SqlClientInternal;
+
+import java.util.ArrayList;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+
+public class PgConnectionPool implements AutoCloseable {
+
+    static final String SELECT_WORLD = "SELECT id, randomnumber from WORLD where id=$1";
+    static final String SELECT_FORTUNE = "SELECT id, message from FORTUNE";
+    private final FastThreadLocal<PgClientConnection> pgConnectionPool;
+    private final AtomicReferenceArray<PgClientConnection> pgConnections;
+
+    public PgConnectionPool(final Vertx vertx, final PgConnectOptions options) {
+        final var executors = new ArrayList<EventExecutor>(Runtime.getRuntime().availableProcessors());
+        vertx.nettyEventLoopGroup().forEach(executors::add);
+        final var connectionsCompleted = new CompletableFuture<>();
+        final var completedConnections = new AtomicReferenceArray<AsyncResult<PgClientConnection>>(executors.size());
+        final var allCompleted = new AtomicInteger(executors.size());
+        final var connectionAffinityMap = new ConcurrentHashMap<Thread, PgClientConnection>(executors.size());
+        for (int i = 0; i < executors.size(); i++) {
+            final int executorId = i;
+            executors.get(i).execute(() -> connect(vertx, options)
+                    .onComplete(ar -> {
+                        final boolean lastCompleted = allCompleted.decrementAndGet() == 0;
+                        if (!completedConnections.compareAndSet(executorId, null, ar)) {
+                            if (ar.succeeded()) {
+                                ar.result().connection.close();
+                            }
+                        } else if (ar.succeeded()) {
+                            // assign the executorId to the connection
+                            ar.result().executorId = executorId;
+                            connectionAffinityMap.put(Thread.currentThread(), ar.result());
+                        }
+                        if (lastCompleted) {
+                            connectionsCompleted.complete(null);
+                        }
+                    }));
+        }
+        // TODO make the global timeout to be configurable
+        try {
+            connectionsCompleted.join();
+        } catch (final Throwable t) {
+            // let's forcibly close all completed connections
+            forceCloseEstablishedConnections(completedConnections);
+            throw new IllegalStateException("cannot establish all connections", t);
+        }
+        // let's fast-fail if we cannot establish all connections
+        pgConnections = new AtomicReferenceArray<>(completedConnections.length());
+        for (int i = 0; i < completedConnections.length(); i++) {
+            final AsyncResult<PgClientConnection> ar = completedConnections.get(i);
+            if (ar == null || ar.failed()) {
+                forceCloseEstablishedConnections(completedConnections);
+                throw new IllegalStateException("cannot establish all connections");
+            } else {
+                pgConnections.set(i, ar.result());
+            }
+        }
+        pgConnectionPool = new FastThreadLocal<>() {
+            @Override
+            protected PgClientConnection initialValue() {
+                return connectionAffinityMap.get(Thread.currentThread());
+            }
+
+            @Override
+            protected void onRemoval(final PgClientConnection value) {
+                final PgClientConnection removed = connectionAffinityMap.remove(Thread.currentThread());
+                if (removed != null) {
+                    final var connectionToClose = pgConnections.getAndSet(removed.executorId, null);
+                    if (connectionToClose != null) {
+                        assert connectionToClose == removed;
+                        connectionToClose.connection.close();
+                    }
+                }
+            }
+        };
+    }
+
+    private static <T> Handler<AsyncResult<T>> onSuccess(final Handler<T> handler) {
+        return ar -> {
+            if (ar.succeeded()) {
+                handler.handle(ar.result());
+            }
+        };
+    }
+
+    private static Future<PgClientConnection> connect(final Vertx vertx, final PgConnectOptions options) {
+        final PgClientConnection result = new PgClientConnection();
+        final Promise<PgClientConnection> connectionEstablished = Promise.promise();
+        final var future = PgConnection.connect(vertx, options)
+                .flatMap(conn -> {
+                    result.connection = (SqlClientInternal) conn;
+                    final Future<PreparedStatement> f1 = conn.prepare(SELECT_WORLD)
+                            .andThen(onSuccess(ps -> result.SELECT_WORLD_QUERY = ps.query()));
+                    final Future<PreparedStatement> f2 = conn.prepare(SELECT_FORTUNE)
+                            .andThen(onSuccess(ps -> result.SELECT_FORTUNE_QUERY = ps.query()));
+
+                    final int QUERIES = 500;
+                    result.UPDATE_WORLD_QUERY = new PreparedQuery[QUERIES];
+                    var updateWorldQueries = new ArrayList<Future<PreparedStatement>>(QUERIES);
+                    for (int queryCount = 1; queryCount <= QUERIES; queryCount++) {
+                        updateWorldQueries.add(result.prepareUpdateQueryFor(conn, queryCount));
+                    }
+                    return Future.join(f1, f2, Future.join(updateWorldQueries));
+                }).onComplete(ar -> {
+                    if (ar.failed() && result.connection != null) {
+                        result.connection.close();
+                    }
+                });
+        future.onComplete(ar -> {
+            if (ar.succeeded()) {
+                connectionEstablished.complete(result);
+            } else {
+                connectionEstablished.fail(ar.cause());
+            }
+        });
+        return connectionEstablished.future();
+    }
+
+    private static void forceCloseEstablishedConnections(final AtomicReferenceArray<AsyncResult<PgClientConnection>> completedConnections) {
+        final AsyncResult<PgClientConnection> noResult = Future.succeededFuture();
+        for (int i = 0; i < completedConnections.length(); i++) {
+            final AsyncResult<PgClientConnection> ar = completedConnections.getAndSet(i, noResult);
+            if (ar != null && ar.succeeded() && ar.result() != null && ar.result().connection != null) {
+                try {
+                    ar.result().connection.close();
+                } catch (final Throwable t2) {
+                    // ignore
+                }
+            }
+        }
+    }
+
+    public PgClientConnection pgConnection() {
+        return pgConnectionPool.get();
+    }
+
+    @Override
+    public void close() {
+        for (int i = 0; i < pgConnections.length(); i++) {
+            final var connection = pgConnections.getAndSet(i, null);
+            if (connection != null) {
+                connection.connection.close();
+            }
+        }
+    }
+
+    public static class PgClientConnection {
+        private int executorId;
+        private PreparedQuery<RowSet<Row>> SELECT_WORLD_QUERY;
+        private PreparedQuery<RowSet<Row>> SELECT_FORTUNE_QUERY;
+        private PreparedQuery<RowSet<Row>>[] UPDATE_WORLD_QUERY;
+        private SqlClientInternal connection;
+
+        public PreparedQuery<RowSet<Row>> selectWorldQuery() {
+            return SELECT_WORLD_QUERY;
+        }
+
+        public PreparedQuery<RowSet<Row>> selectFortuneQuery() {
+            return SELECT_FORTUNE_QUERY;
+        }
+
+        public PreparedQuery<RowSet<Row>> updateWorldQuery(int queryCount) {
+            return UPDATE_WORLD_QUERY[queryCount - 1];
+        }
+
+        private Future<PreparedStatement> prepareUpdateQueryFor(PgConnection connection, int queryCount) {
+            return connection.prepare(updateQueryFor(queryCount)).andThen(onSuccess(ps -> UPDATE_WORLD_QUERY[queryCount - 1] = ps.query()));
+        }
+        private static String updateQueryFor(int queryCount) {
+            StringBuilder sql = new StringBuilder();
+            sql.append("UPDATE WORLD SET RANDOMNUMBER = CASE ID");
+            for (int i = 0; i < queryCount; i++) {
+                int offset = (i * 2) + 1;
+                sql.append(" WHEN $" + offset + " THEN $" + (offset + 1));
+            }
+            sql.append(" ELSE RANDOMNUMBER");
+            sql.append(" END WHERE ID IN ($1");
+            for (int i = 1; i < queryCount; i++) {
+                int offset = (i * 2) + 1;
+                sql.append(",$" + offset);
+            }
+            sql.append(")");
+            return sql.toString();
+        }
+
+        public SqlClientInternal rawConnection() {
+            return connection;
+        }
+    }
+}

+ 187 - 0
frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/repository/WorldRepository.java

@@ -0,0 +1,187 @@
+package io.quarkus.benchmark.repository;
+
+import io.quarkus.benchmark.model.World;
+import io.vertx.core.AsyncResult;
+import io.vertx.core.Future;
+import io.vertx.core.Handler;
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
+import io.vertx.sqlclient.PreparedQuery;
+import io.vertx.sqlclient.Row;
+import io.vertx.sqlclient.RowIterator;
+import io.vertx.sqlclient.RowSet;
+import io.vertx.sqlclient.Tuple;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.IntStream;
+
+@Singleton
+public class WorldRepository {
+
+    @Inject
+    private PgConnectionPool connectionPool;
+
+    private static final Integer[] BOXED_RND = IntStream.range(1, 10001).boxed().toArray(Integer[]::new);
+
+    private static Integer boxedRandomWorldNumber() {
+        final int rndValue = ThreadLocalRandom.current().nextInt(1, 10001);
+        final var boxedRnd = BOXED_RND[rndValue - 1];
+        assert boxedRnd.intValue() == rndValue;
+        return boxedRnd;
+    }
+
+    public void loadRandomJsonWorld(final Handler<AsyncResult<JsonWorld>> worldHandler) {
+        connectionPool.pgConnection().selectWorldQuery().execute(Tuple.of(boxedRandomWorldNumber()), randomWorldRow -> {
+            if (randomWorldRow.succeeded()) {
+                final RowIterator<Row> resultSet = randomWorldRow.result().iterator();
+                if (!resultSet.hasNext()) {
+                    worldHandler.handle(Future.succeededFuture());
+                    return;
+                }
+                final Row row = resultSet.next();
+                worldHandler.handle(Future.succeededFuture(new JsonWorld(row.getInteger(0), row.getInteger(1))));
+            } else {
+                worldHandler.handle(Future.failedFuture(randomWorldRow.cause()));
+            }
+        });
+    }
+
+    public void loadNJsonWorlds(final int count, final Handler<AsyncResult<JsonWorlds>> worldsHandler) {
+        FindRandomWorldsCommand.execute(connectionPool, count, worldsHandler);
+    }
+
+    public void updateNJsonWorlds(final int count, final Handler<AsyncResult<JsonWorlds>> worldsHandler) {
+        UpdateWorldsCommand.execute(connectionPool, count, worldsHandler);
+    }
+
+    public static class JsonWorlds extends JsonArray {
+        private JsonWorlds(final int capacity) {
+            super(new ArrayList(capacity));
+        }
+
+        private JsonWorlds(final World[] worlds) {
+            this(worlds.length);
+            for (final World world : worlds) {
+                add(new JsonWorld(world.getId(), world.getRandomNumber()));
+            }
+        }
+    }
+
+    public static class JsonWorld extends JsonObject {
+        private JsonWorld(final Integer id, final Integer random) {
+            super(Map.of("id", id, "randomNumber", random));
+        }
+    }
+
+    private static final class UpdateWorldsCommand {
+        private final PgConnectionPool.PgClientConnection connection;
+        private final World[] worldsToUpdate;
+        private final Handler<AsyncResult<JsonWorlds>> resultHandler;
+        private boolean failed;
+        private int selectWorldCompletedCount;
+
+        private UpdateWorldsCommand(final PgConnectionPool.PgClientConnection connection, final int queries, final Handler<AsyncResult<JsonWorlds>> resultHandler) {
+            this.connection = connection;
+            this.worldsToUpdate = new World[queries];
+            this.resultHandler = resultHandler;
+        }
+
+        // execute
+        public static void execute(final PgConnectionPool connectionPool, final int count, final Handler<AsyncResult<JsonWorlds>> resultHandler) {
+            new UpdateWorldsCommand(connectionPool.pgConnection(), count, resultHandler).run();
+        }
+
+        private void run() {
+            connection.rawConnection().group(c -> {
+                final PreparedQuery<RowSet<Row>> preparedQuery = c.preparedQuery(PgConnectionPool.SELECT_WORLD);
+                for (int i = 0; i < worldsToUpdate.length; i++) {
+                    final Integer id = boxedRandomWorldNumber();
+                    final int index = i;
+                    preparedQuery.execute(Tuple.of(id), worldId -> {
+                        if (!failed) {
+                            if (worldId.failed()) {
+                                failed = true;
+                                resultHandler.handle(Future.failedFuture(worldId.cause()));
+                                return;
+                            }
+                            worldsToUpdate[index] = new World(worldId.result().iterator().next().getInteger(0), boxedRandomWorldNumber());
+                            if (++selectWorldCompletedCount == worldsToUpdate.length) {
+                                randomWorldsQueryCompleted();
+                            }
+                        }
+                    });
+                }
+            });
+        }
+
+        private void randomWorldsQueryCompleted() {
+            Arrays.sort(worldsToUpdate);
+            final List<Integer> params = new ArrayList<>(worldsToUpdate.length * 2);
+            for (int i = 0, count = worldsToUpdate.length; i < count; i++) {
+                var world = worldsToUpdate[i];
+                params.add(world.getId());
+                params.add(world.getRandomNumber());
+            }
+            connection.updateWorldQuery(worldsToUpdate.length).execute(Tuple.wrap(params), updateResult -> {
+                if (updateResult.failed()) {
+                    resultHandler.handle(Future.failedFuture(updateResult.cause()));
+                    return;
+                }
+                resultHandler.handle(Future.succeededFuture(new JsonWorlds(worldsToUpdate)));
+            });
+        }
+    }
+
+    private static final class FindRandomWorldsCommand implements Handler<AsyncResult<RowSet<Row>>> {
+        private final Handler<AsyncResult<JsonWorlds>> resultHandler;
+        private final int count;
+        private final JsonWorlds jsonWorlds;
+        private final PgConnectionPool.PgClientConnection connection;
+        private boolean failed;
+
+        private FindRandomWorldsCommand(final PgConnectionPool.PgClientConnection connection, final int count, final Handler<AsyncResult<JsonWorlds>> resultHandler) {
+            this.connection = connection;
+            this.count = count;
+            this.jsonWorlds = new JsonWorlds(count);
+            this.resultHandler = resultHandler;
+        }
+
+        public static void execute(final PgConnectionPool connectionPool, final int queries, final Handler<AsyncResult<JsonWorlds>> resultHandler) {
+            new FindRandomWorldsCommand(connectionPool.pgConnection(), queries, resultHandler).run();
+        }
+
+        private void run() {
+            connection.rawConnection().group(c -> {
+                for (int i = 0; i < count; i++) {
+                    c.preparedQuery(PgConnectionPool.SELECT_WORLD).execute(Tuple.of(boxedRandomWorldNumber()), this);
+                }
+            });
+        }
+
+        @Override
+        public void handle(final AsyncResult<RowSet<Row>> ar) {
+            if (!failed) {
+                if (ar.failed()) {
+                    failed = true;
+                    resultHandler.handle(Future.failedFuture(ar.cause()));
+                    return;
+                }
+
+                final Tuple row = ar.result().iterator().next();
+                jsonWorlds.add(new JsonWorld(row.getInteger(0), row.getInteger(1)));
+
+                // stop condition
+                if (jsonWorlds.size() == count) {
+                    resultHandler.handle(Future.succeededFuture(jsonWorlds));
+                }
+            }
+        }
+    }
+
+}

+ 30 - 0
frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/resource/DbHttpHandler.java

@@ -0,0 +1,30 @@
+package io.quarkus.benchmark.resource;
+
+import io.netty.handler.codec.http.HttpHeaderValues;
+import io.quarkus.benchmark.repository.WorldRepository;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.core.http.HttpServerRequest;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+
+@Singleton
+public class DbHttpHandler {
+
+    @Inject
+    WorldRepository worldRepository;
+
+    // write handle
+    public void handle(final HttpServerRequest request) {
+        worldRepository.loadRandomJsonWorld(jsonWorld -> {
+            if (jsonWorld.succeeded()) {
+                var res = request.response();
+                res.headers().add(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
+                res.end(jsonWorld.result().toBuffer(), null);
+            } else {
+                request.response().setStatusCode(500).end();
+            }
+        });
+    }
+
+
+}

+ 34 - 0
frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/resource/FortunesHttpHandler.java

@@ -0,0 +1,34 @@
+package io.quarkus.benchmark.resource;
+
+import io.quarkus.benchmark.repository.FortuneRepository;
+import io.quarkus.benchmark.rocker.VertxRawRockerOutputFactories;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.core.http.HttpServerRequest;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import views.Fortunes;
+
+@Singleton
+public class FortunesHttpHandler {
+
+    private static final CharSequence HTML_UTF8_CONTENT_TYPE = HttpHeaders.createOptimized("text/html; charset=UTF-8");
+
+    @Inject
+    VertxRawRockerOutputFactories factories;
+
+    @Inject
+    FortuneRepository fortuneRepository;
+
+    public void handle(final HttpServerRequest request) {
+        fortuneRepository.findAllSortedFortunes(fortunes -> {
+            if (fortunes.succeeded()) {
+                final var vertxRockerOutput = Fortunes.template(fortunes.result()).render(factories.ioFactory());
+                var res = request.response();
+                res.headers().add(HttpHeaders.CONTENT_TYPE, HTML_UTF8_CONTENT_TYPE);
+                res.end(vertxRockerOutput.buffer(), null);
+            } else {
+                request.response().setStatusCode(500).end(fortunes.cause().getMessage());
+            }
+        });
+    }
+}

+ 20 - 0
frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/resource/HttpQueryParameterUtils.java

@@ -0,0 +1,20 @@
+package io.quarkus.benchmark.resource;
+
+import io.vertx.core.http.HttpServerRequest;
+
+public class HttpQueryParameterUtils {
+
+    public static int queriesFrom(final HttpServerRequest request) {
+        final String param = request.getParam("queries");
+
+        if (param == null) {
+            return 1;
+        }
+        try {
+            final int parsedValue = Integer.parseInt(param);
+            return Math.min(500, Math.max(1, parsedValue));
+        } catch (final NumberFormatException e) {
+            return 1;
+        }
+    }
+}

+ 109 - 0
frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/resource/HttpRoutes.java

@@ -0,0 +1,109 @@
+package io.quarkus.benchmark.resource;
+
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.util.concurrent.EventExecutor;
+import io.quarkus.benchmark.filter.HttpResponseDecorator;
+import io.quarkus.runtime.StartupEvent;
+import io.vertx.core.Future;
+import io.vertx.core.Handler;
+import io.vertx.core.Promise;
+import io.vertx.core.Vertx;
+import io.vertx.core.http.HttpServer;
+import io.vertx.core.http.HttpServerOptions;
+import io.vertx.core.http.HttpServerRequest;
+import jakarta.annotation.PreDestroy;
+import jakarta.enterprise.event.Observes;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Singleton
+public class HttpRoutes {
+    private static final int PORT = 8080;
+    private static final String PATH_PLAINTEXT = "/plaintext";
+    private static final String PATH_JSON = "/json";
+    private static final String PATH_DB = "/db";
+    private static final String PATH_QUERIES = "/queries";
+    private static final String PATH_UPDATES = "/updates";
+    private static final String PATH_FORTUNES = "/fortunes";
+    private HttpServer[] servers;
+    @Inject
+    private HttpResponseDecorator responseDecorator;
+    @Inject
+    private PlaintextHttpHandler plaintextHttpHandler;
+    @Inject
+    private JsonHttpHandler jsonHandler;
+    @Inject
+    private DbHttpHandler dbHandler;
+    @Inject
+    private UpdateHttpHandler updateHandler;
+    @Inject
+    private QueriesHttpHandler queriesHandler;
+    @Inject
+    private FortunesHttpHandler fortunesHandler;
+
+    private static HttpServer[] createAndStartHttpServers(final Vertx vertx, final Handler<HttpServerRequest> requestHandler) {
+        final var executors = new ArrayList<EventExecutor>(Runtime.getRuntime().availableProcessors());
+        vertx.nettyEventLoopGroup().forEach(executors::add);
+        final HttpServer[] servers = new HttpServer[executors.size()];
+        final List<Future<?>> allHttpServerListening = new ArrayList<>(servers.length);
+        for (int i = 0; i < servers.length; i++) {
+            final int executorId = i;
+            final var done = Promise.promise();
+            allHttpServerListening.add(done.future());
+            executors.get(executorId).execute(() -> {
+                servers[executorId] = vertx.createHttpServer(new HttpServerOptions());
+                servers[executorId]
+                        .requestHandler(requestHandler)
+                        .listen(PORT);
+                done.complete();
+            });
+        }
+        Future.join(allHttpServerListening).toCompletionStage().toCompletableFuture().join();
+        return servers;
+    }
+
+    public void init(@Observes final StartupEvent startupEvent, final Vertx vertx) {
+        servers = createAndStartHttpServers(vertx, this::handle);
+    }
+
+    @PreDestroy
+    public void shutdown() {
+        for (final HttpServer server : servers) {
+            server.close();
+        }
+    }
+
+    private void handle(final HttpServerRequest request) {
+        try {
+            responseDecorator.decorate(request.response());
+            switch (request.path()) {
+                case PATH_PLAINTEXT:
+                    plaintextHttpHandler.handle(request);
+                    break;
+                case PATH_JSON:
+                    jsonHandler.handle(request);
+                    break;
+                case PATH_DB:
+                    dbHandler.handle(request);
+                    break;
+                case PATH_QUERIES:
+                    queriesHandler.handle(request);
+                    break;
+                case PATH_UPDATES:
+                    updateHandler.handle(request);
+                    break;
+                case PATH_FORTUNES:
+                    fortunesHandler.handle(request);
+                    break;
+                default:
+                    request.response().setStatusCode(HttpResponseStatus.NOT_FOUND.code()).end();
+                    break;
+            }
+        } catch (final Exception e) {
+            request.response().setStatusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.code()).end();
+        }
+    }
+}

+ 21 - 0
frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/resource/JsonHttpHandler.java

@@ -0,0 +1,21 @@
+package io.quarkus.benchmark.resource;
+
+import io.netty.handler.codec.http.HttpHeaderValues;
+import io.quarkus.benchmark.model.JsonMessage;
+import io.vertx.core.MultiMap;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.core.http.HttpServerRequest;
+import io.vertx.core.http.HttpServerResponse;
+import jakarta.inject.Singleton;
+
+@Singleton
+public class JsonHttpHandler {
+
+    public void handle(final HttpServerRequest request) {
+        final HttpServerResponse response = request.response();
+        final MultiMap headers = response.headers();
+        headers.add(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
+        response.end(new JsonMessage("Hello, World!").toBuffer(), null);
+    }
+
+}

+ 24 - 0
frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/resource/PlaintextHttpHandler.java

@@ -0,0 +1,24 @@
+package io.quarkus.benchmark.resource;
+
+import io.netty.handler.codec.http.HttpHeaderValues;
+import io.vertx.core.MultiMap;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.core.http.HttpServerRequest;
+import io.vertx.core.http.HttpServerResponse;
+import jakarta.inject.Singleton;
+
+@Singleton
+public class PlaintextHttpHandler {
+    private static final String HELLO_WORLD = "Hello, world!";
+    private static final Buffer HELLO_WORLD_BUFFER = Buffer.buffer(HELLO_WORLD, "UTF-8");
+    private static final CharSequence HELLO_WORLD_LENGTH = HttpHeaders.createOptimized("" + HELLO_WORLD.length());
+
+    public void handle(final HttpServerRequest request) {
+        final HttpServerResponse response = request.response();
+        final MultiMap headers = response.headers();
+        headers.add(HttpHeaders.CONTENT_LENGTH, HELLO_WORLD_LENGTH);
+        headers.add(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN);
+        response.end(HELLO_WORLD_BUFFER, null);
+    }
+}

+ 28 - 0
frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/resource/QueriesHttpHandler.java

@@ -0,0 +1,28 @@
+package io.quarkus.benchmark.resource;
+
+import io.netty.handler.codec.http.HttpHeaderValues;
+import io.quarkus.benchmark.repository.WorldRepository;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.core.http.HttpServerRequest;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+
+import static io.quarkus.benchmark.resource.HttpQueryParameterUtils.queriesFrom;
+
+@Singleton
+public class QueriesHttpHandler {
+    @Inject
+    WorldRepository worldRepository;
+
+    public void handle(final HttpServerRequest request) {
+        worldRepository.loadNJsonWorlds(queriesFrom(request), jsonWorlds -> {
+            if (jsonWorlds.succeeded()) {
+                var res = request.response();
+                res.headers().add(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
+                res.end(jsonWorlds.result().toBuffer(), null);
+            } else {
+                request.response().setStatusCode(500).end();
+            }
+        });
+    }
+}

+ 29 - 0
frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/resource/UpdateHttpHandler.java

@@ -0,0 +1,29 @@
+package io.quarkus.benchmark.resource;
+
+import io.netty.handler.codec.http.HttpHeaderValues;
+import io.quarkus.benchmark.repository.WorldRepository;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.core.http.HttpServerRequest;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+
+import static io.quarkus.benchmark.resource.HttpQueryParameterUtils.queriesFrom;
+
+@Singleton
+public class UpdateHttpHandler {
+
+    @Inject
+    WorldRepository worldRepository;
+
+    public void handle(final HttpServerRequest request) {
+        worldRepository.updateNJsonWorlds(queriesFrom(request), jsonWorlds -> {
+            if (jsonWorlds.succeeded()) {
+                var res = request.response();
+                res.headers().add(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
+                res.end(jsonWorlds.result().toBuffer(), null);
+            } else {
+                request.response().setStatusCode(500).end();
+            }
+        });
+    }
+}

+ 65 - 0
frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/rocker/RawRockerOutput.java

@@ -0,0 +1,65 @@
+package io.quarkus.benchmark.rocker;
+
+import com.fizzed.rocker.ContentType;
+import com.fizzed.rocker.RockerOutputFactory;
+import io.netty.buffer.ByteBuf;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.buffer.impl.PartialPooledByteBufAllocator;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+final class RawRockerOutput implements VertxRawRockerOutput {
+
+    private final ByteBuf buff = PartialPooledByteBufAllocator.INSTANCE.directBuffer();
+    private final Buffer buffer = Buffer.buffer(buff);
+
+    RawRockerOutput() {
+    }
+
+    public static RockerOutputFactory<VertxRawRockerOutput> raw() {
+        final RawRockerOutput output = new RawRockerOutput();
+        return (_contentType, charsetName) -> {
+            output.reset();
+            return output;
+        };
+    }
+
+    private void reset() {
+        buff.resetReaderIndex();
+        buff.resetWriterIndex();
+    }
+
+    @Override
+    public RawRockerOutput w(final byte[] bytes) throws IOException {
+        buffer.appendBytes(bytes);
+        return this;
+    }
+
+    @Override
+    public RawRockerOutput w(final String s) throws IOException {
+        buffer.appendString(s);
+        return this;
+    }
+
+    @Override
+    public ContentType getContentType() {
+        return ContentType.RAW;
+    }
+
+    @Override
+    public Charset getCharset() {
+        return StandardCharsets.UTF_8;
+    }
+
+    @Override
+    public int getByteLength() {
+        return buffer.length();
+    }
+
+    @Override
+    public Buffer buffer() {
+        return buffer;
+    }
+}

+ 15 - 0
frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/rocker/VertxRawRockerOutput.java

@@ -0,0 +1,15 @@
+package io.quarkus.benchmark.rocker;
+
+import com.fizzed.rocker.RockerOutputFactory;
+import io.vertx.core.buffer.Buffer;
+
+public interface VertxRawRockerOutput extends com.fizzed.rocker.RockerOutput<VertxRawRockerOutput> {
+
+    // factory
+    static RockerOutputFactory<VertxRawRockerOutput> factory() {
+        return RawRockerOutput.raw();
+    }
+
+    Buffer buffer();
+
+}

+ 29 - 0
frameworks/Java/quarkus/vertx/src/main/java/io/quarkus/benchmark/rocker/VertxRawRockerOutputFactories.java

@@ -0,0 +1,29 @@
+package io.quarkus.benchmark.rocker;
+
+import com.fizzed.rocker.RockerOutputFactory;
+import io.netty.util.concurrent.FastThreadLocal;
+import io.vertx.core.Context;
+import jakarta.inject.Singleton;
+
+@Singleton
+public class VertxRawRockerOutputFactories {
+
+    private final FastThreadLocal<RockerOutputFactory<VertxRawRockerOutput>> ioPool;
+
+    VertxRawRockerOutputFactories() {
+        ioPool = new FastThreadLocal<>() {
+            @Override
+            protected RockerOutputFactory<VertxRawRockerOutput> initialValue() {
+                if (!Context.isOnEventLoopThread()) {
+                    return null;
+                }
+                return VertxRawRockerOutput.factory();
+            }
+        };
+    }
+
+    public RockerOutputFactory<VertxRawRockerOutput> ioFactory() {
+        return ioPool.get();
+    }
+
+}

+ 10 - 0
frameworks/Java/quarkus/vertx/src/main/resources/application.properties

@@ -0,0 +1,10 @@
+quarkus.datasource.url=vertx-reactive:postgresql://tfb-database:5432/hello_world
+%dev.quarkus.datasource.url=vertx-reactive:postgresql://localhost:5432/hello_world
+quarkus.datasource.username=benchmarkdbuser
+quarkus.datasource.password=benchmarkdbpass
+
+quarkus.log.console.enable=true
+quarkus.log.console.level=INFO
+quarkus.log.file.enable=false
+quarkus.log.level=INFO
+

+ 1 - 0
frameworks/Java/quarkus/vertx/src/main/resources/import.sql

@@ -0,0 +1 @@
+INSERT INTO Fortune(id, message) VALUES (1, 'Test value One');

+ 8 - 0
frameworks/Java/quarkus/vertx/src/main/resources/views/Fortunes.rocker.html

@@ -0,0 +1,8 @@
+@import java.util.*
+@import io.quarkus.benchmark.model.*
+@args(List fortunes)
+<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>
+@for ((ForIterator i, Fortune fortune) : fortunes) {
+<tr><td>@fortune.getId()</td><td>@fortune.getMessage()</td></tr>
+}
+</table></body></html>