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