Browse Source

Merge pull request #603 from lhotari/master

Grails 2.3.3 upgrade and implementation for all test types
Mike Smith 11 years ago
parent
commit
c7559d7601

+ 1 - 1
config/benchmark_profile

@@ -1,6 +1,6 @@
 export JAVA_HOME=/usr/lib/jvm/java-1.7.0-openjdk-amd64
 export RESIN_HOME=~/FrameworkBenchmarks/installs/resin-4.0.36
-export GRAILS_HOME=~/FrameworkBenchmarks/installs/grails-2.3.1
+export GRAILS_HOME=~/FrameworkBenchmarks/installs/grails-2.3.3
 export VERTX_HOME=~/FrameworkBenchmarks/installs/vert.x-2.0.2-final
 export GOROOT=~/FrameworkBenchmarks/installs/go
 export GOPATH=~/FrameworkBenchmarks/go:~/FrameworkBenchmarks/webgo:~/FrameworkBenchmarks/revel

+ 21 - 4
grails/README.md

@@ -4,17 +4,34 @@ This is the Grails portion of a [benchmarking test suite](../) comparing a varie
 
 ## Infrastructure Software Versions
 The tests were run with:
-* [Grails 2.3.1](http://grails.org/)
+* [Grails 2.3.3](http://grails.org/)
 * [Java OpenJDK 1.7.0_09](http://openjdk.java.net/)
 * [Resin 4.0.34](http://www.caucho.com/)
 * [MySQL 5.5.29](https://dev.mysql.com/)
 
 
 ## Test URLs
-### JSON Encoding Test
+
+### Test type 1: JSON serialization
 
 http://localhost:8080/grails/hello/json
 
-### Data-Store/Database Mapping Test
+### Test type 2: Single database query
+
+http://localhost:8080/grails/hello/db
+
+### Test type 3: Multiple database queries
+
+http://localhost:8080/grails/hello/queries?queries=10
+
+### Test type 4: Fortunes
+
+http://localhost:8080/grails/hello/fortunes
+
+### Test type 5: Database updates
+
+http://localhost:8080/grails/hello/updates?queries=10
+
+### Test type 6: Plaintext
 
-http://localhost:8080/grails/hello/db?queries=5
+http://localhost:8080/grails/hello/plaintext

+ 5 - 3
grails/benchmark_config

@@ -5,7 +5,10 @@
       "setup_file": "setup",
       "json_url": "/grails/hello/json",
       "db_url": "/grails/hello/db",
-      "query_url": "/grails/hello/db?queries=",
+      "query_url": "/grails/hello/queries?queries=",
+      "fortune_url": "/grails/hello/fortunes",
+      "update_url": "/grails/hello/updates?queries=",
+      "plaintext_url": "/grails/hello/plaintext",
       "port": 8080,
       "approach": "Realistic",
       "classification": "Fullstack",
@@ -19,8 +22,7 @@
       "database_os": "Linux",
       "display_name": "grails",
       "notes": "",
-      "versus": "servlet",
-      "skip": "true"
+      "versus": "servlet"
     }
   }]
 }

+ 15 - 13
grails/hello/.classpath

@@ -1,14 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-    <classpathentry kind="src" path="src/java"/>
-    <classpathentry kind="src" path="src/groovy"/>
-    <classpathentry kind="src" path="grails-app/conf"/>
-    <classpathentry kind="src" path="grails-app/controllers"/>
-    <classpathentry kind="src" path="grails-app/domain"/>
-    <classpathentry kind="src" path="grails-app/services"/>
-    <classpathentry kind="src" path="grails-app/taglib"/>
-    <classpathentry kind="src" path="test/integration"/>
-    <classpathentry kind="src" path="test/unit"/>
-    <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-    <classpathentry kind="con" path="com.springsource.sts.grails.core.CLASSPATH_CONTAINER"/>
-    <classpathentry kind="output" path="web-app/WEB-INF/classes"/>
-</classpath>
+	<classpathentry kind="src" path="src/java"/>
+	<classpathentry kind="src" path="src/groovy"/>
+	<classpathentry kind="src" path="grails-app/conf"/>
+	<classpathentry kind="src" path="grails-app/controllers"/>
+	<classpathentry kind="src" path="grails-app/domain"/>
+	<classpathentry kind="src" path="grails-app/services"/>
+	<classpathentry kind="src" path="grails-app/taglib"/>
+	<classpathentry kind="src" path="grails-app/utils"/>
+	<classpathentry kind="src" path="test/integration"/>
+	<classpathentry kind="src" path="test/unit"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.grails.ide.eclipse.core.CLASSPATH_CONTAINER"/>
+	<classpathentry kind="output" path="target-eclipse/classes"/>
+</classpath>

+ 17 - 0
grails/hello/.gitignore

@@ -0,0 +1,17 @@
+*.iws
+*Db.properties
+*Db.script
+.settings
+stacktrace.log
+/*.zip
+/plugin.xml
+/*.log
+/*DB.*
+/cobertura.ser
+.DS_Store
+/target/
+/out/
+/web-app/plugins
+/web-app/WEB-INF/classes
+/.link_to_grails_plugins/
+/target-eclipse/

+ 7 - 1
grails/hello/.project

@@ -5,6 +5,11 @@
 	<projects>
 	</projects>
 	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.wst.common.project.facet.core.builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
 		<buildCommand>
 			<name>org.eclipse.jdt.core.javabuilder</name>
 			<arguments>
@@ -12,8 +17,9 @@
 		</buildCommand>
 	</buildSpec>
 	<natures>
-	    <nature>com.springsource.sts.grails.core.nature</nature>
+		<nature>org.grails.ide.eclipse.core.nature</nature>
 		<nature>org.eclipse.jdt.groovy.core.groovyNature</nature>
 		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
 	</natures>
 </projectDescription>

+ 2 - 2
grails/hello/application.properties

@@ -1,6 +1,6 @@
 #Grails Metadata file
-#Sat Oct 26 13:32:20 PDT 2013
-app.grails.version=2.3.1
+#Sun Oct 20 22:08:59 EEST 2013
+app.grails.version=2.3.3
 app.name=hello
 app.servlet.version=2.5
 app.version=0.1

+ 32 - 5
grails/hello/grails-app/conf/BuildConfig.groovy

@@ -2,10 +2,34 @@ grails.servlet.version = "2.5" // Change depending on target container complianc
 grails.project.class.dir = "target/classes"
 grails.project.test.class.dir = "target/test-classes"
 grails.project.test.reports.dir = "target/test-reports"
+grails.project.work.dir = "target/work"
 grails.project.target.level = 1.7
 grails.project.source.level = 1.7
 //grails.project.war.file = "target/${appName}-${appVersion}.war"
 
+grails.project.fork = [
+    // configure settings for compilation JVM, note that if you alter the Groovy version forked compilation is required
+    //  compile: [maxMemory: 256, minMemory: 64, debug: false, maxPerm: 256, daemon:true],
+
+    // configure settings for the test-app JVM, uses the daemon by default
+    test: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, daemon:true],
+    // configure settings for the run-app JVM
+    run: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, forkReserve:false],
+    // configure settings for the run-war JVM
+    war: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, forkReserve:false],
+    // configure settings for the Console UI JVM
+    console: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256]
+]
+
+def yjpConfig = [jvmArgs: [
+        "-agentpath:/opt/yjp/bin/linux-x86-64/libyjpagent.so=delay=30000,disablealloc,disablej2ee,noj2ee,builtinprobes=none,sampling,monitors,onexit=snapshot,telemetryperiod=250"
+    ]]
+if (System.getProperty("grails.yjp")) {
+    grails.project.fork.war += yjpConfig
+    println "Using YJP for run-war"
+}
+
+grails.project.dependency.resolver = "maven" // or ivy
 grails.project.dependency.resolution = {
     // inherit Grails' default dependencies
     inherits("global") {
@@ -14,11 +38,13 @@ grails.project.dependency.resolution = {
     }
     log "error" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose'
     checksums true // Whether to verify checksums on resolve
+    legacyResolve false // whether to do a secondary resolve on plugin installation, not advised and here for backwards compatibility
 
     repositories {
         inherits true // Whether to inherit repository definitions from plugins
         grailsPlugins()
         grailsHome()
+		mavenLocal()
         grailsCentral()
         mavenCentral()
 
@@ -33,18 +59,19 @@ grails.project.dependency.resolution = {
     dependencies {
         // specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes eg.
 
-        runtime 'mysql:mysql-connector-java:5.1.22'
+        runtime 'mysql:mysql-connector-java:5.1.27'
     }
 
     plugins {
-        compile ":hibernate:3.6.10.2"
-        runtime ":jquery:1.7.1"
-        runtime ":resources:1.1.6"
+        runtime ":hibernate:3.6.10.4"
+        //runtime ":jquery:1.10.2"
+        //runtime ":resources:1.2.1"
 
         // Uncomment these (or add new ones) to enable additional resources capabilities
         //runtime ":zipped-resources:1.0"
         //runtime ":cached-resources:1.0"
         //runtime ":yui-minify-resources:0.1.4"
-        build ':tomcat:7.0.40.1'
+
+        build ":tomcat:7.0.47"
     }
 }

+ 49 - 42
grails/hello/grails-app/conf/Config.groovy

@@ -12,21 +12,26 @@
 
 
 grails.project.groupId = appName // change this to alter the default package name and Maven publishing destination
-grails.mime.file.extensions = true // enables the parsing of file extensions from URLs into the request format
-grails.mime.use.accept.header = false
-grails.mime.types = [ html: ['text/html','application/xhtml+xml'],
-                      xml: ['text/xml', 'application/xml'],
-                      text: 'text/plain',
-                      js: 'text/javascript',
-                      rss: 'application/rss+xml',
-                      atom: 'application/atom+xml',
-                      css: 'text/css',
-                      csv: 'text/csv',
-                      all: '*/*',
-                      json: ['application/json','text/json'],
-                      form: 'application/x-www-form-urlencoded',
-                      multipartForm: 'multipart/form-data'
-                    ]
+
+grails.app.context = '/grails'
+
+// The ACCEPT header will not be used for content negotiation for user agents containing the following strings (defaults to the 4 major rendering engines)
+grails.mime.disable.accept.header.userAgents = ['Gecko', 'WebKit', 'Presto', 'Trident']
+grails.mime.types = [
+    all:           '*/*',
+    atom:          'application/atom+xml',
+    css:           'text/css',
+    csv:           'text/csv',
+    form:          'application/x-www-form-urlencoded',
+    html:          ['text/html','application/xhtml+xml'],
+    js:            'text/javascript',
+    json:          ['application/json', 'text/json'],
+    multipartForm: 'multipart/form-data',
+    rss:           'application/rss+xml',
+    text:          'text/plain',
+    hal:           ['application/hal+json','application/hal+xml'],
+    xml:           ['text/xml', 'application/xml']
+]
 
 // URL Mapping Cache Max Size, defaults to 5000
 //grails.urlmapping.cache.maxsize = 1000
