Grails 1.3.5 Tomcat plugin and JNDI environment variables

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 dsplayed 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.

Grails 1.3.5 log4j DSL

I could find very little in the way of straight forward and informative examples of how to do basic log4j configuration in the Grails Config.groovy file. Since this is not something that I tend to revisit very often it is needlessly involved whenever I do have to go there. This represents my attempt to fill that general need of mine, and perhaps someone else will find it useful too.

The most basic configuration you can have would be this.

log4j = {
}

Let’s fill in what that represents. You get a stdout appender to the console for free. If you were to define it you would have approximately this.

log4j = {
  appenders {
    console name:'stdout', layout:pattern(conversionPattern: '%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %p %c{2} - %m%n')
  }
}

The conversion pattern conforms to org.apache.log4j.PatternLayout. To make this appender a default appender explicitly and log at the “error” (or greater) level you would have something like this.

log4j = {
  appenders {
    console name:'stdout', layout:pattern(conversionPattern: '%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %p %c{2} - %m%n')
  }

  root {
    error 'stdout'
  }
}

So now we have the approximate equivalent of the first example. The next component comes into play when you want a category – by convention the equivalent of a package – treated differently than other stuff. For me, it is when I want to see informational or debugging messages related to some, or possibly all, of my code. Now I can decide to log that at a different level than my default (or root) logger.

log4j = {
  appenders {
    console name:'stdout', layout:pattern(conversionPattern: '%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %p %c{2} - %m%n')
  }

  root {
    error 'stdout'
  }

  debug 'com.mydomain'
}

At this point everything that starts with com.mydomain that is logged at the level of debug or greater will get sent to my default appender, stdout. Of course everything that is at error or greater will be sent to my default appender too, just as before. So what if I want a different appender – not a stdout appender?

Well, we can define stdout to be any appender that we want! Just as we can define the pattern layout that we want, we can define the type of appender that we want.

log4j = {
  appenders {
    rollingFile name:'stdout', maxFileSize:"50MB", maxBackupIndex:100,file:"./logs/${appName}_app.log",{
        layout:pattern(
          conversionPattern: '%d{yyyy-MM-dd HH:mm:ss.SSS} | %p | %c | %t | %m | %x%n'
        )
    }
  }

  root {
    error 'stdout'
  }

  debug 'com.mydomain'
}

Of course we might be better off not calling our appender stdout if we want it to be something considerably different – that convention over configuration thing. How about:

log4j = {
  appenders {
    rollingFile name:'logile', maxFileSize:'50MB', maxBackupIndex:100,file:"./logs/${appName}_app.log",{
        layout:pattern(
          conversionPattern: '%d{yyyy-MM-dd HH:mm:ss.SSS} | %p | %c | %t | %m | %x%n'
        )
    }
  }

  root {
    error 'logfile'
  }

  debug 'com.mydomain'
}

This makes logfile our default appender. Of course we can have multiple appenders too. Unless you override stdout you have that automatically. So the prior example has both stdout and logfile. You can have them both as default appenders like so.

log4j = {
  appenders {
    rollingFile name:'logile', maxFileSize:'50MB', maxBackupIndex:100,file:"./logs/${appName}_app.log",{
        layout:pattern(
          conversionPattern: '%d{yyyy-MM-dd HH:mm:ss.SSS} | %p | %c | %t | %m | %x%n'
        )
    }
  }

  root {
    error 'logfile', 'stdout'
  }

  debug 'com.mydomain'
}

Now they both receive the default logging output. What if I want only debugging to go to logfile, and both errors and debugging to go to stdout? Well, default appenders get everything by default, so we don’t want logfile as a default appender but rather a named appender.

log4j = {
  appenders {
    rollingFile name:'logile', maxFileSize:'50MB', maxBackupIndex:100,file:"./logs/${appName}_app.log",{
        layout:pattern(
          conversionPattern: '%d{yyyy-MM-dd HH:mm:ss.SSS} | %p | %c | %t | %m | %x%n'
        )
    }
  }

  root {
    error 'stdout'
  }

  debug logfile: 'com.mydomain'
}

It is valid to name a default appender here but it will result in the appender being written to twice, once due to the explicit reference and once as the default. If you want to write to the named appender only – not to any default appenders – then you need to specify no additivity.

log4j = {
  appenders {
    rollingFile name:'logile', maxFileSize:'50MB', maxBackupIndex:100,file:"./logs/${appName}_app.log",{
        layout:pattern(
          conversionPattern: '%d{yyyy-MM-dd HH:mm:ss.SSS} | %p | %c | %t | %m | %x%n'
        )
    }
  }

  root {
    error 'stdout'
  }

  debug logfile: 'com.mydomain', additivity: false
}

You can supply a list of packages instead of repeating a line for each.

log4j = {
  appenders {
    rollingFile name:'logile', maxFileSize:'50MB', maxBackupIndex:100,file:"./logs/${appName}_app.log",{
        layout:pattern(
          conversionPattern: '%d{yyyy-MM-dd HH:mm:ss.SSS} | %p | %c | %t | %m | %x%n'
        )
    }
  }

  root {
    error 'stdout'
  }

  debug logfile: ['com.mydomain.one','com.mydomain.two'], additivity: false
}

Since ultimately you are setting up (and resolving) logging attributes for a package, the resolution of the logger must be unambiguous. Consequently if you set up the same package multiple times the last one wins for setting the level. So although the following logs to both appenders as you wold expect, it logs to them both as debug.

log4j = {
  appenders {
    rollingFile name:'logile1', maxFileSize:'50MB', maxBackupIndex:100,file:"./logs/${appName}_app1.log",{
        layout:pattern(
          conversionPattern: '%d{yyyy-MM-dd HH:mm:ss.SSS} | %p | %c | %t | %m | %x%n'
        )
    }
    rollingFile name:'logile2', maxFileSize:'50MB', maxBackupIndex:100,file:"./logs/${appName}_app2.log",{
        layout:pattern(
          conversionPattern: '%d{yyyy-MM-dd HH:mm:ss.SSS} | %p | %c | %t | %m | %x%n'
        )
    }
  }

  root {
    error 'stdout'
  }

  error logfile1: 'com.mydomain', additivity: false
  debug logfile2: 'com.mydomain', additivity: false
}