Tuesday, February 12, 2008

Hidden gem: grails.war.resources

In the process of packaging up the Grails application as a WAR in preparation for deployment to Tomcat, I needed to figure out a way to exclude the Oracle JDBC drver from WEB-INF/lib (we use 'shared' Oracle driver for our web apps which gets loaded by the Catalina's common ClassLoader). So I was thinking of a way to provide an "event hook" script, and while reading Grails' War.groovy I found a "hidden gem". That is a closure which is defined in the application's Config.groovy. So when this closure is defined, the War.groovy calls it with "staging directory" as a parameter and sets Ant as its 'delegate' right before zipping it up as a war.

So I was able to do the following (... from Config.groovy):


//Closure to customize the packaging of a war. In particular it excludes the Oracle JDBC driver
//from the war as it is loaded by the Catalina common ClassLoader when deployed to Tomcat
grails.war.resources = {stagingDir ->
delete(file: "$stagingDir/WEB-INF/lib/ojdbc14-10.2.jar")
}

This is real nice, but I don't think this feature is documented anywhere.

Later...

Monday, February 11, 2008

Script event hooks are cool

Grails has had the so called 'event hooks' for Gant scripts since 0.5. It basically is a way to tap into the Grails Gant scripts execution flow to customize build process, etc. It is elegantly implemented (as everything else in Grails) as a collection of closures in the Events.groovy script. The 'event closures' get called by Grails when the corresponding 'events' get generated in the Gant script e.g. a method 'event' is called with the event name as a parameter. So for example, event("StatusUpdate", [ "Status update event is called"]) would result in eventStatusUpdate { msg -> } closure defined in Events.groovy being called.

So, at work, for the typical Spring web applications we use Ant-based "common build" system originally created by the Spring guys. The common build uses ivy for the dependency resolution, so we have a custom ivyconf.xml which contains definitions for our local dependency repository, etc.

Grails has an ivy plugin, but when installing it for each web app, it puts the default ivyconf.xml into the root of the web app. So, I needed a way to 'tap' into the _Install.groovy script of the plugin to copy our local invyconf.xml. Sure enough the script 'statusUpdate' hook did the trick.

Here is the Events.groovy:


static final ESS_IVYCONF_FILE_LOCATION = "$userHome/.common-build/ntg/common-build"

eventStatusUpdate = { msg ->
if(msg ==~ /.*ivyconf.xml.*/) {
println "Copying ESS ivyconf.xml from common-build global directory ..."
Ant.copy(todir: "${basedir}", overwrite: true) {
fileset(dir: "$ESS_IVYCONF_FILE_LOCATION") {
include(name: "ivyconf.xml")
}
}
}
}


Later...

Plugins for local needs

The Grails plugin system is great not only for creating all kinds of useful functionality available publicly, but also is a perfect framework to extract bits and pieces of reusable logic suitable only for internal corporate projects.

At our organization we started finally using Grails for internal projects. The first small web application was done in a matter of a few days. But then when it was time to package it up as a 'war', we had a little dilemma and that's where Grails plugin system came to the rescue.

In a nutshell, we use Spring Security (formerly known as ACEGI) together with JA-SIG Central Authentication Service, for all our auth/authz needs. For Grails applications, I did not choose Grails Acegi plugin as it does not seem to provide CAS authentication support (yet?). So we reverted back to good 'ol Spring resources.xml and dropped all the verbose Acegi bean definitions there (hopefully Spring Security 2 should reduce the 'configuration chaos' dramatically). Works just fine. But we have 3 "String" bean definitions there pertaining to CAS properties, such as 'casServiceUrlPrefix', 'casLoginUrl', and 'casValidateUrl'. So, for the typical Spring web app., we define those as JNDI-bound resources, like so:


<bean id="casServiceUrlPrefix" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName"><value>java:comp/env/casServiceUrlPrefix</value></property>
</bean>

<bean id="casLoginUrl" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName"><value>java:comp/env/casLoginUrl</value></property>
</bean>

<bean id="casValidateUrl" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName"><value>java:comp/env/casValidateUrl</value></property>
</bean>
So, when packaging application as a 'war' and deploying it to Tomcat, this is fine, but in the 'development' mode, in embedded Jetty, I would have liked to put this config somewhere else. Like conf/Config.groovy for example. That's where Grails plugin system came in, and with its wonderful SpringBeanBuilder I was able easily dynamically create those 3 bean definitions during assembly phase of the ApplicationContext, depending on the environment the application was running in.

Here's the plugin:


import grails.util.GrailsUtil as GU
import org.springframework.jndi.JndiObjectFactoryBean

class CasPropertiesResolverGrailsPlugin {
def version = 0.1
def author = "Dmitriy Kopylenko"
def title = "This plugin is used to provide cas related properties to the Spring beans used for Spring Security config"
def description = ''' The plugin participates in the creation phase of the Spring application context
which is used to configure Spring Security related beans and dynamically provides
'configuration beans' namely 'casServiceUrlPrefix', 'casLoginUrl', and 'casValidateUrl'
depending on the environment in which Grails application is preparing to run.

More specifically, if the environment is 'development' then the value is sourced from
Grails Config instance. On the other hand, if the environment is 'production', the value
is sourced from standard ess JNDI namespace.
'''

def dependsOn = [:]

def doWithSpring = {
switch (GU.environment) {
case 'development':
casServiceUrlPrefix(java.lang.String, application.config.casServiceUrlPrefix)
casLoginUrl(java.lang.String, application.config.casLoginUrl)
casValidateUrl(java.lang.String, application.config.casValidateUrl)
break
case 'production':
casServiceUrlPrefix(JndiObjectFactoryBean) {
jndiName = 'java:comp/env/casServiceUrlPrefix'
}
casLoginUrl(JndiObjectFactoryBean) {
jndiName = 'java:comp/env/casLoginUrl'
}
casValidateUrl(JndiObjectFactoryBean) {
jndiName = 'java:comp/env/casValidateUrl'
}
break
}
}
...

I'm sure there are other ways to do this, but that was fun nevertheless :-)

Later...

Tuesday, February 5, 2008

Grails 1.0 is golden!

Yes, after more than 2 years in the making, with the development team hard at work, the Grails 1.0 final has been officially released. IMO, this event is historical and a great milestone for "lightweight" web development on the JVM platform.

Happy Grails hacking!

Later...

Friday, February 1, 2008

A year of blogging

Just a small milestone for me: a year of blogging. It hasn't been that bad, and I really enjoyed it. I will certainly continue to do so.

Later...