views:

363

answers:

3

I have a web-app that requires two settings:

  • A JDBC datasource
  • A string token

I desperately want to be able to deploy one .war to various different containers (jetty,tomcat,gf3 minimum) and configure these settings at application level within the container.

My code does this:

InitialContext ctx = new InitialContext();
Context envCtx = (javax.naming.Context) ctx.lookup("java:comp/env");
token = (String)envCtx.lookup("token");
ds = (DataSource)envCtx.lookup("jdbc/datasource")

Let's assume I've used the glassfish management interface to create two jdbc resources: jdbc/test-datasource and jdbc/live-datasource which connect to different copies of the same schema, on different servers, different credentials etc. Say I want to deploy this to glassfish with and point it at the test datasource, I might have this in my sun-web.xml:

...
<resource-ref>
  <res-ref-name>jdbc/datasource</res-ref-name>
  <jndi-name>jdbc/test-datasource</jndi-name>
</resource-ref>
...

but

  • sun-web.xml goes inside my war, right?
  • surely there must be a way to do this through the management interface

Am I even trying to do the right thing? Do other containers make this any easier? I'd be particularly interested in how jetty 7 handles this since I use it for development.

EDIT Tomcat has a reasonable way to do this:

Create $TOMCAT_HOME/conf/Catalina/localhost/webapp.xml with:

<?xml version="1.0" encoding="UTF-8"?>
<Context antiResourceLocking="false" privileged="true">
  <!-- String resource -->
  <Environment name="token" value="value of token" type="java.lang.String" override="false" />

  <!-- Linking to a global resource -->
  <ResourceLink name="jdbc/datasource1" global="jdbc/test" type="javax.sql.DataSource" />

  <!-- Derby -->
  <Resource name="jdbc/datasource2"
    type="javax.sql.DataSource"
    auth="Container"
    driverClassName="org.apache.derby.jdbc.EmbeddedDataSource"
    url="jdbc:derby:test;create=true"
    />

  <!-- H2 -->
  <Resource name="jdbc/datasource3"
    type="javax.sql.DataSource"
    auth="Container"
    driverClassName="org.h2.jdbcx.JdbcDataSource"
    url="jdbc:h2:~/test"
    username="sa"
    password=""
    />
</Context>

Note that override="false" means the opposite. It means that this setting can't be overriden by web.xml.

I like this approach because the file is part of the container configuration not the war, but it's not part of the global configuration; it's webapp specific.

I guess I expect a bit more from glassfish since it is supposed to have a full web admin interface, but I would be happy enough with something equivalent to the above.

A: 

I'm not sure to really understand the question/problem.

As an Application Component Provider, you declare the resource(s) required by your application in a standard way (container agnostic) in the web.xml.

At deployment time, the Application Deployer and Administrator is supposed to follow the instructions provided by the Application Component Provider to resolve external dependencies (amongst other things) for example by creating a datasource at the application server level and mapping its real JNDI name to the resource name used by the application through the use of an application server specific deployment descriptor (e.g. the sun-web.xml for GlassFish). Obviously, this is a container specific step and thus not covered by the Java EE specification.

Now, if you want to change the database an application is using, you'll have to either:

  • change the mapping in the application server deployment descriptor - or -
  • modify the configuration of the existing datasource to make it points on another database.

Having an admin interface doesn't really change anything. If I missed something, don't hesitate to let me know. And just in case, maybe have a look at this previous answer.

Pascal Thivent
My issue is that sun-web.xml is *inside* the war and deployed as part of the app, but it shouldn't be because it's container specific. The only difference the admin interface makes is that I think there should be a way to change the map to the real JNDI name on a per web-app basis.
Draemon
@Dreamon That's sadly not how things work and changing the binding is not a very common use case. If you want to do that, you'll have to repackage your war.
Pascal Thivent
I would have thought the application component provider should be providing a .war (possibly a signed, paid for .war with no source) and deploying it shouldn't involve repacking it. Tomcat can do it as I've noted above. Having an admin interface doesn't change anything except I was looking for a configuration option there rather than something akin to Tomcat's XML config. I'm surprised that others don't want to use a single war against testing and production databases.
Draemon
@Draemon I understand but you'll have to change the datasource configuration in that case. Maybe a deployment plan as suggested by @vkraemer is your best option in that case.
Pascal Thivent
+1  A: 

For GF v3, you may want to try leveraging the --deploymentplan option of the deploy subcommand of asadmin. It is discussed on the man page for the deploy subcommand.

vkraemer
Not perfect since it's a deployment-time thing, but looks pretty good in that it maintains separation (allows deploying a pure EAR).
Draemon
A: 

We had just this issue when migrating from Tomcat to Glassfish 3. Here is what works for us.

  • In the Glassfish admin console, configure datasources (JDBC connection pools and resources) for DEV/TEST/PROD/etc.
  • Record your deployment time parameters (in our case database connect info) in properties file. For example:
# Database connection properties
dev=jdbc/dbdev
test=jdbc/dbtest
prod=jdbc/dbprod
  • Each web app can load the same database properties file.
  • Lookup the JDBC resource as follows.

import java.sql.Connection;
import javax.sql.DataSource;
import java.sql.SQLException;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

/**
 * @param resourceName the resource name of the connection pool (eg jdbc/dbdev)
 * @return Connection a pooled connection from the data source 
 * associated with resourceName
 * @throws NamingException will be thrown if resource name is not found
 */
 public Connection getDatabaseConnection(String resourceName) 
             throws NamingException, SQLException {
    Context initContext = new InitialContext();
    DataSource pooledDataSource = (DataSource) initContext.lookup(resourceName);
    return pooledDataSource.getConnection();
 }

Note that this is not the usual two step process involving a look up using the naming context "java:comp/env." I have no idea if this works in application containers other than GF3, but in GF3 there is no need to add resource descriptors to web.xml when using the above approach.

idplmal