20 Aug 2014 by Ben

There are some gotchas with running Grails in a shared Tomcat environment.

I run this website on a host that shares the Tomcat instance among a number of clients. To do this, the instance uses Tomcat’s Security Manager to protect accounts and the server in general.

That causes some side-effects when uploading a Grails app.

For example, just recently I uploaded a new version of this site and got a number of access denied errors in my logs. Most were easily fixed, but the most perplexing was this one:

script14079441961801874947311.groovy: 8: Fatal error occurred applying query transformations [ access denied ("java.io.FilePermission" "/usr/local/tomcat/TomcatSandbox/Box/script14079441961801874947311.groovy" "read")] to source [script14079441961801874947311.groovy]. Please report an issue.
java.security.AccessControlException: access denied ("java.io.FilePermission" "/usr/local/tomcat/TomcatSandbox/Box/script14079441961801874947311.groovy" "read")
    at java.security.AccessControlContext.checkPermission(AccessControlContext.java:372)
    at java.security.AccessController.checkPermission(AccessController.java:559)
    at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
    at java.lang.SecurityManager.checkRead(SecurityManager.java:888)
    at java.io.File.isDirectory(File.java:838)
    at java.io.File.toURI(File.java:732)
    at org.grails.datastore.gorm.query.transform.DetachedCriteriaTransformer.isDomainClass(DetachedCriteriaTransformer.java:1316)
    at org.grails.datastore.gorm.query.transform.DetachedCriteriaTransformer.visitField(DetachedCriteriaTransformer.java:196)
    at org.codehaus.groovy.ast.ClassNode.visitContents(ClassNode.java:1055)
    at org.codehaus.groovy.ast.ClassCodeVisitorSupport.visitClass(ClassCodeVisitorSupport.java:50)
    at org.grails.datastore.gorm.query.transform.DetachedCriteriaTransformer.visitClass(DetachedCriteriaTransformer.java:174)
    at org.grails.datastore.gorm.query.transform.GlobalDetachedCriteriaASTTransformation.visit(GlobalDetachedCriteriaASTTransformation.java:51)
    at org.codehaus.groovy.transform.ASTTransformationVisitor$3.call(ASTTransformationVisitor.java:319)
    at org.codehaus.groovy.control.CompilationUnit.applyToSourceUnits(CompilationUnit.java:923)
    at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:585)
    at org.codehaus.groovy.control.CompilationUnit.processPhaseOperations(CompilationUnit.java:561)
    at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:538)
    at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:286)
    at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:259)
    at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:245)
    at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:203)
    at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:213)
    at groovy.lang.GroovyClassLoader$parseClass$0.call(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
    at ScaffoldingGrailsPlugin.createScaffoldedInstance(ScaffoldingGrailsPlugin.groovy:184)
    at ScaffoldingGrailsPlugin.this$2$createScaffoldedInstance(ScaffoldingGrailsPlugin.groovy)
    at ScaffoldingGrailsPlugin$this$2$createScaffoldedInstance$2.callCurrent(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:49)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:145)
    at ScaffoldingGrailsPlugin.configureScaffoldingController(ScaffoldingGrailsPlugin.groovy:139)
    at ScaffoldingGrailsPlugin.this$2$configureScaffoldingController(ScaffoldingGrailsPlugin.groovy)
    at ScaffoldingGrailsPlugin$this$2$configureScaffoldingController.callCurrent(Unknown Source)
    at ScaffoldingGrailsPlugin.configureScaffolding(ScaffoldingGrailsPlugin.groovy:114)
    at ScaffoldingGrailsPlugin.this$2$configureScaffolding(ScaffoldingGrailsPlugin.groovy)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

Very little to go on here, as I had set no configuration that directed Grails to write files into /usr/local/tomcat/TomcatSandbox. This is apparently the current working directory on shared Tomcat VMs and thus something in my app was writing a file without a path.

The stack trace points to the Scaffolding plugin, which can be made to automatically generate some of the app’s pages, which means that this plugin is probably the source of the problem. I had forgotten that a number of my app’s pages still used dynamic scaffolding! The use of this feature is generally recommended against in production environments because it requires extra processing and can slow down the site.

Several colleagues with more Grails experience than me (hi Dave, Nick) alerted me to a couple of ways to deal with this.

Try and move the directory used to write the generated files

Attempt to convince Grails to write the dynamically-generated files in a location that my account is permitted to, e.g. somewhere under my account’s home directory. This might be accomplished by starting Tomcat with -Dgroovy.target.directory=/home/directory, but that in itself might be a problem in a shared Tomcat instance. An alternative would be to try and use the environment closure in Config.groovy:

environments {
    development {
        // dev configuration
    }
    production {
        System.setProperty("groovy.target.directory", "/home/directory")
    }
}

or

environments {
    development {
        // dev configuration
    }
    production {
        groovy.target.directory = "/home/directory"
    }
}

if (groovy.target.directory) {
    System.setProperty("groovy.target.directory", groovy.target.directory)
}

Rebuild the project with static scaffolding

This should avoid the problem, because no files will be generated on the production server. To do this, you run, for example:

$ grails generate-views DomainClass

 and repeat for each controller and view set that is using dynamic scaffolding. Then merge in any changes you had made to the controller or view and you should be done.

Summary

Of the two, the first would be quicker, if you had a desperate need to get the site running and the server hit from dynamic scaffolding wasn’t an issue. The second would be the better approach, because it avoids the problem completely and might be beneficial for busy sites.