Browse Source

Improvements for Ninja. First shot.

reyez 11 years ago
parent
commit
64d8e8091a
38 changed files with 811 additions and 73 deletions
  1. 6 5
      ninja-standalone/benchmark_config
  2. 9 2
      ninja-standalone/pom.xml
  3. 1 1
      ninja-standalone/setup.py
  4. 4 7
      ninja-standalone/src/main/java/conf/Routes.java
  5. 37 2
      ninja-standalone/src/main/java/hello/controllers/HelloDbController.java
  6. 2 6
      ninja-standalone/src/main/java/hello/controllers/HelloJsonController.java
  7. 1 1
      ninja-standalone/src/main/java/hello/controllers/HelloPlaintextController.java
  8. 2 0
      ninja-standalone/src/main/java/hello/dao/FortuneDao.java
  9. 8 0
      ninja-standalone/src/main/java/hello/dao/WorldDao.java
  10. 1 1
      ninja-standalone/src/main/java/hello/views/HelloFortuneController/index.ftl.html
  11. 26 0
      ninja-standalone/src/main/resources/META-INF/persistence.xml
  12. 17 4
      ninja-standalone/src/main/resources/conf/application.conf
  13. 0 13
      ninja-standalone/src/main/webapp/WEB-INF/resin-web.xml
  14. 100 0
      ninja-standalone/src/test/java/hello/controllers/HelloDbControllerTest.java
  15. 46 0
      ninja-standalone/src/test/java/hello/controllers/HelloFortuneControllerTest.java
  16. 56 0
      ninja-standalone/src/test/java/hello/controllers/HelloJsonControllerTest.java
  17. 38 0
      ninja-standalone/src/test/java/hello/controllers/HelloPlaintextControllerTest.java
  18. 29 0
      ninja-standalone/src/test/java/hello/controllers/SetupDao.java
  19. 5 1
      ninja/README.md
  20. 2 1
      ninja/benchmark_config
  21. 5 0
      ninja/changelog.md
  22. 16 3
      ninja/pom.xml
  23. 2 2
      ninja/setup.py
  24. 4 7
      ninja/src/main/java/conf/Routes.java
  25. 31 0
      ninja/src/main/java/hello/controllers/DatabaseAccess.java
  26. 38 2
      ninja/src/main/java/hello/controllers/HelloDbController.java
  27. 2 6
      ninja/src/main/java/hello/controllers/HelloJsonController.java
  28. 1 1
      ninja/src/main/java/hello/controllers/HelloPlaintextController.java
  29. 2 1
      ninja/src/main/java/hello/dao/FortuneDao.java
  30. 7 1
      ninja/src/main/java/hello/dao/WorldDao.java
  31. 1 1
      ninja/src/main/java/hello/views/HelloFortuneController/index.ftl.html
  32. 25 0
      ninja/src/main/resources/META-INF/persistence.xml
  33. 18 5
      ninja/src/main/resources/conf/application.conf
  34. 100 0
      ninja/src/test/java/hello/controllers/HelloDbControllerTest.java
  35. 46 0
      ninja/src/test/java/hello/controllers/HelloFortuneControllerTest.java
  36. 56 0
      ninja/src/test/java/hello/controllers/HelloJsonControllerTest.java
  37. 38 0
      ninja/src/test/java/hello/controllers/HelloPlaintextControllerTest.java
  38. 29 0
      ninja/src/test/java/hello/controllers/SetupDao.java

+ 6 - 5
ninja-standalone/benchmark_config

