Spring and JNDI (Tomcat or Jetty)

Recently I had need to deploy some Spring webapps which required predeploy configuration. Being the first time I had to find a serious answer I looked to the mythical JNDI for an answer. This document is meant to complement other Spring JNDI documents out there.

Essentially the problem is this. We need to deploy a webapp. The webapp needs configurations (database and webservice endpoint locations). Editing properties files or XML config within the webapp isn’t nice, because on a redeploy the config will be lost. Inside containers like Tomcat I am not aware of a way to easily add extra items to the classpath which won’t get nuked unexpectedly, so solutions like PropertyPlaceholderConfigurer don’t really fly as the properties file will end up within the webapp. And I don’t like the idea of setting environment variables for to locate such things.

In steps JNDI. JNDI is the Java answer to namespaced, centralised configuration. Application containers like Tomcat, Jetty, Glassfish, etc all allow you to export objects via JNDI. This may not be a completely correct description, but it is sufficient for this demonstration. The trick is how to use these. I’ll show Jetty configs (which in Maven live in src/main/webapp/WEB-INF/jetty-env.xml) as well as some references to Tomcat (in $CATALINA_HOME/conf/server.xml or better still, in $CATALINA_HOME/conf/Catalina/[engine]/<webapp>.xml) (more on Tomcat here). This means that the config lives OUTSIDE the webapp, and is immune to inadvertant changes, making hot-patching sites easier as War/webapp is independent of the site config.

First, exposing a DB.
This exposes a Postgres DB on the name icatDB. Note, there is a special JDBC namespace. Also note I am not using the normal Postgres connection class, rather I’m using the connection pooling class.
Jetty:
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN"
"http://jetty.mortbay.org/configure.dtd">
<Configure class="org.mortbay.jetty.webapp.WebAppContext">
<New id="icatDB" class="org.mortbay.jetty.plus.naming.Resource">
<Arg>jdbc/icatDB</Arg>
<Arg>
<New class="org.postgresql.ds.PGPoolingDataSource">
<Set name="serverName">localhost</Set>
<Set name="databaseName">icat2</Set>
<Set name="user">nigel</Set>
<Set name="password"></Set>
</New>
</Arg>
</New>
</Configure>

Tomcat: Example from a different project:
<Context path="/continuum">
<Resource name="jdbc/users"
auth="Container"
type="javax.sql.DataSource"
username="sa"
password=""
driverClassName="org.apache.derby.jdbc.EmbeddedDriver"
url="jdbc:derby:database/users;create=true" />
</Context>

Spring: I am going to pass this into an entity manager:
...
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="POSTGRESQL" />
<property name="showSql" value="true" />
<property name="generateDdl" value="true" />
</bean>
</property>
</bean>

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

So I cheated here. The data source already has a JNDI entrypoint so Spring isn’t involved. However in this next example I need to pass in a String which is a webservice endpoint address:Passing a String:
These kinds of elements are passed via the env namespace. From the Jetty JNDI page it tells me we can only pass in these types:

  • java.lang.String
  • java.lang.Integer
  • java.lang.Float
  • java.lang.Double
  • java.lang.Long
  • java.lang.Short
  • java.lang.Character
  • java.lang.Byte
  • java.lang.Boolean

This is fine for configuration work, which is all we are doing.
Jetty:
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN"
"http://jetty.mortbay.org/configure.dtd">
<Configure class="org.mortbay.jetty.webapp.WebAppContext">
<New class="org.mortbay.jetty.plus.naming.EnvEntry">
<Arg>icatWebservice</Arg>
<Arg type="java.lang.String">http://hostname:8081/ws/ICAT</Arg>
</New>
</Configure>

Tomcat:

<Context path="/icat"
docBase="/var/home/tomcat/icat.war">
<Environment name="mcatextWebservice"
type="java.lang.String"
value="http://localhost:8180/mcatext/ws"/>
</Context>

Now Spring.

First import the jee namespace into your Spring config:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
...
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.0.xsd">
...

Now we can use the jee:jndi-lookup element in place of a value element:
...
<bean id="icatConnectionManagerBase" scope="session"
class="au.edu.archer.services.icat.ICATWsClientImpl">
<constructor-arg index="0">
<jee:jndi-lookup jndi-name="java:comp/env/icatWebservice"/>
</constructor-arg>
</bean>

There has also been discussion of writing a PropertyPlaceholderConfigurer like bean which can bring all the JNDI into the properties scope so we could just use ${env.property} notation.

This entry was posted in java, Maven, Spring. Bookmark the permalink.

9 Responses to Spring and JNDI (Tomcat or Jetty)

  1. Adam Sherman says:

    The Jetty example you use starts with ‘org.postgresql.ds.PGPoolingDataSource’ but when I do almost exactly the same thing and then try to inject this into Spring as a usual “dataSource” property, the types do not match. (Spring expects a javax.sql.DataSource) Is there not an additional piece that deals with the PooledDataSource?

    Thank you,

    A.

  2. simbot says:

    As I understand it the PGPoolingDataSource is wrapped in the JndiObjectFactoryBean for this reason. The LocalContainerEntityManagerFactoryBean expects this, or a raw DataSource, and if it gets the factory bean it will produce DataSources as required.

    So, I would suggest checking you are actually using this EntityManagerFactoryBean, and if this is the case perhaps we can take this offline and look in more detail at the spring config you are using.

  3. Rob says:

    Good article.

    For Jetty you suggest putting the entry in WEB-INF/jetty-env.xml. This is inside the WAR, so is not externalising the configuration, so doesn’t really allow environment-specific configuration (i.e. using the same WAR in different environments). Do you know how to set up JNDI entries in Jetty that are *local* to the webapp? (I know how to create global entries.)

    Rob

  4. Nigel says:

    Hi Rob,

    I’ll just clarify. I use the WEB-INF/jetty-env.xml file for development purposes within Maven2. I can run “mvn jetty:run” from within the top level project directory, and it will use the jetty-env.xml file to configure environment, using the compiled files in place. I’ve not actually used Jetty in production, just development.

    I’m sure you can transfer this file, perhaps renamed, to a fully jetty environment, but I’m not sure of the procedure. Hence the Tomcat configs.

    Nigel

  5. Mark says:

    What does your pom.xml look like. Did you have to modify the plugin section of the maven-jetty-plugin?

  6. Eugene says:

    Thanks for the article, I found it useful!

  7. Konstantin says:

    Thanks for the helpful article.

    In response to Rob’s earlier comment, you can keep jetty-env.xml outside the WAR like so. Also, I had trouble getting Spring to find JNDI datasources with later versions of the maven-jetty-plugin than 6.1.11.

    org.mortbay.jetty
    maven-jetty-plugin

    10

    ${basedir}/src/main/config/jetty-env.xml

    6.1.11

    Konstantin

  8. Konstantin says:

    Yikes, the XML tags disappeared from the POM snippet in my previous comment. To clarify, the jetty-env.xml location goes within a jettyEnvXml element within the plugin’s configuration element.

    Konstantin

  9. Pingback: Spring 2.x + EJB 3 Integration « Life Codecs

Leave a Reply