Michael R. Head (suppressingfire) wrote,

Accessing the java:comp/env JNDI lookup context outside of a J2EE container

I was recently modifying some custom JDBC code that is meant to work inside of Tomcat where a JNDI naming context has been setup with a reference for a javax.sql.DataSource. In other words, somewhere in the data layer, we have code like this:

Context initContext = new InitialContext();
DataSource dataSource = (DataSource) initContext.lookup("java:comp/env/jdbc/db1");

And somewhere in the container configuration (in this case Tomcat's context.xml), we have something like this (obviously the IP address, username and password have been made up):

<Resource name="jdbc/db1" auth="Container" type="javax.sql.DataSource" driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver" url="jdbc:sqlserver://sql.example.com:1433" username="dbuser" password="dbpass" maxActive="20" maxIdle="10" maxWait="-1" />


The modification was to create a main() method that can use the same code from the command line. One option is just to “fix” the above code and remove to dependence on JNDI. While that is probably the most straightforward approach, I found it undesirable in this case to modify the existing code.

It took me some time to figure out how to mimic what Tomcat does with the above Resource tag. It's not terribly complicated, but it wasn't obvious to me, and I ran past a large number of “Name java: is not bound in this Context” when trying to bind the name "java:comp/env/jdbc/db1" and javax.naming.NoInitialContextExceptions before I understood how the javax.naming package is meant to work.

After some trial and error and reading through some of the Tomcat sources, I was able to put together some working code. The first trick is to create the data source, which will differ slightly depending on the specific database and JDBC driver used:

private static DataSource createDataSource() {
SQLServerDataSource ds = new SQLServerDataSource();
ds.setURL("jdbc:sqlserver://sql.example.com:1433");
ds.setUser("dbuser");
ds.setPassword("dbpass");
return ds;
}

And the following sets that data source into the context so it can be found by the unmodified JDBC code above.

private static void setupNamingContext(DataSource ds) throws NamingException {
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, javaURLContextFactory.class.getName());
Context ctx = new InitialContext();
Context javaCtx = ctx.createSubcontext("java");
javaCtx.createSubcontext("comp").createSubcontext("env").createSubcontext("jdbc").bind("db1", ds);
ctx.bind("java:", javaCtx);
}

And then in main, I call these directly:

public static void main(String[] args) throws NamingException {
setupNamingContext(createDataSource());
...
}


So far, I've just run the code from within an Eclipse project that was already configured as a Dynamic Web Project configured to run with Tomcat, so all the main jars from Tomcat are in the classpath as well as the jar for the JDBC driver. I did specifically need to add tomcat-juli.jar to the build path of the project to avoid an exception when first accessing the NamingContext:
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/juli/logging/LogFactory


In conclusion, the above few lines of Java should allow the use of code written with the expectation that it will run within a servlet container, with a JNDI naming context with resources in the “java:comp/env” context, to be called from a standalone main() method without the entire servlet engine up and running.



Update (2010-12-10): The setupNamingContext() method above doesn't quite seem to work anymore, but I've made a new post about doing the same for JMS resources that sets up the context tree a little more cleanly.
Tags: eclipse, java, programming
  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 11 comments