Thursday, December 17, 2009

Run a Java web application within grails

Ever needed to run an existing java web application side by side with your grails project? I found myself in exactly that position this week, and discovered that it (like most things in grails) was rather simple to accomplish. Considering I work in a predominately Java environment, I often need to run a java web application side by side with my grails work. To date, I would simply run grails in the embedded tomcat engine (or Jetty in grails <1.2) bundled with grails, and use a local install of tomcat to power my java web apps.

I wanted to see if I could streamline the environment, and have my locally installed tomcat run my grails application. It is possible to use the excellent tomcat plugin to export a WAR of your project to an external tomcat instance via tomcat's manager application. The problem with this configuration is that you lose the "hot-deploy" features of grails which was a deal breaker for me.

After quite a bit of head scratching, googling, and yelling at my computer, I finally got pointed in the right direction from Graeme Rocher via the grails-user mailing list. If you are not aware, grails uses tomcat's embedded version under the hood as of 1.2. I was aware that grails emits numerous lifecycle events that you can hook into, but was unaware that the tomcat plugin uses this system to announce a "TomcatConfigured" event that exposes the underlying tomcat API. During startup of your grails project, the tomcat plugin will configure the embedded engine, and then announce the "TomcatConfigured" event, and pass along the newly configured tomcat. This can be used to manage and configure every aspect of the tomcat engine.

My current implementation supports only one specific web application. In the future, I plan on updating this code to support multiple applications which will be configurable via Config.groovy, which should be rather trivial.

Begin by adding your project specific configuration to Config.groovy:

grails.myproject.contextRoot = "/myproject"
grails.myproject.build.path = "/myproject/WEB-INF/classes"
grails.myproject.web.root = "/myproject/"

Next we need to add the event handler to our Grails application. This is done by simply creating a new file named _Events.groovy and placing it in the /scripts directory of your project.

import org.apache.catalina.*
import org.apache.catalina.connector.*
import org.apache.catalina.loader.WebappLoader
import org.codehaus.groovy.grails.commons.ConfigurationHolder

eventConfigureTomcat = {tomcat ->
println "### Starting load of custom application"
def contextRoot = ConfigurationHolder.config.grails.myproject.contextRoot
def buildroot= ConfigurationHolder.config.grails.myproject.build.path
def webroot  = ConfigurationHolder.config.grails.myproject.web.root

File appDir = new File(webroot);
context = tomcat.addWebapp(contextRoot, appDir.getAbsolutePath());
context.reloadable = true

WebappLoader loader = new WebappLoader(tomcat.class.classLoader)

loader.addRepository(new File(buildroot).toURI().toURL().toString());
context.loader = loader
loader.container = context

println "### Ending load of custom application"
}

You can now start you application with the grails run-app command, and your java web application will be available under the context you set in Config.groovy.

As a side note, if you need to support having an apache web server sit in fron of your application, you can add the following to turn on AJP connections for the embedded tomcat container.

// enable AJP to allow apache to front tomcat
def ajpConnector = new Connector("org.apache.jk.server.JkCoyoteHandler")
ajpConnector.port = 8009
ajpConnector.setProperty("redirectPort", "8443")
ajpConnector.setProperty("protocol", "AJP/1.3")
ajpConnector.setProperty("enableLookups", "false")

tomcat.service.addConnector ajpConnector

8 comments:

  1. This is very useful. Thanks for sharing.

    Any thoughts about contributing this as a Grails plugin?

    ReplyDelete
  2. Very helpful! Thanks for taking the time to post this! It's saved me a huge headache and now I can do grails ajax w/o the Firefox XSS issues trying to talk between two tomcats on different ports.

    ReplyDelete
  3. Thanks for sharing thi!s One question left to me, where do you put the code regarding AJP connections?

    ReplyDelete
  4. @Anonymous:
    Add the code to _Events.groovy. In my config, I have it right after the "context.reloadable = true" line.

    ReplyDelete
  5. Very helpful especially with 1.2.
    Quick question: I tried to use ajpConnector.setProperty("address", "ip address") because of conflict with jboss port 8009 and 8080 but it doesn't seems to solve port 8080 conflict. Any idea?

    ReplyDelete
  6. @Anonymous:
    You are running into problems because jBoss is using port 8009 for AJP, and 8080 for web communications with it's internal tomcat. Instead of using ajpConnector.setProperty("address", "ipaddress"), I would update ajpConnecto.port = 8009 to a new port. You will need to update the AJP configuration on the apache side to match. To change the port Tomcat is using (defaults to 8080) you can start grails with the -Dserver.port option. For example: grails -Dserver.port=8090 run-app will start the app on port 8090.

    Good luck!

    ReplyDelete
  7. Hello.

    How can I map static path(like c:/images/) to some web url(like localhost:8080/myProj/mappedUrl?

    ReplyDelete
  8. @Anonymous (static resources):
    You will just need to turn on directory listing. This is usually done in /WEB-INF/web.xml, but you will need to check the API doc's for Tomcat to see how to programmatically set this value, as I'm not sure how to configure the DefaultServlet via the API. See the API at http://tomcat.apache.org/tomcat-6.0-doc/api/index.html

    ReplyDelete