@@ -34,10 +39,33 @@ grails.mime.types = [ html: ['text/html','application/xhtml+xml'],
 // What URL patterns should be processed by the resources plugin
 grails.resources.adhoc.patterns = ['/images/*', '/css/*', '/js/*', '/plugins/*']
 
+// Legacy setting for codec used to encode data with ${}
+grails.views.default.codec = "html"
+
+// The default scope for controllers. May be prototype, session or singleton.
+// If unspecified, controllers are prototype scoped.
+grails.controllers.defaultScope = 'singleton'
 
-// The default codec used to encode data with ${}
-grails.views.default.codec = "none" // none, html, base64
-grails.views.gsp.encoding = "UTF-8"
+// GSP settings
+grails {
+    views {
+        gsp {
+            encoding = 'UTF-8'
+            htmlcodec = 'xml' // use xml escaping instead of HTML4 escaping
+            codecs {
+                expression = 'html' // escapes values inside ${}
+                scriptlet = 'html' // escapes output from scriptlets in GSPs
+                taglib = 'none' // escapes output from taglibs
+                staticparts = 'none' // escapes output from static template parts
+            }
+        }
+        // escapes all not-encoded output at final stage of outputting
+        filteringCodecForContentType {
+            //'text/html' = 'html'
+        }
+    }
+}
+ 
 grails.converters.encoding = "UTF-8"
 // enable Sitemesh preprocessing of GSP pages
 grails.views.gsp.sitemesh.preprocess = true
@@ -59,6 +87,9 @@ grails.exceptionresolver.params.exclude = ['password']
 // disabling query cache
 grails.hibernate.cache.queries = false
 
+// OSIV is readonly by default
+grails.hibernate.osiv.readonly = true
+
 // set per-environment serverURL stem for creating absolute links
 environments {
     development {
@@ -91,27 +122,3 @@ log4j = {
            'org.hibernate',
            'net.sf.ehcache.hibernate'
 }
-
-// Uncomment and edit the following lines to start using Grails encoding & escaping improvements
-
-/* remove this line 
-// GSP settings
-grails {
-    views {
-        gsp {
-            encoding = 'UTF-8'
-            htmlcodec = 'xml' // use xml escaping instead of HTML4 escaping
-            codecs {
-                expression = 'html' // escapes values inside null
-                scriptlet = 'none' // escapes output from scriptlets in GSPs
-                taglib = 'none' // escapes output from taglibs
-                staticparts = 'none' // escapes output from static template parts
-            }
-        }
-        // escapes all not-encoded output at final stage of outputting
-        filteringCodecForContentType {
-            //'text/html' = 'html'
-        }
-    }
-}
-remove this line */

+ 31 - 3
grails/hello/grails-app/conf/DataSource.groovy

@@ -1,25 +1,53 @@
+import org.springframework.beans.factory.config.PropertiesFactoryBean
+import org.springframework.core.io.support.ResourceArrayPropertyEditor
+
 dataSource {
     pooled = true
     dbCreate = "update"
-    url = "jdbc:mysql://localhost:3306/hello_world?jdbcCompliantTruncation=false&elideSetAutoCommits=true&useLocalSessionState=true&cachePrepStmts=true&cacheCallableStmts=true&alwaysSendSetIsolation=false&prepStmtCacheSize=4096&cacheServerConfiguration=true&prepStmtCacheSqlLimit=2048&zeroDateTimeBehavior=convertToNull&traceProtocol=false&useUnbufferedInput=false&useReadAheadInput=false&maintainTimeStats=false&useServerPrepStmts&cacheRSMetadata=true"
+    url = "jdbc:mysql://localhost:3306/hello_world"
     driverClassName = "com.mysql.jdbc.Driver"
     dialect = org.hibernate.dialect.MySQL5InnoDBDialect
     username = "benchmarkdbuser"
     password = "benchmarkdbpass"
     properties {
-        maxActive = 256
+        fairQueue = false
+        maxActive = 512
         maxIdle = 25
         minIdle = 5
         initialSize = 5
         minEvictableIdleTimeMillis = 60000
         timeBetweenEvictionRunsMillis = 60000
         maxWait = 10000
-        validationQuery = "/* ping */"
+        maxAge = 1800 * 1000
+        numTestsPerEvictionRun=3
+        testOnBorrow=false
+        testWhileIdle=true
+        testOnReturn=false
+        validationQuery="/* ping */"
+        validationInterval=15000
+        jdbcInterceptors="ConnectionState;StatementCache"
+        defaultTransactionIsolation = java.sql.Connection.TRANSACTION_READ_UNCOMMITTED
+        dbProperties = this.loadProperties("classpath:mysql-connection.properties")
     }
 }
+
 hibernate {
     // Purposely turning off query cache
     cache.use_second_level_cache = false
     cache.use_query_cache = false
     cache.region.factory_class = 'net.sf.ehcache.hibernate.EhCacheRegionFactory'
+    default_batch_fetch_size=256
+    jdbc.fetch_size=256
+    jdbc.batch_size=500
 }
+
+static Properties loadProperties(String path) {
+    PropertiesFactoryBean pfb=new PropertiesFactoryBean()
+    pfb.setIgnoreResourceNotFound(true)
+    def converter=new ResourceArrayPropertyEditor()
+    converter.setAsText(path)
+    pfb.setLocations(converter.getValue())
+    pfb.afterPropertiesSet()
+    return pfb.object
+}
+

+ 18 - 0
grails/hello/grails-app/conf/JsonBootStrap.groovy

@@ -0,0 +1,18 @@
+import org.codehaus.groovy.grails.web.converters.Converter
+import org.codehaus.groovy.grails.web.json.JSONWriter
+import grails.converters.JSON
+import hello.World
+
[email protected]
+class JsonBootStrap {
+    def init = { servletContext ->
+        JSON.registerObjectMarshaller(World, { World world, Converter converter ->
+            JSONWriter writer = (JSONWriter)converter.writer
+            writer.object()
+            writer.key('id').value(world.id)
+            writer.key('randomNumber').value(world.randomNumber)
+            writer.endObject()
+            null
+        })
+    }
+}

+ 19 - 0
grails/hello/grails-app/conf/JsonWorkaroundBootStrap.groovy

@@ -0,0 +1,19 @@
+import org.codehaus.groovy.grails.web.json.JSONObject
+import org.springframework.util.ClassUtils
+import org.springframework.util.ReflectionUtils
+
+class JsonWorkaroundBootStrap {
+    def init = { servletContext ->
+        // activate workaround for GRAILS-10823
+        println("activating workaround for GRAILS-10823 - use this only for Grails 2.3.3")
+        def encoderInstance = ClassUtils.forName("org.codehaus.groovy.grails.plugins.codecs.JSONEncoder", JSONObject.class.getClassLoader()).newInstance()
+        ['javascriptEncoderStateless', 'javascriptEncoder'].each { fieldName ->
+            ReflectionUtils.findField(JSONObject, fieldName).with {
+                accessible = true
+                set(null, encoderInstance)
+            }
+        }
+    }
+    def destroy = {
+    }
+}

+ 1 - 1
grails/hello/grails-app/conf/UrlMappings.groovy

@@ -1,7 +1,7 @@
 class UrlMappings {
 
 	static mappings = {
-		"/$controller/$action?/$id?"{
+        "/$controller/$action?/$id?(.${format})?"{
 			constraints {
 				// apply constraints here
 			}

+ 60 - 0
grails/hello/grails-app/conf/mysql-connection.properties

@@ -0,0 +1,60 @@
+# MYSQL JDBC Connection properties
+# http://dev.mysql.com/doc/refman/5.5/en/connector-j-reference-configuration-properties.html
+jdbcCompliantTruncation=false
+autoReconnect=false
+zeroDateTimeBehavior=convertToNull
+
+# disable unicode handling
+useUnicode=false
+
+# Get rid of SQL exceptions thrown in Mysql jdbc driver
+holdResultsOpenOverStatementClose=true
+
+# charset
+#useUnicode=true
+#characterEncoding=Cp1252
+
+#dumpQueriesOnException=true
+
+dontTrackOpenResources=true
+
+#useCompression=true
+#tcpRcvBuf=262144
+#tcpSndBuf=262144
+
+# setAutocommit / setIsolationLevel optimizations
+useLocalSessionState=true
+useLocalTransactionState=true
+elideSetAutoCommits=true
+alwaysSendSetIsolation=false
+relaxAutoCommit=true
+
+# metadata caching
+cacheServerConfiguration=true
+cacheResultSetMetadata=true
+metadataCacheSize=100
+
+# enable MySQL query cache - server prep stmts wont use caching
+useServerPrepStmts=false
+
+# prepared statement caching on JDBC client
+cachePrepStmts=false
+cacheCallableStmts=false
+prepStmtCacheSize=4096
+prepStmtCacheSqlLimit=32000
+
+# log slow queries
+#logSlowQueries=true
+#slowQueryThresholdMillis=2000
+
+noDatetimeStringSync=true
+enableQueryTimeouts=false
+
+traceProtocol=false
+useUnbufferedInput=true
+useReadAheadInput=true
+maintainTimeStats=false
+
+# timeouts for TCP/IP
+connectTimeout=15000
+socketTimeout=120000

+ 85 - 16
grails/hello/grails-app/controllers/hello/HelloController.groovy

@@ -1,32 +1,101 @@
 package hello
 
 import grails.converters.JSON
+import grails.transaction.Transactional
+import groovy.transform.CompileStatic
+import groovy.transform.TypeCheckingMode;
+
 import java.util.concurrent.ThreadLocalRandom
 
+import org.springframework.transaction.annotation.Isolation;
+
+@CompileStatic
 class HelloController {
 
     def index() {
-      def response = [
-        message: "Hello, world"
-      ]
-      render response as JSON
+        plaintext()
+    }
+
+    // benchmark specification
+    // http://www.techempower.com/benchmarks/#section=code
+    
+    // Test type 1: JSON serialization
+    def json() {
+        def msg = [
+            message: "Hello, world"
+        ]
+        render msg as JSON
     }
 
+    // Test type 2: Single database query
+    @Transactional(readOnly=true)
     def db() {
-      int queries = params.queries ? params.int('queries') : 1
-      def worlds = new ArrayList(queries)
-      def random = ThreadLocalRandom.current();
+        def random = ThreadLocalRandom.current()
+        def world = World.read(random.nextInt(10000) + 1)
+        render world as JSON
+    }
+    
+    // Test type 3: Multiple database queries
+    @Transactional(readOnly=true)
+    def queries(int queries) {
+        def worlds = fetchRandomWorlds(queries, false)
+        render worlds as JSON
+    }
+
+    private List<World> fetchRandomWorlds(int queries, boolean updateAlso) {
+        if(queries < 1) queries=1
+        if(queries > 500) queries=500
+        def random = ThreadLocalRandom.current()
 
-      for (int i = 0; i < queries; i++) {
-        worlds.add(World.read(random.nextInt(10000) + 1));
-      }
-      render worlds as JSON
+        List<Integer> worldIds = new ArrayList<Integer>(queries)
+        for (int i = 0; i < queries; i++) {
+            worldIds.add(random.nextInt(10000) + 1)
+        }
+        List<World> worlds
+        if (updateAlso) {
+            worlds = getAllLocked(worldIds as Serializable[])
+            for (World world : worlds) {
+                world.randomNumber = random.nextInt(10000) + 1
+            }
+        } else {
+            worlds = new ArrayList<World>(queries)
+            for (Integer id : worldIds) {
+                worlds.add(World.read(id))
+            }
+        }
+        return worlds
     }
     
-    def json() {
-      def response = [
-        message: "Hello, world"
-      ]
-      render response as JSON
+    @CompileStatic(TypeCheckingMode.SKIP)
+    private List<World> getAllLocked(Serializable[] worldIds) {
+        World.withCriteria {
+            'in'('id', worldIds as Serializable[])
+            lock true
+        }
+    }
+    
+    // Test type 4: Fortunes
+    @Transactional(readOnly=true)
+    def fortunes() {
+        def fortunes = Fortune.getAll()
+        fortunes << new Fortune(message: 'Additional fortune added at request time.')
+        fortunes.sort(true){Fortune it -> it.message}
+        [fortunes: fortunes]
+    }
+    
+    // Test type 5: Database updates
+    def updates(int queries) {
+        def worlds = updateWorlds(queries)
+        render worlds as JSON
+    }
+
+    @Transactional(isolation=Isolation.READ_COMMITTED)
+    private List updateWorlds(int queries) {
+        fetchRandomWorlds(queries, true)
+    }
+    
+    // Test type 6: Plaintext
+    def plaintext() {
+        render text:'Hello, world', contentType:'text/plain'
     }
 }

+ 14 - 0
grails/hello/grails-app/domain/hello/Fortune.groovy

@@ -0,0 +1,14 @@
+package hello
+
+class Fortune {
+    Integer id
+    String message
+
+    static constraints = {
+    }
+
+    static mapping = {
+      table name: 'Fortune'
+      version false
+    }
+}

+ 2 - 0
grails/hello/grails-app/domain/hello/World.groovy

@@ -1,12 +1,14 @@
 package hello
 
 class World {
+    Integer id
     Integer randomNumber
 
     static constraints = {
     }
 
     static mapping = {
+      table name: 'World'
       version false
       columns {
         randomNumber     column:"randomNumber"

+ 12 - 5
grails/hello/grails-app/views/error.gsp

@@ -1,11 +1,18 @@
-<!doctype html>
+<!DOCTYPE html>
 <html>
 	<head>
-		<title>Grails Runtime Exception</title>
+		<title><g:if env="development">Grails Runtime Exception</g:if><g:else>Error</g:else></title>
 		<meta name="layout" content="main">
-		<link rel="stylesheet" href="${resource(dir: 'css', file: 'errors.css')}" type="text/css">
+		<g:if env="development"><link rel="stylesheet" href="${resource(dir: 'css', file: 'errors.css')}" type="text/css"></g:if>
 	</head>
 	<body>
-		<g:renderException exception="${exception}" />
+		<g:if env="development">
+			<g:renderException exception="${exception}" />
+		</g:if>
+		<g:else>
+			<ul class="errors">
+				<li>An error has occurred</li>
+			</ul>
+		</g:else>
 	</body>
-</html>
+</html>

+ 10 - 0
grails/hello/grails-app/views/hello/fortunes.gsp

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head><title>Fortunes</title></head>
+<body>
+<table>
+<tr><th>id</th><th>message</th></tr>
+<g:each var="fortune" in="${fortunes}"><tr><td>${fortune.id}</td><td>${fortune.message}</td></tr></g:each>
+</table>
+</body>
+</html>

+ 3 - 3
grails/hello/grails-app/views/index.gsp

@@ -1,4 +1,4 @@
-<!doctype html>
+<!DOCTYPE html>
 <html>
 	<head>
 		<meta name="layout" content="main"/>
@@ -29,7 +29,7 @@
 				margin-bottom: 0.6em;
 				padding: 0;
 			}
-            
+
 			#status li {
 				line-height: 1.3;
 			}
@@ -87,7 +87,7 @@
 			<ul>
 				<li>App version: <g:meta name="app.version"/></li>
 				<li>Grails version: <g:meta name="app.grails.version"/></li>
-				<li>Groovy version: ${org.codehaus.groovy.runtime.InvokerHelper.getVersion()}</li>
+				<li>Groovy version: ${GroovySystem.getVersion()}</li>
 				<li>JVM version: ${System.getProperty('java.version')}</li>
 				<li>Reloading active: ${grails.util.Environment.reloadingAgentEnabled}</li>
 				<li>Controllers: ${grailsApplication.controllerClasses.size()}</li>

+ 85 - 0
grails/hello/src/groovy/org/codehaus/groovy/grails/plugins/codecs/JSONEncoder.java

@@ -0,0 +1,85 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.codehaus.groovy.grails.plugins.codecs;
+
+import org.apache.commons.lang.StringUtils;
+import org.codehaus.groovy.grails.support.encoding.CodecIdentifier;
+import org.codehaus.groovy.grails.support.encoding.DefaultCodecIdentifier;
+
+/**
+ * Escapes characters in JSON output
+ *
+ * @author Lari Hotari
+ * @since 2.3.4
+ */
+public class JSONEncoder extends AbstractCharReplacementEncoder {
+    public static final CodecIdentifier JSON_CODEC_IDENTIFIER = new DefaultCodecIdentifier(
+            "JSON", "Json") {
+        public boolean isEquivalent(CodecIdentifier other) {
+            return super.isEquivalent(other) || JavaScriptEncoder.JAVASCRIPT_CODEC_IDENTIFIER.getCodecName().equals(other.getCodecName());
+        };
+    };
+
+    public JSONEncoder() {
+        super(JSON_CODEC_IDENTIFIER);
+    }
+
+    /* (non-Javadoc)
+     * @see org.codehaus.groovy.grails.plugins.codecs.AbstractCharReplacementEncoder#escapeCharacter(char, char)
+     */
+    @Override
+    protected String escapeCharacter(char ch, char previousChar) {
+        switch (ch) {
+            case '"':
+                return "\\\"";
+            case '\\':
+                return "\\\\";
+            case '\t':
+                return "\\t";
+            case '\n':
+                return "\\n";
+            case '\r':
+                return "\\r";
+            case '\f':
+                return "\\f";
+            case '\b':
+                return "\\b";
+            case '\u000B': // vertical tab: http://bclary.com/2004/11/07/#a-7.8.4
+                return "\\v";
+            case '\u2028':
+                return "\\u2028"; // Line separator
+            case '\u2029':
+                return "\\u2029"; // Paragraph separator
+            case '/':
+                // preserve special handling that exists in JSONObject.quote to improve security if JSON is embedded in HTML document
+                // prevents outputting "</" gets outputted with unicode escaping for the slash
+                if (previousChar == '<') {
+                    return "\\u002f"; 
+                }
+                break;
+        }
+        if(ch < ' ') {
+            // escape all other control characters
+            return "\\u" + StringUtils.leftPad(Integer.toHexString(ch), 4, '0');
+        }
+        return null;
+    }
+
+    @Override
+    public boolean isApplyToSafelyEncoded() {
+        return true;
+    }
+}

+ 8 - 0
grails/hello/web-app/WEB-INF/sitemesh-excludes.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<sitemesh-excludes>
+  <excludes>
+      <pattern>/hello/*</pattern>
+      <pattern>/hello</pattern>
+  </excludes>
+</sitemesh-excludes>
+

+ 2 - 1
grails/hello/web-app/WEB-INF/sitemesh.xml

@@ -1,4 +1,5 @@
 <sitemesh>
+    <excludes file="/WEB-INF/sitemesh-excludes.xml" />
     <page-parsers>
         <parser content-type="text/html"
             class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser" />
@@ -11,4 +12,4 @@
     <decorator-mappers>
         <mapper class="org.codehaus.groovy.grails.web.sitemesh.GrailsLayoutDecoratorMapper" />
     </decorator-mappers>
-</sitemesh>
+</sitemesh>

+ 3 - 3
grails/setup.py

@@ -7,8 +7,8 @@ def start(args, logfile, errfile):
   setup_util.replace_text("grails/hello/grails-app/conf/DataSource.groovy", "jdbc:mysql:\/\/.*:3306", "jdbc:mysql://" + args.database_host + ":3306")
   
   try:
-    subprocess.check_call("grails install-dependency mysql:mysql-connector-java:5.1.22", shell=True, cwd="grails/hello", stderr=errfile, stdout=logfile)
-    subprocess.check_call("grails war", shell=True, cwd="grails/hello", stderr=errfile, stdout=logfile)
+    subprocess.check_call("grails -non-interactive -plain-output compile", shell=True, cwd="grails/hello", stderr=errfile, stdout=logfile)
+    subprocess.check_call("grails prod -non-interactive -plain-output war", shell=True, cwd="grails/hello", stderr=errfile, stdout=logfile)
     subprocess.check_call("rm -rf $RESIN_HOME/webapps/*", shell=True, stderr=errfile, stdout=logfile)
     subprocess.check_call("cp grails/hello/target/hello-0.1.war $RESIN_HOME/webapps/grails.war", shell=True, stderr=errfile, stdout=logfile)
     subprocess.check_call("$RESIN_HOME/bin/resinctl start", shell=True, stderr=errfile, stdout=logfile)
@@ -21,4 +21,4 @@ def stop(logfile, errfile):
     subprocess.check_call("rm -rf $RESIN_HOME/resin-data/*", shell=True, stderr=errfile, stdout=logfile)
     return 0
   except subprocess.CalledProcessError:
-    return 1
+    return 1

+ 3 - 3
toolset/setup/linux/installer.py

@@ -248,9 +248,9 @@ class Installer:
     #
     # Grails
     #
-    self.__download("http://dist.springframework.org.s3.amazonaws.com/release/GRAILS/grails-2.3.1.zip")
-    self.__run_command("unzip -o grails-2.3.1.zip")
-    self.__run_command("rm grails-2.3.1.zip")
+    self.__download("http://dist.springframework.org.s3.amazonaws.com/release/GRAILS/grails-2.3.3.zip")
+    self.__run_command("unzip -o grails-2.3.3.zip")
+    self.__run_command("rm grails-2.3.3.zip")
 
     #
     # Play 2