Browse Source

jooby: Added mvc tests (a.k.a Controller) + fix pgclient update test (#5037)

Edgar Espina 6 years ago
parent
commit
cbcbeb108c

+ 23 - 0
frameworks/Java/jooby2/benchmark_config.json

@@ -24,6 +24,29 @@
       "display_name": "jooby2-netty",
       "versus": "netty"
     },
+    "mvc": {
+      "json_url": "/json",
+      "plaintext_url": "/plaintext",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Fullstack",
+      "framework": "jooby 2.x",
+      "language": "Java",
+      "flavor": "None",
+      "platform": "Undertow",
+      "database": "Postgres",
+      "database_os": "Linux",
+      "orm": "Raw",
+      "webserver": "None",
+      "os": "Linux",
+      "notes": "Jooby 2 MVC using Undertow",
+      "display_name": "jooby2-mvc",
+      "versus": "undertow"
+    },
     "undertow": {
       "json_url": "/json",
       "plaintext_url": "/plaintext",

+ 12 - 0
frameworks/Java/jooby2/jooby2-mvc.dockerfile

@@ -0,0 +1,12 @@
+FROM maven:3.6.1-jdk-11-slim as maven
+WORKDIR /jooby2
+COPY pom.xml pom.xml
+COPY src src
+COPY public public
+RUN mvn package -q -P undertow
+
+FROM openjdk:11.0.3-jdk-slim
+WORKDIR /jooby2
+COPY --from=maven /jooby2/target/jooby-2x.jar app.jar
+COPY conf conf
+CMD ["java", "-server", "-Xms4g", "-Xmx4g", "-XX:+AggressiveOpts", "-XX:-UseBiasedLocking", "-XX:+UseStringDeduplication", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-cp", "app.jar", "com.techempower.MvcApp"]

+ 15 - 6
frameworks/Java/jooby2/pom.xml

@@ -7,7 +7,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>jooby-project</artifactId>
-    <version>2.0.6</version>
+    <version>2.1.0</version>
   </parent>
 
   <artifactId>jooby2</artifactId>
@@ -17,7 +17,7 @@
   <name>jooby 2.x</name>
 
   <properties>
-    <jooby.version>2.0.6</jooby.version>
+    <jooby.version>2.1.0</jooby.version>
     <!-- downgrade netty and make pg-client happy -->
     <netty.version>4.1.34.Final</netty.version>
     <postgresql.version>42.2.5</postgresql.version>
@@ -97,10 +97,19 @@
         </executions>
       </plugin>
       <plugin>
-         <groupId>org.apache.maven.plugins</groupId>
-         <artifactId>maven-compiler-plugin</artifactId>
-         <version>3.8.0</version>
-       </plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.8.1</version>
+        <configuration>
+          <annotationProcessorPaths>
+            <path>
+              <groupId>io.jooby</groupId>
+              <artifactId>jooby-apt</artifactId>
+              <version>${jooby.version}</version>
+            </path>
+          </annotationProcessorPaths>
+        </configuration>
+      </plugin>
       <!-- Build fat jar -->
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>

+ 4 - 9
frameworks/Java/jooby2/src/main/java/com/techempower/App.java

@@ -29,10 +29,6 @@ public class App extends Jooby {
 
   private static final byte[] MESSAGE_BYTES = MESSAGE.getBytes(StandardCharsets.UTF_8);
 
-  public static class Message {
-    public final String message = MESSAGE;
-  }
-
   {
     /** JSON: */
     install(new JacksonModule());
@@ -51,7 +47,7 @@ public class App extends Jooby {
 
     get("/json", ctx -> ctx
         .setResponseType(JSON)
-        .send(mapper.writeValueAsBytes(new Message()))
+        .send(mapper.writeValueAsBytes(new Message(MESSAGE)))
     );
 
     /** Go blocking: */
@@ -107,7 +103,7 @@ public class App extends Jooby {
               statement.setInt(1, randomWorld());
               try (ResultSet rs = statement.executeQuery()) {
                 rs.next();
-                result[i] = new World(rs.getInt("id"), rs.getInt("randomNumber"));
+                result[i] = new World(rs.getInt("id"), randomWorld());
               }
               // prepare update query
               updateSql.add("(?, ?)");
@@ -117,9 +113,8 @@ public class App extends Jooby {
           try (PreparedStatement statement = connection.prepareStatement(updateSql.toString())) {
             int i = 0;
             for (World world : result) {
-              world.randomNumber = randomWorld();
-              statement.setInt(++i, world.id);
-              statement.setInt(++i, world.randomNumber);
+              statement.setInt(++i, world.getId());
+              statement.setInt(++i, world.getRandomNumber());
             }
             statement.executeUpdate();
           }

+ 139 - 0
frameworks/Java/jooby2/src/main/java/com/techempower/Controller.java

@@ -0,0 +1,139 @@
+package com.techempower;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.jooby.Context;
+import io.jooby.annotations.Dispatch;
+import io.jooby.annotations.GET;
+import views.fortunes;
+
+import javax.sql.DataSource;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.StringJoiner;
+
+import static com.techempower.Util.randomWorld;
+import static io.jooby.MediaType.JSON;
+
+public class Controller {
+  private static final String SELECT_WORLD = "select * from world where id=?";
+
+  private static final String MESSAGE = "Hello, World!";
+
+  private static final byte[] MESSAGE_BYTES = MESSAGE.getBytes(StandardCharsets.UTF_8);
+
+  private final DataSource dataSource;
+
+  private final ObjectMapper mapper;
+
+  public Controller(DataSource dataSource, ObjectMapper mapper) {
+    this.dataSource = dataSource;
+    this.mapper = mapper;
+  }
+
+  @GET("/plaintext")
+  public void plaintText(Context ctx) {
+    ctx.send(MESSAGE_BYTES);
+  }
+
+  @GET("/json")
+  public void json(Context ctx) throws IOException {
+    ctx.setResponseType(JSON);
+    ctx.send(mapper.writeValueAsBytes(new Message(MESSAGE)));
+  }
+
+  @GET("/db")
+  @Dispatch
+  public void db(Context ctx) throws Exception {
+    World result;
+    try (Connection conn = dataSource.getConnection()) {
+      try (final PreparedStatement statement = conn.prepareStatement(SELECT_WORLD)) {
+        statement.setInt(1, randomWorld());
+        try (ResultSet rs = statement.executeQuery()) {
+          rs.next();
+          result = new World(rs.getInt("id"), rs.getInt("randomNumber"));
+        }
+      }
+    }
+    ctx.setResponseType(JSON);
+    ctx.send(mapper.writeValueAsBytes(result));
+  }
+
+  @GET("/queries")
+  @Dispatch
+  public void queries(Context ctx) throws Exception {
+    World[] result = new World[Util.queries(ctx)];
+    try (Connection conn = dataSource.getConnection()) {
+      for (int i = 0; i < result.length; i++) {
+        try (final PreparedStatement statement = conn.prepareStatement(SELECT_WORLD)) {
+          statement.setInt(1, randomWorld());
+          try (ResultSet rs = statement.executeQuery()) {
+            rs.next();
+            result[i] = new World(rs.getInt("id"), rs.getInt("randomNumber"));
+          }
+        }
+      }
+    }
+    ctx.setResponseType(JSON);
+    ctx.send(mapper.writeValueAsBytes(result));
+  }
+
+  @GET("/updates")
+  @Dispatch
+  public void updates(Context ctx) throws Exception {
+    World[] result = new World[Util.queries(ctx)];
+    StringJoiner updateSql = new StringJoiner(
+        ", ",
+        "UPDATE world SET randomNumber = temp.randomNumber FROM (VALUES ",
+        " ORDER BY 1) AS temp(id, randomNumber) WHERE temp.id = world.id");
+
+    try (Connection connection = dataSource.getConnection()) {
+      try (PreparedStatement statement = connection.prepareStatement(SELECT_WORLD)) {
+        for (int i = 0; i < result.length; i++) {
+          statement.setInt(1, randomWorld());
+          try (ResultSet rs = statement.executeQuery()) {
+            rs.next();
+            result[i] = new World(rs.getInt("id"), randomWorld());
+          }
+          // prepare update query
+          updateSql.add("(?, ?)");
+        }
+      }
+
+      try (PreparedStatement statement = connection.prepareStatement(updateSql.toString())) {
+        int i = 0;
+        for (World world : result) {
+          statement.setInt(++i, world.getRandomNumber());
+          statement.setInt(++i, world.getRandomNumber());
+        }
+        statement.executeUpdate();
+      }
+    }
+    ctx.setResponseType(JSON);
+    ctx.send(mapper.writeValueAsBytes(result));
+  }
+
+  @GET("/fortunes")
+  public fortunes fortunes(Context ctx) throws Exception {
+    List<Fortune> fortunes = new ArrayList<>();
+    try (Connection connection = dataSource.getConnection()) {
+      try (PreparedStatement stt = connection.prepareStatement("select * from fortune")) {
+        try (ResultSet rs = stt.executeQuery()) {
+          while (rs.next()) {
+            fortunes.add(new Fortune(rs.getInt("id"), rs.getString("message")));
+          }
+        }
+      }
+    }
+    fortunes.add(new Fortune(0, "Additional fortune added at request time."));
+    Collections.sort(fortunes);
+
+    /** render view: */
+    return views.fortunes.template(fortunes);
+  }
+}

+ 13 - 0
frameworks/Java/jooby2/src/main/java/com/techempower/Message.java

@@ -0,0 +1,13 @@
+package com.techempower;
+
+public class Message {
+  private String message;
+
+  public Message(String message) {
+    this.message = message;
+  }
+
+  public String getMessage() {
+    return message;
+  }
+}

+ 28 - 0
frameworks/Java/jooby2/src/main/java/com/techempower/MvcApp.java

@@ -0,0 +1,28 @@
+package com.techempower;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.jooby.hikari.HikariModule;
+import io.jooby.json.JacksonModule;
+import io.jooby.rocker.RockerModule;
+
+import javax.sql.DataSource;
+
+import static io.jooby.ExecutionMode.EVENT_LOOP;
+import static io.jooby.Jooby.runApp;
+
+public class MvcApp {
+  public static void main(String[] args) {
+    runApp(args, EVENT_LOOP, app -> {
+      /** JSON: */
+      app.install(new JacksonModule());
+
+      /** Database: */
+      app.install(new HikariModule());
+
+      /** Template engine: */
+      app.install(new RockerModule());
+
+      app.mvc(new Controller(app.require(DataSource.class), app.require(ObjectMapper.class)));
+    });
+  }
+}

+ 6 - 9
frameworks/Java/jooby2/src/main/java/com/techempower/PgClients.java

@@ -1,9 +1,9 @@
 package com.techempower;
 
 import io.reactiverse.pgclient.PgClient;
+import io.reactiverse.pgclient.PgPool;
 import io.reactiverse.pgclient.PgPoolOptions;
 import io.vertx.core.Vertx;
-import io.vertx.core.VertxOptions;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -12,21 +12,18 @@ import java.util.List;
 import java.util.stream.Stream;
 
 public class PgClients {
-  private final Iterator<PgClient> iterator;
+  private final Iterator<PgPool> iterator;
 
-  private PgClients(Collection<PgClient> clients) {
+  private PgClients(Collection<PgPool> clients) {
     iterator = Stream.generate(() -> clients).flatMap(Collection::stream).iterator();
   }
 
-  public synchronized PgClient next() {
+  public synchronized PgPool next() {
     return iterator.next();
   }
 
-  public static PgClients create(PgPoolOptions options) {
-    List<PgClient> clients = new ArrayList<>();
-    VertxOptions vertxOptions = new VertxOptions();
-    vertxOptions.setPreferNativeTransport(true);
-    Vertx vertx = Vertx.vertx(vertxOptions);
+  public static PgClients create(Vertx vertx, PgPoolOptions options) {
+    List<PgPool> clients = new ArrayList<>();
     for (int i = 0; i < Runtime.getRuntime().availableProcessors(); i++) {
       clients.add(PgClient.pool(vertx, options));
     }

+ 53 - 39
frameworks/Java/jooby2/src/main/java/com/techempower/ReactivePg.java

@@ -6,10 +6,13 @@ import io.jooby.Jooby;
 import io.jooby.json.JacksonModule;
 import io.jooby.rocker.RockerModule;
 import io.reactiverse.pgclient.PgClient;
+import io.reactiverse.pgclient.PgConnection;
 import io.reactiverse.pgclient.PgIterator;
 import io.reactiverse.pgclient.PgPoolOptions;
 import io.reactiverse.pgclient.Row;
 import io.reactiverse.pgclient.Tuple;
+import io.vertx.core.Vertx;
+import io.vertx.core.VertxOptions;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -30,7 +33,9 @@ public class ReactivePg extends Jooby {
 
   {
     /** PG client: */
-    PgClients pool = database(getConfig().getConfig("db"));
+    Vertx vertx = Vertx.vertx(new VertxOptions().setPreferNativeTransport(true));
+    PgPoolOptions options = pgPoolOptions(getConfig().getConfig("db"));
+    PgClients clients = PgClients.create(vertx, new PgPoolOptions(options).setMaxSize(1));
 
     /** Template engine: */
     install(new RockerModule());
@@ -41,7 +46,7 @@ public class ReactivePg extends Jooby {
 
     /** Single query: */
     get("/db", ctx -> {
-      pool.next().preparedQuery(SELECT_WORLD, Tuple.of(randomWorld()), rsp -> {
+      clients.next().preparedQuery(SELECT_WORLD, Tuple.of(randomWorld()), rsp -> {
         try {
           if (rsp.succeeded()) {
             PgIterator rs = rsp.result().iterator();
@@ -64,7 +69,7 @@ public class ReactivePg extends Jooby {
       AtomicInteger counter = new AtomicInteger();
       AtomicBoolean failed = new AtomicBoolean(false);
       World[] result = new World[queries];
-      PgClient client = pool.next();
+      PgClient client = clients.next();
       for (int i = 0; i < result.length; i++) {
         client.preparedQuery(SELECT_WORLD, Tuple.of(randomWorld()), rsp -> {
           if (rsp.succeeded()) {
@@ -96,48 +101,58 @@ public class ReactivePg extends Jooby {
       World[] result = new World[queries];
       AtomicInteger counter = new AtomicInteger(0);
       AtomicBoolean failed = new AtomicBoolean(false);
-      PgClient client = pool.next();
-      for (int i = 0; i < queries; i++) {
-        client.preparedQuery(SELECT_WORLD, Tuple.of(randomWorld()), rsp -> {
-          if (rsp.succeeded()) {
-            PgIterator rs = rsp.result().iterator();
-            Tuple row = rs.next();
-            World world = new World(row.getInteger(0), row.getInteger(1));
-            world.randomNumber = randomWorld();
-            result[counter.get()] = world;
-          } else {
-            if (failed.compareAndSet(false, true)) {
-              ctx.sendError(rsp.cause());
-            }
+      clients.next().getConnection(ar -> {
+        if (ar.failed()) {
+          if (failed.compareAndSet(false, true)) {
+            ctx.sendError(ar.cause());
           }
-
-          if (counter.incrementAndGet() == queries && !failed.get()) {
-            List<Tuple> batch = new ArrayList<>(queries);
-            for (World world : result) {
-              batch.add(Tuple.of(world.randomNumber, world.id));
+          return;
+        }
+        PgConnection conn = ar.result();
+        for (int i = 0; i < queries; i++) {
+          conn.preparedQuery(SELECT_WORLD, Tuple.of(randomWorld()), query -> {
+            if (query.succeeded()) {
+              PgIterator rs = query.result().iterator();
+              Tuple row = rs.next();
+              World world = new World(row.getInteger(0), randomWorld());
+              result[counter.get()] = world;
+            } else {
+              conn.close();
+              if (failed.compareAndSet(false, true)) {
+                ctx.sendError(query.cause());
+                return;
+              }
             }
 
-            client.preparedBatch(UPDATE_WORLD, batch, ar -> {
-              if (ar.failed()) {
-                ctx.sendError(ar.cause());
-              } else {
-                try {
-                  ctx.setResponseType(JSON)
-                      .send(mapper.writeValueAsBytes(result));
-                } catch (IOException x) {
-                  ctx.sendError(x);
-                }
+            if (counter.incrementAndGet() == queries && !failed.get()) {
+              List<Tuple> batch = new ArrayList<>(queries);
+              for (World world : result) {
+                batch.add(Tuple.of(world.getRandomNumber(), world.getId()));
               }
-            });
-          }
-        });
-      }
+
+              conn.preparedBatch(UPDATE_WORLD, batch, update -> {
+                conn.close();
+                if (update.failed()) {
+                  ctx.sendError(update.cause());
+                } else {
+                  try {
+                    ctx.setResponseType(JSON)
+                        .send(mapper.writeValueAsBytes(result));
+                  } catch (IOException x) {
+                    ctx.sendError(x);
+                  }
+                }
+              });
+            }
+          });
+        }
+      });
       return ctx;
     });
 
     /** Fortunes: */
     get("/fortunes", ctx -> {
-      pool.next().preparedQuery(SELECT_FORTUNE, rsp -> {
+      clients.next().preparedQuery(SELECT_FORTUNE, rsp -> {
         if (rsp.succeeded()) {
           PgIterator rs = rsp.result().iterator();
           List<Fortune> fortunes = new ArrayList<>();
@@ -161,7 +176,7 @@ public class ReactivePg extends Jooby {
     });
   }
 
-  private PgClients database(Config config) {
+  private PgPoolOptions pgPoolOptions(Config config) {
     PgPoolOptions options = new PgPoolOptions();
     options.setDatabase(config.getString("databaseName"));
     options.setHost(config.getString("serverName"));
@@ -169,8 +184,7 @@ public class ReactivePg extends Jooby {
     options.setUser(config.getString("user"));
     options.setPassword(config.getString("password"));
     options.setCachePreparedStatements(true);
-    options.setMaxSize(1);
-    return PgClients.create(options);
+    return options;
   }
 
   public static void main(String[] args) {

+ 9 - 2
frameworks/Java/jooby2/src/main/java/com/techempower/World.java

@@ -2,12 +2,19 @@ package com.techempower;
 
 public class World {
 
-  public int id;
-  public int randomNumber;
+  private int id;
+  private int randomNumber;
 
   public World(int id, int randomNumber) {
     this.id = id;
     this.randomNumber = randomNumber;
   }
 
+  public int getId() {
+    return id;
+  }
+
+  public int getRandomNumber() {
+    return randomNumber;
+  }
 }