Unfortunately Java EE specs does not specify any standard way of JNDI naming conventions, hence most of the application servers have their own way of JNDI naming. On specifying a Datasource's JNDI name as 'jdbc/myDatasource', Tomcat (6) binds that as 'java:/comp/env/jdbc/myDatasource' while JBoss (5) binds as 'java:/jdbc/myDatasource'. So if one wants to deploy the application on multiple application servers then at least JNDI names has to be changed. I was wondering if Spring Framework has a solution for this, so tried to do lookup using <jee:jndi-lookup ...> as shown below-
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/myDatasource" resource-ref="true"/>it works well with Tomcat but fails on JBoss. Reason, the JNDI prefix is hard coded in Spring to 'java:comp/env/'.
package org.springframework.jndi; public abstract class JndiLocatorSupport extends JndiAccessor { /** JNDI prefix used in a J2EE container */ public static final String CONTAINER_PREFIX = "java:comp/env/"; . . . }So it is obvious that it will fail on JBoss. To overcome this limitation, I came up with following solution-
public final class ServiceLocator { private static final Map<String, Object> services = new ConcurrentHashMap<String, Object>(); private static ServiceLocator instance; private static Context context; static { try { Context initContext = new InitialContext(); if (ServerDetector.isJBoss()) { context = (Context) initContext.lookup("java:"); } else if (ServerDetector.isTomcat()) { context = (Context) initContext.lookup("java:/comp/env"); } else { context = initContext; // or add more 'else if' blocks according to servers to be supported } } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public DataSource getDataSource(String name) throws Exception { if (name == null || name.length() <= 0) throw new IllegalArgumentException("name"); if (services.containsKey(name)) return (DataSource) services.get(name); DataSource ds = (DataSource) context.lookup(name); services.put(name, ds); return ds; } }
Here ServerDetector is a good utility class I found in Liferay code base. Now ServiceLocator takes care of application server specific JNDI prefixes and avoids hassles of changing configuration or code to deploy it on a specific server. In case of Spring instead of using <jee:jndi-lookup ...> one can do the lookup using above-mentioned ServiceLocator as shown below-
<bean id="serviceLocator" class="com.vinodsingh.ServiceLocator" factory-method="getInstance" /> <bean id="dataSource" factory-bean="serviceLocator" factory-method="getDataSource"> <constructor-arg value="jdbc/myDatasource" /> </bean>Now entire code (including configuration files) becomes truly portable, at least for JNDI lookups :-)