@@ -3,11 +3,12 @@
   "tests": [{
     "default": {
       "setup_file": "setup",
-      "json_url": "/ninja/json",
-      "db_url": "/ninja/db",
-      "query_url": "/ninja/queries/",
-      "fortune_url": "/ninja/fortunes",
-      "plaintext_url": "/ninja/plaintext",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/update?queries=",
+      "plaintext_url": "/plaintext",
       "port": 8080,
       "approach": "Realistic",
       "classification": "Fullstack",

+ 9 - 2
ninja-standalone/pom.xml

@@ -4,12 +4,12 @@
 	<groupId>helo.world</groupId>
 	<artifactId>hello-ninja-standalone</artifactId>
 	<version>0.0.1-SNAPSHOT</version>
-	<name>Ninja Framework Test Project</name>
+	<name>ninja-techempower-standalone</name>
 	<description>Nnja test for the TechEmpower/FrameworkBenchmarks project</description>
 
 	<properties>
 		<java-version>1.7</java-version>
-		<ninja.version>2.1.0</ninja.version>
+		<ninja.version>2.4.0</ninja.version>
 		<mysql.version>5.1.26</mysql.version>
 		<jetty.version>9.0.5.v20130815</jetty.version>
 	</properties>
@@ -46,6 +46,12 @@
 			<artifactId>mysql-connector-java</artifactId>
 			<version>${mysql.version}</version>
 		</dependency>
+                        
+                <dependency>
+                    <groupId>com.h2database</groupId>
+                    <artifactId>h2</artifactId>
+                    <version>1.3.174</version>
+                </dependency>
 	</dependencies>
 
 	<build>
@@ -63,6 +69,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-war-plugin</artifactId>
+                <version>2.4</version>
                 <configuration>
                     <warName>ninja</warName>
                 </configuration>

+ 1 - 1
ninja-standalone/setup.py

@@ -8,7 +8,7 @@ def start(args, logfile, errfile):
   
   try:
     subprocess.check_call("mvn clean compile assembly:single", shell=True, cwd="ninja-standalone", stderr=errfile, stdout=logfile)
-    subprocess.check_call("java -Dninja.port=8080 -Dninja.mode=prod -Dninja.context=/ninja -jar ninja-standalone/target/hello-ninja-standalone-0.0.1-SNAPSHOT-jar-with-dependencies.jar", shell=True, stderr=errfile, stdout=logfile)
+    subprocess.check_call("java -Dninja.port=8080 -jar target/hello-ninja-standalone-0.0.1-SNAPSHOT-jar-with-dependencies.jar", cwd="ninja-standalone", shell=True, stderr=errfile, stdout=logfile)
     return 0
   except subprocess.CalledProcessError:
     return 1

+ 4 - 7
ninja-standalone/src/main/java/conf/Routes.java

@@ -12,15 +12,12 @@ public class Routes implements ApplicationRoutes {
     @Override
     public void init(Router router) {
 
-	router.GET().route("/").with(HelloPlaintextController.class, "index");
-	router.GET().route("/plaintext")
-		.with(HelloPlaintextController.class, "index");
+	router.GET().route("/plaintext").with(HelloPlaintextController.class, "index");
 	router.GET().route("/json").with(HelloJsonController.class, "index");
-	router.GET().route("/queries/{queries}")
-		.with(HelloDbController.class, "multiGet");
+	router.GET().route("/queries").with(HelloDbController.class, "multiGet");
 	router.GET().route("/db").with(HelloDbController.class, "singleGet");
-	router.GET().route("/fortunes")
-		.with(HelloFortuneController.class, "index");
+	router.GET().route("/fortunes").with(HelloFortuneController.class, "index");
+        router.GET().route("/update").with(HelloDbController.class, "update");
 
     }
 }

+ 37 - 2
ninja-standalone/src/main/java/hello/controllers/HelloDbController.java

@@ -8,10 +8,11 @@ import java.util.concurrent.ThreadLocalRandom;
 
 import ninja.Result;
 import ninja.Results;
-import ninja.params.PathParam;
 
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.google.inject.persist.Transactional;
+import ninja.params.Param;
 
 @Singleton
 public class HelloDbController {
@@ -22,11 +23,39 @@ public class HelloDbController {
     @Inject
     WorldDao worldDao;
 
+
     public Result singleGet() {
 	return Results.json().render(getRandomWorld());
     }
 
-    public Result multiGet(@PathParam("queries") Integer queries) {
+    // @Transactional is important here as it encapsulates all
+    // JPA calls in dependent methods inside one - and only one - 
+    // transaction. Otherwise WorldDao would open x new transactions what
+    // is of course slower than only having one encapsulating transaction.
+    @Transactional
+    public Result multiGet(@Param("queries") Integer queries) {
+	if (queries == null || queries < 1) {
+	    queries = 1;
+	}
+	if (queries > 500) {
+	    queries = 500;
+	}
+
+	final World[] worlds = new World[queries];
+
+	for (int i = 0; i < queries; i++) {
+	    worlds[i] = getRandomWorld();
+	}
+
+	return Results.json().render(worlds);
+    }
+    
+    // @Transactional is important here as it encapsulates all
+    // JPA calls in dependent methods inside one - and only one - 
+    // transaction. Otherwise WorldDao would open x new transactions what
+    // is of course slower than only having one encapsulating transaction.
+    @Transactional
+    public Result update(@Param("queries") Integer queries) {
 	if (queries == null || queries < 1) {
 	    queries = 1;
 	}
@@ -39,6 +68,12 @@ public class HelloDbController {
 	for (int i = 0; i < queries; i++) {
 	    worlds[i] = getRandomWorld();
 	}
+        
+        // now update stuff:
+        for (World world : worlds) {
+            world.randomNumber = random.nextInt();
+            worldDao.put(world);
+        }
 
 	return Results.json().render(worlds);
     }

+ 2 - 6
ninja-standalone/src/main/java/hello/controllers/HelloJsonController.java

@@ -12,16 +12,12 @@ public class HelloJsonController {
 	return Results.json().render(new Message("Hello, world"));
     }
 
-    public static class Message {
+    public final static class Message {
 
-	private final String message;
+	public final String message;
 
 	public Message(String message) {
 	    this.message = message;
 	}
-
-	public String getMessage() {
-	    return message;
-	}
     }
 }

+ 1 - 1
ninja-standalone/src/main/java/hello/controllers/HelloPlaintextController.java

@@ -8,6 +8,6 @@ import com.google.inject.Singleton;
 @Singleton
 public class HelloPlaintextController {
     public Result index() {
-	return Results.html().renderRaw("Hello, world!");
+	return Results.text().render("Hello, world!");
     }
 }

+ 2 - 0
ninja-standalone/src/main/java/hello/dao/FortuneDao.java

@@ -9,8 +9,10 @@ import javax.persistence.Query;
 
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.Singleton;
 import com.google.inject.persist.Transactional;
 
+@Singleton
 public class FortuneDao {
 
     @Inject

+ 8 - 0
ninja-standalone/src/main/java/hello/dao/WorldDao.java

@@ -6,8 +6,10 @@ import javax.persistence.EntityManager;
 
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.Singleton;
 import com.google.inject.persist.Transactional;
 
+@Singleton
 public class WorldDao {
 
     @Inject
@@ -18,4 +20,10 @@ public class WorldDao {
 	EntityManager entityManager = entitiyManagerProvider.get();
 	return entityManager.find(World.class, id);
     }
+    
+    @Transactional
+    public void put(World world) {
+	EntityManager entityManager = entitiyManagerProvider.get();
+	entityManager.persist(world);
+    }
 }

+ 1 - 1
ninja-standalone/src/main/java/hello/views/HelloFortuneController/index.ftl.html

@@ -12,7 +12,7 @@
 		<#list fortunes as fortune>
 		<tr>
 			<td>${fortune.id}</td>
-			<td>${fortune.message?html}</td>
+			<td>${fortune.message}</td>
 		</tr>
 		</#list>
 	</table>

+ 26 - 0
ninja-standalone/src/main/resources/META-INF/persistence.xml

@@ -4,6 +4,32 @@
 	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
 	version="2.0">
+    
+        
+        <!-- Database for tests and local dev mode... -->
+        <persistence-unit name="h2" transaction-type="RESOURCE_LOCAL">
+            <provider>org.hibernate.ejb.HibernatePersistence</provider>
+            <properties>
+                <property name="javax.persistence.provider" value="org.hibernate.ejb.HibernatePersistence" />
+                <property name="hibernate.connection.driver_class" value="org.h2.Driver" />
+                <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
+                <!-- you may want to enable the ddl if you do not use migrations. -->
+                <property name="hibernate.hbm2ddl.auto" value="create" />
+                <property name="hibernate.show_sql" value="false" />
+                <property name="hibernate.format_sql" value="false" />
+
+                <!-- Connection Pooling settings -->
+                <property name="hibernate.connection.provider_class"
+                          value="org.hibernate.service.jdbc.connections.internal.C3P0ConnectionProvider" />
+
+                <property name="hibernate.c3p0.max_size" value="100" />
+                <property name="hibernate.c3p0.min_size" value="0" />
+                <property name="hibernate.c3p0.acquire_increment" value="1" />
+                <property name="hibernate.c3p0.idle_test_period" value="300" />
+                <property name="hibernate.c3p0.max_statements" value="0" />
+                <property name="hibernate.c3p0.timeout" value="100" />     
+            </properties>
+        </persistence-unit>
 
 	<!-- Direct mysql -->
 	<persistence-unit name="mysql" transaction-type="RESOURCE_LOCAL">

+ 17 - 4
ninja-standalone/src/main/resources/conf/application.conf

@@ -30,9 +30,22 @@ application.session.transferred_over_https_only=false
 ##############################################################################
 ninja.migration.run=false
 
-ninja.jpa.persistence_unit_name = mysql
-db.connection.url=jdbc:mysql://localhost:3306/hello_world?jdbcCompliantTruncation=false&amp;elideSetAutoCommits=true&amp;useLocalSessionState=true&amp;cachePrepStmts=true&amp;cacheCallableStmts=true&amp;alwaysSendSetIsolation=false&amp;prepStmtCacheSize=4096&amp;cacheServerConfiguration=true&amp;prepStmtCacheSqlLimit=2048&amp;zeroDateTimeBehavior=convertToNull&amp;traceProtocol=false&amp;useUnbufferedInput=false&amp;useReadAheadInput=false&amp;maintainTimeStats=false&amp;useServerPrepStmts&amp;cacheRSMetadata=true
-db.connection.username=benchmarkdbuser
-db.connection.password=benchmarkdbpass
+%prod.ninja.jpa.persistence_unit_name = mysql
+%prod.db.connection.url=jdbc:mysql://localhost:3306/hello_world?jdbcCompliantTruncation=false&amp;elideSetAutoCommits=true&amp;useLocalSessionState=true&amp;cachePrepStmts=true&amp;cacheCallableStmts=true&amp;alwaysSendSetIsolation=false&amp;prepStmtCacheSize=4096&amp;cacheServerConfiguration=true&amp;prepStmtCacheSqlLimit=2048&amp;zeroDateTimeBehavior=convertToNull&amp;traceProtocol=false&amp;useUnbufferedInput=false&amp;useReadAheadInput=false&amp;maintainTimeStats=false&amp;useServerPrepStmts&amp;cacheRSMetadata=true
+%prod.db.connection.username=benchmarkdbuser
+%prod.db.connection.password=benchmarkdbpass
+
+## for testing and developing locally:
+%dev.ninja.jpa.persistence_unit_name = h2
+%dev.db.connection.url=jdbc:h2:target/h2database
+%dev.db.connection.username=ra
+%dev.db.connection.password=
+
+## for testing
+%test.ninja.jpa.persistence_unit_name = h2
+# in memory database for testing...
+%test.db.connection.url=jdbc:h2:mem:
+%test.db.connection.username=ra
+%test.db.connection.password=
 
 application.secret = b9z4AQO0huDRrJXFVjNiNXmSVqPSbcqjEiNjdPVBApb8n9GnxVjWBr9jp8tRfe73

+ 0 - 13
ninja-standalone/src/main/webapp/WEB-INF/resin-web.xml

@@ -1,13 +0,0 @@
-<web-app xmlns="http://caucho.com/ns/resin">
-
-<database jndi-name='jdbc/hello_world'>
-  <driver>
-    <type>com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource</type>
-    <url>jdbc:mysql://localhost:3306/hello_world?jdbcCompliantTruncation=false&amp;elideSetAutoCommits=true&amp;useLocalSessionState=true&amp;cachePrepStmts=true&amp;cacheCallableStmts=true&amp;alwaysSendSetIsolation=false&amp;prepStmtCacheSize=4096&amp;cacheServerConfiguration=true&amp;prepStmtCacheSqlLimit=2048&amp;zeroDateTimeBehavior=convertToNull&amp;traceProtocol=false&amp;useUnbufferedInput=false&amp;useReadAheadInput=false&amp;maintainTimeStats=false&amp;useServerPrepStmts&amp;cacheRSMetadata=true</url>
-    <user>benchmarkdbuser</user>
-    <password>benchmarkdbpass</password>
-    <useUnicode/>
-  </driver>
-</database>
-
-</web-app>

+ 100 - 0
ninja-standalone/src/test/java/hello/controllers/HelloDbControllerTest.java

@@ -0,0 +1,100 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package hello.controllers;
+
+import hello.model.World;
+import ninja.NinjaDocTester;
+import org.doctester.testbrowser.Request;
+import org.doctester.testbrowser.Response;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+/**
+ *
+ * @author ra
+ */
+public class HelloDbControllerTest extends NinjaDocTester {
+            
+    String SINGLE_GET = "/db";
+    String QUERIES = "/queries";
+    String URL_UPDATE = "/update";
+    
+    @Before
+    public void setupClass() {
+        getInjector().getInstance(SetupDao.class).generateWorldsForTest();
+    }
+    
+    @Test
+    public void testSingleGet() {
+        
+        Response response = makeRequest(
+                Request
+                        .GET()
+                        .url(testServerUrl().path(SINGLE_GET))
+                        .contentTypeApplicationJson());
+        
+        // Just make sure that we get back a World Json.
+        assertThat(response.payloadAs(World.class), CoreMatchers.notNullValue());      
+                
+    }
+    
+    @Test
+    public void multipleQueries() {
+        
+        assertThatMutipleGetWorksFor(1);
+        assertThatMutipleGetWorksFor(5);
+        assertThatMutipleGetWorksFor(10);
+        assertThatMutipleGetWorksFor(15);
+        assertThatMutipleGetWorksFor(20);
+                
+    }
+    
+    private void assertThatMutipleGetWorksFor(int numberOfQueries) {
+        Response response = makeRequest(
+            Request
+                .GET()
+                .url(
+                    testServerUrl()
+                    .path(QUERIES)
+                    .addQueryParameter("queries", numberOfQueries + ""))
+                .contentTypeApplicationJson());
+        
+        // Just make sure that we get back an array
+        assertThat(response.payloadAs(World[].class).length, CoreMatchers.is(numberOfQueries)); 
+    }
+    
+    @Test
+    public void testUpdates() {
+        
+        assertThatUpdateWorks(1);
+        assertThatUpdateWorks(5);
+        assertThatUpdateWorks(10);
+        assertThatUpdateWorks(15);
+        assertThatUpdateWorks(20);
+                
+    }
+    
+    private void assertThatUpdateWorks(int numberOfQueries) {
+        
+        Response response = makeRequest(
+            Request.GET()
+                .url(
+                    testServerUrl()
+                    .path(URL_UPDATE)
+                    .addQueryParameter("queries", numberOfQueries + ""))
+                .contentTypeApplicationJson());
+        
+        assertThat(response.payloadAs(World[].class).length, CoreMatchers.is(numberOfQueries)); 
+        
+    }
+
+    
+    
+}

+ 46 - 0
ninja-standalone/src/test/java/hello/controllers/HelloFortuneControllerTest.java

@@ -0,0 +1,46 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package hello.controllers;
+
+import hello.model.Fortune;
+import ninja.NinjaDocTester;
+import org.doctester.testbrowser.Request;
+import org.doctester.testbrowser.Response;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author ra
+ */
+public class HelloFortuneControllerTest extends NinjaDocTester {
+
+    String URL = "/fortunes";
+    
+    @Test
+    public void testSomeMethod() {
+        
+        getInjector().getInstance(SetupDao.class).generateFortunesForTest();
+
+        Response response 
+                = makeRequest(Request.GET().url(testServerUrl().path(URL)));
+        
+        System.out.println(" " + response.payload);
+        
+        // make sure escaping works
+        assertTrue(response.payload.contains("&lt;script&gt;I want to be escaped&lt;/script&gt;"));
+
+        // make sure utf-8 works
+        assertTrue(response.payload.contains("レームワークのベンチマーク<"));
+        
+        // make sure new Fortune has been added to response
+        assertTrue(response.payload.contains("Additional fortune added at request time."));
+
+        
+    }
+    
+}

+ 56 - 0
ninja-standalone/src/test/java/hello/controllers/HelloJsonControllerTest.java

@@ -0,0 +1,56 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package hello.controllers;
+
+import ninja.NinjaDocTester;
+import org.doctester.testbrowser.Request;
+import org.doctester.testbrowser.Response;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author ra
+ */
+public class HelloJsonControllerTest extends NinjaDocTester {
+    
+    String URL = "/json";
+
+    @Test
+    public void testHelloJsonController() {
+        
+        Response response = makeRequest(
+            Request
+                .GET()
+                .url(testServerUrl().path(URL))
+                .contentTypeApplicationJson());
+        
+        assertThat(
+            response.payloadAs(Message.class).message, 
+            CoreMatchers.is("Hello, world"));
+        
+    }
+    
+    /**
+     * Duplicated from HelloJsonController.
+     * 
+     * Stuff in HelloJsonController is final, but to deserialize the message we
+     * need an empty constructor...
+     */
+    public final static class Message {
+
+	public String message;
+        
+	public Message() {}
+
+	public Message(String message) {
+	    this.message = message;
+	}
+    }
+    
+}

+ 38 - 0
ninja-standalone/src/test/java/hello/controllers/HelloPlaintextControllerTest.java

@@ -0,0 +1,38 @@
+ /*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package hello.controllers;
+
+import ninja.NinjaDocTester;
+import org.doctester.testbrowser.Request;
+import org.doctester.testbrowser.Response;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author ra
+ */
+public class HelloPlaintextControllerTest extends NinjaDocTester {
+    
+    String URL = "/plaintext";
+    
+    @Test
+    public void helloPlaintextControllerTest() {
+        
+        Response response = makeRequest(
+            Request.GET().url(testServerUrl().path(URL)));
+        
+        assertThat(response.payload, CoreMatchers.is("Hello, world!"));
+        assertThat(
+            response.headers.get("Content-Type"), 
+            CoreMatchers.is("text/plain; charset=UTF-8"));
+        
+   
+    }
+    
+}

+ 29 - 0
ninja-standalone/src/test/java/hello/controllers/SetupDao.java

@@ -0,0 +1,29 @@
+package hello.dao;
+
+import hello.model.Fortune;
+
+import java.util.List;
+
+import javax.persistence.EntityManager;
+import javax.persistence.Query;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.persist.Transactional;
+
+public class FortuneDao {
+
+    @Inject
+    Provider<EntityManager> entitiyManagerProvider;
+
+    @Transactional
+    public List<Fortune> getAll() {
+	EntityManager entityManager = entitiyManagerProvider.get();
+
+	Query q = entityManager.createQuery("SELECT x FROM Fortune x");
+	List<Fortune> fortunes = q.getResultList();
+
+	return fortunes;
+    }
+
+}

+ 5 - 1
ninja/README.md

@@ -13,12 +13,16 @@ http://localhost:8080/ninja/db
 
 ### Multiple Queries Test
 
-http://localhost:8080/ninja/queries/5
+http://localhost:8080/ninja/queries?5
 
 ### Fortunes Test
 
 http://localhost:8080/ninja/fotunes
 
+### Update Test
+
+http://localhost:8080/ninja/update?5
+
 ### Plaintext Test
 
 http://localhost:8080/ninja/plaintext

+ 2 - 1
ninja/benchmark_config

@@ -5,8 +5,9 @@
       "setup_file": "setup",
       "json_url": "/ninja/json",
       "db_url": "/ninja/db",
-      "query_url": "/ninja/queries/",
+      "query_url": "/ninja/queries",
       "fortune_url": "/ninja/fortunes",
+      "update_url": "/ninja/update",
       "plaintext_url": "/ninja/plaintext",
       "port": 8080,
       "approach": "Realistic",

+ 5 - 0
ninja/changelog.md

@@ -0,0 +1,5 @@
+ * 2013-12-13 Removed route that is never used in tests.
+ * 2013-12-14 Bump to Ninja 2.4.0.
+ * 2013-12-14 Added testcases and support for h2 in memory to develop locally with
+              in memory db.
+ * 2013-12-14 Added support for "updates" benchmark.

+ 16 - 3
ninja/pom.xml

@@ -4,12 +4,12 @@
 	<groupId>helo.world</groupId>
 	<artifactId>hello-ninja</artifactId>
 	<version>0.0.1-SNAPSHOT</version>
-	<name>Ninja Framework Test Project</name>
+	<name>ninja-techempower-resin</name>
 	<description>Nnja test for the TechEmpower/FrameworkBenchmarks project</description>
 
 	<properties>
 		<java-version>1.7</java-version>
-		<ninja.version>2.1.0</ninja.version>
+		<ninja.version>2.4.0</ninja.version>
 		<mysql.version>5.1.26</mysql.version>
 		<jetty.version>9.0.5.v20130815</jetty.version>
 	</properties>
@@ -17,7 +17,7 @@
 	<dependencies>
 		<dependency>
 			<groupId>org.ninjaframework</groupId>
-			<artifactId>ninja-servlet</artifactId>
+			<artifactId>ninja-standalone</artifactId>
 			<version>${ninja.version}</version>
 		</dependency>
 
@@ -40,6 +40,12 @@
 			<artifactId>mysql-connector-java</artifactId>
 			<version>${mysql.version}</version>
 		</dependency>
+                
+                <dependency>
+                    <groupId>com.h2database</groupId>
+                    <artifactId>h2</artifactId>
+                    <version>1.3.174</version>
+                </dependency>
 	</dependencies>
 
 	<build>
@@ -57,10 +63,17 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-war-plugin</artifactId>
+                <version>2.4</version>
                 <configuration>
                     <warName>ninja</warName>
                 </configuration>
             </plugin>
+            
+            <plugin>
+                <groupId>org.ninjaframework</groupId>
+                <artifactId>ninja-maven-plugin</artifactId>
+                <version>${ninja.version}</version>
+            </plugin>
 
 			<plugin>
 				<groupId>org.apache.maven.plugins</groupId>

+ 2 - 2
ninja/setup.py

@@ -13,11 +13,11 @@ def start(args, logfile, errfile):
       subprocess.check_call('rmdir /S /Q "%RESIN_HOME%\\webapps\\"', shell=True, stderr=errfile, stdout=logfile)
       subprocess.check_call('mkdir "%RESIN_HOME%\\webapps\\"', shell=True, stderr=errfile, stdout=logfile)
       subprocess.check_call('copy ninja\\target\\ninja.war "%RESIN_HOME%\\webapps\\ninja.war"', shell=True, stderr=errfile, stdout=logfile)
-      subprocess.check_call('"%RESIN_HOME%\\bin\\start.bat -Dninja.mode=prod"', shell=True, stderr=errfile, stdout=logfile)
+      subprocess.check_call('"%RESIN_HOME%\\bin\\start.bat"', shell=True, stderr=errfile, stdout=logfile)
     else:
       subprocess.check_call("rm -rf $RESIN_HOME/webapps/*", shell=True, stderr=errfile, stdout=logfile)
       subprocess.check_call("cp ninja/target/ninja.war $RESIN_HOME/webapps/ninja.war", shell=True, stderr=errfile, stdout=logfile)
-      subprocess.check_call("$RESIN_HOME/bin/resinctl start -Dninja.mode=prod", shell=True, stderr=errfile, stdout=logfile)
+      subprocess.check_call("$RESIN_HOME/bin/resinctl", shell=True, stderr=errfile, stdout=logfile)
     return 0
   except subprocess.CalledProcessError:
     return 1

+ 4 - 7
ninja/src/main/java/conf/Routes.java

@@ -12,15 +12,12 @@ public class Routes implements ApplicationRoutes {
     @Override
     public void init(Router router) {
 
-	router.GET().route("/").with(HelloPlaintextController.class, "index");
-	router.GET().route("/plaintext")
-		.with(HelloPlaintextController.class, "index");
+	router.GET().route("/plaintext").with(HelloPlaintextController.class, "index");
 	router.GET().route("/json").with(HelloJsonController.class, "index");
-	router.GET().route("/queries/{queries}")
-		.with(HelloDbController.class, "multiGet");
+	router.GET().route("/queries").with(HelloDbController.class, "multiGet");
 	router.GET().route("/db").with(HelloDbController.class, "singleGet");
-	router.GET().route("/fortunes")
-		.with(HelloFortuneController.class, "index");
+	router.GET().route("/fortunes").with(HelloFortuneController.class, "index");
+        router.GET().route("/update").with(HelloDbController.class, "update");
 
     }
 }

+ 31 - 0
ninja/src/main/java/hello/controllers/DatabaseAccess.java

@@ -0,0 +1,31 @@
+package hello.controllers;
+
+import com.google.inject.Inject;
+import com.google.inject.persist.UnitOfWork;
+import ninja.Context;
+import ninja.Filter;
+import ninja.FilterChain;
+import ninja.Result;
+
+/**
+ *
+ * @author ra
+ */
+public class DatabaseAccess implements Filter {
+    
+    @Inject
+    private UnitOfWork unitOfWork;
+
+    @Override
+    public Result filter(FilterChain filterChain, Context context) {
+        
+        unitOfWork.begin();
+        Result result = filterChain.next(context);
+        unitOfWork.end();
+        
+        return result;
+     }
+ 
+}
+
+

+ 38 - 2
ninja/src/main/java/hello/controllers/HelloDbController.java

@@ -8,10 +8,12 @@ import java.util.concurrent.ThreadLocalRandom;
 
 import ninja.Result;
 import ninja.Results;
-import ninja.params.PathParam;
 
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.google.inject.persist.Transactional;
+import ninja.FilterWith;
+import ninja.params.Param;
 
 @Singleton
 public class HelloDbController {
@@ -22,11 +24,17 @@ public class HelloDbController {
     @Inject
     WorldDao worldDao;
 
+    @FilterWith(DatabaseAccess.class)
     public Result singleGet() {
 	return Results.json().render(getRandomWorld());
     }
 
-    public Result multiGet(@PathParam("queries") Integer queries) {
+    // @Transactional is important here as it encapsulates all
+    // JPA calls in dependent methods inside one - and only one - 
+    // transaction. Otherwise WorldDao would open x new transactions what
+    // is of course slower than only having one encapsulating transaction.
+    @FilterWith(DatabaseAccess.class)
+    public Result multiGet(@Param("queries") Integer queries) {
 	if (queries == null || queries < 1) {
 	    queries = 1;
 	}
@@ -42,6 +50,34 @@ public class HelloDbController {
 
 	return Results.json().render(worlds);
     }
+    
+    // @Transactional is important here as it encapsulates all
+    // JPA calls in dependent methods inside one - and only one - 
+    // transaction. Otherwise WorldDao would open x new transactions what
+    // is of course slower than only having one encapsulating transaction.
+    @FilterWith(DatabaseAccess.class)
+    public Result update(@Param("queries") Integer queries) {
+	if (queries == null || queries < 1) {
+	    queries = 1;
+	}
+	if (queries > 500) {
+	    queries = 500;
+	}
+
+	final World[] worlds = new World[queries];
+
+	for (int i = 0; i < queries; i++) {
+	    worlds[i] = getRandomWorld();
+	}
+        
+        // now update stuff:
+        for (World world : worlds) {
+            world.randomNumber = random.nextInt();
+            worldDao.put(world);
+        }
+
+	return Results.json().render(worlds);
+    }
 
     private World getRandomWorld() {
 	return worldDao.get(random.nextInt(DB_ROWS) + 1);

+ 2 - 6
ninja/src/main/java/hello/controllers/HelloJsonController.java

@@ -12,16 +12,12 @@ public class HelloJsonController {
 	return Results.json().render(new Message("Hello, world"));
     }
 
-    public static class Message {
+    public final static class Message {
 
-	private final String message;
+	public final String message;
 
 	public Message(String message) {
 	    this.message = message;
 	}
-
-	public String getMessage() {
-	    return message;
-	}
     }
 }

+ 1 - 1
ninja/src/main/java/hello/controllers/HelloPlaintextController.java

@@ -8,6 +8,6 @@ import com.google.inject.Singleton;
 @Singleton
 public class HelloPlaintextController {
     public Result index() {
-	return Results.html().renderRaw("Hello, world!");
+	return Results.text().renderRaw("Hello, world!");
     }
 }

+ 2 - 1
ninja/src/main/java/hello/dao/FortuneDao.java

@@ -9,14 +9,15 @@ import javax.persistence.Query;
 
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.Singleton;
 import com.google.inject.persist.Transactional;
 
+@Singleton
 public class FortuneDao {
 
     @Inject
     Provider<EntityManager> entitiyManagerProvider;
 
-    @Transactional
     public List<Fortune> getAll() {
 	EntityManager entityManager = entitiyManagerProvider.get();
 

+ 7 - 1
ninja/src/main/java/hello/dao/WorldDao.java

@@ -6,16 +6,22 @@ import javax.persistence.EntityManager;
 
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.Singleton;
 import com.google.inject.persist.Transactional;
 
+@Singleton
 public class WorldDao {
 
     @Inject
     Provider<EntityManager> entitiyManagerProvider;
 
-    @Transactional
     public World get(int id) {
 	EntityManager entityManager = entitiyManagerProvider.get();
 	return entityManager.find(World.class, id);
     }
+    
+    public void put(World world) {
+	EntityManager entityManager = entitiyManagerProvider.get();
+	entityManager.persist(world);
+    }
 }

+ 1 - 1
ninja/src/main/java/hello/views/HelloFortuneController/index.ftl.html

@@ -12,7 +12,7 @@
 		<#list fortunes as fortune>
 		<tr>
 			<td>${fortune.id}</td>
-			<td>${fortune.message?html}</td>
+			<td>${fortune.message}</td>
 		</tr>
 		</#list>
 	</table>

+ 25 - 0
ninja/src/main/resources/META-INF/persistence.xml

@@ -4,6 +4,31 @@
 	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
 	version="2.0">
+    
+        <!-- Database for tests and local dev mode... -->
+        <persistence-unit name="h2" transaction-type="RESOURCE_LOCAL">
+            <provider>org.hibernate.ejb.HibernatePersistence</provider>
+            <properties>
+                <property name="javax.persistence.provider" value="org.hibernate.ejb.HibernatePersistence" />
+                <property name="hibernate.connection.driver_class" value="org.h2.Driver" />
+                <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
+                <!-- you may want to enable the ddl if you do not use migrations. -->
+                <property name="hibernate.hbm2ddl.auto" value="create" />
+                <property name="hibernate.show_sql" value="false" />
+                <property name="hibernate.format_sql" value="false" />
+
+                <!-- Connection Pooling settings -->
+                <property name="hibernate.connection.provider_class"
+                          value="org.hibernate.service.jdbc.connections.internal.C3P0ConnectionProvider" />
+
+                <property name="hibernate.c3p0.max_size" value="100" />
+                <property name="hibernate.c3p0.min_size" value="0" />
+                <property name="hibernate.c3p0.acquire_increment" value="1" />
+                <property name="hibernate.c3p0.idle_test_period" value="300" />
+                <property name="hibernate.c3p0.max_statements" value="0" />
+                <property name="hibernate.c3p0.timeout" value="100" />     
+            </properties>
+        </persistence-unit>
 
 	<!-- Direct mysql -->
 	<persistence-unit name="mysql" transaction-type="RESOURCE_LOCAL">

+ 18 - 5
ninja/src/main/resources/conf/application.conf

@@ -30,11 +30,24 @@ application.session.transferred_over_https_only=false
 ##############################################################################
 ninja.migration.run=false
 
-ninja.jpa.persistence_unit_name = resin
+%prod.ninja.jpa.persistence_unit_name = resin
 
-%test.ninja.jpa.persistence_unit_name = mysql
-%test.db.connection.url=jdbc:mysql://localhost/hello_world
-%test.db.connection.username=benchmarkdbuser
-%test.db.connection.password=benchmarkdbpass
+#%dev.ninja.jpa.persistence_unit_name = mysql
+#%dev.db.connection.url=jdbc:mysql://localhost/hello_world
+#%dev.db.connection.username=benchmarkdbuser
+#%dev.db.connection.password=benchmarkdbpass
+
+## for testing and developing locally:
+%dev.ninja.jpa.persistence_unit_name = h2
+%dev.db.connection.url=jdbc:h2:target/h2database
+%dev.db.connection.username=ra
+%dev.db.connection.password=
+
+## for testing
+%test.ninja.jpa.persistence_unit_name = h2
+# in memory database for testing...
+%test.db.connection.url=jdbc:h2:mem:
+%test.db.connection.username=ra
+%test.db.connection.password=
 
 application.secret = b9z4AQO0huDRrJXFVjNiNXmSVqPSbcqjEiNjdPVBApb8n9GnxVjWBr9jp8tRfe73

+ 100 - 0
ninja/src/test/java/hello/controllers/HelloDbControllerTest.java

@@ -0,0 +1,100 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package hello.controllers;
+
+import hello.model.World;
+import ninja.NinjaDocTester;
+import org.doctester.testbrowser.Request;
+import org.doctester.testbrowser.Response;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+/**
+ *
+ * @author ra
+ */
+public class HelloDbControllerTest extends NinjaDocTester {
+            
+    String SINGLE_GET = "/db";
+    String QUERIES = "/queries";
+    String URL_UPDATE = "/update";
+    
+    @Before
+    public void setupClass() {
+        getInjector().getInstance(SetupDao.class).generateWorldsForTest();
+    }
+    
+    @Test
+    public void testSingleGet() {
+        
+        Response response = makeRequest(
+                Request
+                        .GET()
+                        .url(testServerUrl().path(SINGLE_GET))
+                        .contentTypeApplicationJson());
+        
+        // Just make sure that we get back a World Json.
+        assertThat(response.payloadAs(World.class), CoreMatchers.notNullValue());      
+                
+    }
+    
+    @Test
+    public void multipleQueries() {
+        
+        assertThatMutipleGetWorksFor(1);
+        assertThatMutipleGetWorksFor(5);
+        assertThatMutipleGetWorksFor(10);
+        assertThatMutipleGetWorksFor(15);
+        assertThatMutipleGetWorksFor(20);
+                
+    }
+    
+    private void assertThatMutipleGetWorksFor(int numberOfQueries) {
+        Response response = makeRequest(
+            Request
+                .GET()
+                .url(
+                    testServerUrl()
+                    .path(QUERIES)
+                    .addQueryParameter("queries", numberOfQueries + ""))
+                .contentTypeApplicationJson());
+        
+        // Just make sure that we get back an array
+        assertThat(response.payloadAs(World[].class).length, CoreMatchers.is(numberOfQueries)); 
+    }
+    
+    @Test
+    public void testUpdates() {
+        
+        assertThatUpdateWorks(1);
+        assertThatUpdateWorks(5);
+        assertThatUpdateWorks(10);
+        assertThatUpdateWorks(15);
+        assertThatUpdateWorks(20);
+                
+    }
+    
+    private void assertThatUpdateWorks(int numberOfQueries) {
+        
+        Response response = makeRequest(
+            Request.GET()
+                .url(
+                    testServerUrl()
+                    .path(URL_UPDATE)
+                    .addQueryParameter("queries", numberOfQueries + ""))
+                .contentTypeApplicationJson());
+        
+        assertThat(response.payloadAs(World[].class).length, CoreMatchers.is(numberOfQueries)); 
+        
+    }
+
+    
+    
+}

+ 46 - 0
ninja/src/test/java/hello/controllers/HelloFortuneControllerTest.java

@@ -0,0 +1,46 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package hello.controllers;
+
+import hello.model.Fortune;
+import ninja.NinjaDocTester;
+import org.doctester.testbrowser.Request;
+import org.doctester.testbrowser.Response;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author ra
+ */
+public class HelloFortuneControllerTest extends NinjaDocTester {
+
+    String URL = "/fortunes";
+    
+    @Test
+    public void testSomeMethod() {
+        
+        getInjector().getInstance(SetupDao.class).generateFortunesForTest();
+
+        Response response 
+                = makeRequest(Request.GET().url(testServerUrl().path(URL)));
+        
+        System.out.println(" " + response.payload);
+        
+        // make sure escaping works
+        assertTrue(response.payload.contains("&lt;script&gt;I want to be escaped&lt;/script&gt;"));
+
+        // make sure utf-8 works
+        assertTrue(response.payload.contains("レームワークのベンチマーク<"));
+        
+        // make sure new Fortune has been added to response
+        assertTrue(response.payload.contains("Additional fortune added at request time."));
+
+        
+    }
+    
+}

+ 56 - 0
ninja/src/test/java/hello/controllers/HelloJsonControllerTest.java

@@ -0,0 +1,56 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package hello.controllers;
+
+import ninja.NinjaDocTester;
+import org.doctester.testbrowser.Request;
+import org.doctester.testbrowser.Response;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author ra
+ */
+public class HelloJsonControllerTest extends NinjaDocTester {
+    
+    String URL = "/json";
+
+    @Test
+    public void testHelloJsonController() {
+        
+        Response response = makeRequest(
+            Request
+                .GET()
+                .url(testServerUrl().path(URL))
+                .contentTypeApplicationJson());
+        
+        assertThat(
+            response.payloadAs(Message.class).message, 
+            CoreMatchers.is("Hello, world"));
+        
+    }
+    
+    /**
+     * Duplicated from HelloJsonController.
+     * 
+     * Stuff in HelloJsonController is final, but to deserialize the message we
+     * need an empty constructor...
+     */
+    public final static class Message {
+
+	public String message;
+        
+	public Message() {}
+
+	public Message(String message) {
+	    this.message = message;
+	}
+    }
+    
+}

+ 38 - 0
ninja/src/test/java/hello/controllers/HelloPlaintextControllerTest.java

@@ -0,0 +1,38 @@
+ /*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package hello.controllers;
+
+import ninja.NinjaDocTester;
+import org.doctester.testbrowser.Request;
+import org.doctester.testbrowser.Response;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author ra
+ */
+public class HelloPlaintextControllerTest extends NinjaDocTester {
+    
+    String URL = "/plaintext";
+    
+    @Test
+    public void helloPlaintextControllerTest() {
+        
+        Response response = makeRequest(
+            Request.GET().url(testServerUrl().path(URL)));
+        
+        assertThat(response.payload, CoreMatchers.is("Hello, world!"));
+        assertThat(
+            response.headers.get("Content-Type"), 
+            CoreMatchers.is("text/plain; charset=UTF-8"));
+        
+   
+    }
+    
+}

+ 29 - 0
ninja/src/test/java/hello/controllers/SetupDao.java

@@ -0,0 +1,29 @@
+package hello.dao;
+
+import hello.model.Fortune;
+
+import java.util.List;
+
+import javax.persistence.EntityManager;
+import javax.persistence.Query;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.persist.Transactional;
+
+public class FortuneDao {
+
+    @Inject
+    Provider<EntityManager> entitiyManagerProvider;
+
+    @Transactional
+    public List<Fortune> getAll() {
+	EntityManager entityManager = entitiyManagerProvider.get();
+
+	Query q = entityManager.createQuery("SELECT x FROM Fortune x");
+	List<Fortune> fortunes = q.getResultList();
+
+	return fortunes;
+    }
+
+}