This approach will not work for the Tomcat plugin included in Grails 2.0.4 (and presumably all Grails 2 releases). I have written an updated approach for that.
We have a number of environment variables in our web applications that control how our applications behave. For instance, there is a variable that is displayed at the top of the pages (outside of production) that identifies what instance you are in.
When we deploy our war file into a Tomcat instance this is controlled by the conf/context.xml file like so.
<Environment name="appServerInstance" value="My server" type="java.lang.String" override="false"/>
In Grails applications using Jetty (the Jetty plugin) this is easy to control in the web-app/WEB-INF/jetty.xml file like so.
<Configure>
...
<New id="appServerInstance" class="org.mortbay.jetty.plus.naming.EnvEntry">
<Arg></Arg>
<Arg type="java.lang.String">appServerInstance</Arg>
<Arg type="java.lang.String">My server</Arg>
<Arg type="boolean">true</Arg>
</New>
...
</Configure>
Now that Grails uses Tomcat I have been looking for a way to control this in development mode – when using grails run-app
from the command line.
The plugin documents a way of handling resource tags using grails.naming.entries in Config.groovy. After repeated attempts and some debugging it became clear that it would not handle environment tags. It is not too difficult to modify the plugin to accommodate environment (env-entry) tags however.
Cutting to the chase, JNDI resources are represented by instances of org.apache.catalina.deploy.ContextResource in the Tomcat naming resources, and these are handled by the plugin. JNDI environments are represented by instances of org.apache.catalina.deploy.ContextEnvironment, and these are not handled by the plugin.
Within the plugin directory (./plugins/tomcat-1.3.5 in my case) under directory src/groovy/org/grails/tomcat you will find TomcatServer.groovy. This is the class that gets things rolling, and the preStart() method is where the grails.naming.entries property is processed. It looked like this when I started.
private preStart() {
eventListener?.event("ConfigureTomcat", [tomcat])
def jndiEntries = grailsConfig?.grails?.naming?.entries
if(jndiEntries instanceof Map) {
jndiEntries.each { name, resCfg ->
if(resCfg) {
if (!resCfg["type"]) {
throw new IllegalArgumentException("Must supply a resource type for JNDI configuration")
}
def res = loadInstance('org.apache.catalina.deploy.ContextResource')
res.name = name
res.type = resCfg.remove("type")
res.auth = resCfg.remove("auth")
res.description = resCfg.remove("description")
res.scope = resCfg.remove("scope")
// now it's only the custom properties left in the Map...
resCfg.each {key, value ->
res.setProperty (key, value)
}
context.namingResources.addResource res
}
}
}
}
It expects a Map that looks something like this.
grails.naming.entries = [
"jdbc/ArtDB": [
type: "javax.sql.DataSource",
auth: "Container",
description: "Art data source",
url: "jdbc:db2://myserver:12345/MyDB",
username: "auser",
password: "apassword",
driverClassName: "com.ibm.db2.jcc.DB2Driver"
],
"jms/bananas": [
type: "org.apache.activemq.command.ActiveMQTopic",
description: "Fruit salad",
factory: "org.apache.activemq.jndi.JNDIReferenceFactory",
physicalName: "bananas"
]
]
My thought was that, since there are multiple types of naming resources each needing to be treated differently, why not support maps by type? So my propsed Config.groovy might contain this.
grails.naming.entries = [
"resources" : [
"jdbc/ArtDB": [
type: "javax.sql.DataSource",
auth: "Container",
description: "Art data source",
url: "jdbc:db2://myserver:12345/MyDB",
username: "auser",
password: "apassword",
driverClassName: "com.ibm.db2.jcc.DB2Driver"
],
"jms/bananas": [
type: "org.apache.activemq.command.ActiveMQTopic",
description: "Fruit salad",
factory: "org.apache.activemq.jndi.JNDIReferenceFactory",
physicalName: "bananas"
]
],
“environments” : [
“appServerInstance”: [
type: “java.lang.String”,
value: “My server”,
override: “false”
]
]
]
I thought it would be nice to support the original style that considers everything a JNDI resource too, and I was able to accomplish this as long as none of them are named the same as my expected resource type names (“resources” and “environments” in my implementation). After figuring out what was needed, modifying the code was not a big deal. Here is what I ended up with as a replacement for the old preStart method.
private preStart() {
eventListener?.event("ConfigureTomcat", [tomcat])
def jndiEntries = grailsConfig?.grails?.naming?.entries
if(jndiEntries instanceof Map) {
def envs = jndiEntries.remove('environments')
if (envs instanceof Map) {
envs.each { name, cfg ->
if (cfg) {
addEnvironment(name, cfg)
}
}
}
def ress = jndiEntries.remove('resources')
if (ress instanceof Map) {
ress.each { name, cfg ->
if (cfg) {
addResource(name, cfg)
}
}
}
jndiEntries.each { name, cfg ->
if(cfg) {
addResource(name, cfg)
}
}
}
}
private addEnvironment(name, envCfg) {
if (!envCfg[“type”]) {
throw new IllegalArgumentException(“Must supply a environment type for JNDI configuration”)
}
def env = loadInstance(‘org.apache.catalina.deploy.ContextEnvironment’)
env.name = name
env.type = envCfg.type
env.description = envCfg.description
env.override = envCfg.override as boolean
env.value = envCfg.value
context.namingResources.addEnvironment env
}
private addResource(name, resCfg) {
if (!resCfg[“type”]) {
throw new IllegalArgumentException(“Must supply a resource type for JNDI configuration”)
}
def res = loadInstance(‘org.apache.catalina.deploy.ContextResource’)
res.name = name
res.auth = resCfg.remove(“auth”)
res.scope = resCfg.remove(“scope”)
res.type = resCfg.remove(“type”)
res.description = resCfg.remove(“description”)
// now it’s only the custom properties left in the Map…
resCfg.each {key, value ->
res.setProperty (key, value)
}
context.namingResources.addResource res
}
There is room for improvement (it could be DRYer for sure) but it works and I have already spent too much time figuring this much out. I hope someone else finds this useful.
It was surprising to me that no one seemed to be missing this capability and that there was no readily available help out there regarding it.