In the old days, DataSources were configured -- well, they were configured in lots of different ways... That's because there was no 'one way' to do it -- in JBoss, you created an XML file that ended in '-ds.xml' and dumped it in the deploy folder... in Glassfish, you either use the admin console or muck with the domain.xml file... in WebLogic you used the web console... and this was all well and good -- until I worked with an IT guy who told me just how much of a pain in the ass it was...
Up until then, it wasn't such a big deal to me -- I set it up once, and that was that... then I ran into this guy a few jobs ago who liked to bitch and complain about how much harder it was to deploy our application than the .NET or Ruby apps he was used to... he had to deploy our data source, then he had to deploy our JMS configurations -- only then would our application work... in the other platforms, that was all built into the app (I'll have to take his word for it, since I haven't actually deployed anything in either platform)... I was a but surprised at first, and then I realized that maybe he had a point... nah, it couldn't be, he must just be having a bad day (lots of us were having bad days back then :) )...
Then I ran into Grails, which is dead simple -- you have a Groovy configuration file that has your db info in it... you even have the ability to specify different 'environments', which can change depending on how you create your archives or run your app... pretty slick...
The Gem
Well, lo and behold, we now have something that's nearly equivalent in Java EE 6 -- the @DataSourceDefinition attribute... it's a new attribute that you can put on a class that provides a standard mechanism to configure a JDBC DataSource into JNDI, and as expected, it can work with local JNDI scopes or the new global scope, meaning you can have an Environment Configuration that uses this attribute making it shareable across your server... it works like this:
import javax.annotation.sql.DataSourceDefinition;
import org.jboss.seam.envconfig.Bind;
import org.jboss.seam.envconfig.EnvironmentBinding;
@DataSourceDefinition (
className="org.apache.derby.jdbc.ClientDataSource",
name="java:global/jdbc/AppDB",
serverName="localhost",
portNumber=1527,
user="user",
password="password",
databaseName="dev-db"
)
public class Config {
...
}
As you would expect, that annotation will create a DataSource that will point to a local Derby db, and stick it into JNDI at the global address 'java:global/jdbc/AppDB', which your application, or other applications can refer to as needed... no separate deployment and no custom server-based implementation -- this code should be portable across any Java EE 6 server (including the Web Profile!)...
It's almost perfect!
In typical Java EE style, there's one thing that just doesn't appear to be working the way I'd like it -- it doesn't appear to honor JCDI Alternatives (at least not in Glassfish)... Here's what I'm thinking -- we should be able to have a different Config class for each of our different environments... in other words, we'd have a QAConfig that pointed to a different Derby db, a StagingConfig that pointed to a MySQL db somewhere on another server, and a ProductionConfig that pointed to kick ass, clustered MySQL db... we could then use Alternatives to turn on the ones that we want in certain environments with a simple XML change, and not have to muck with code... unfortunately, it doesn't appear to work -- it appears in Glassfish that it is processing them in an undeterministic order, with (presumably) the class that is processed last overwriting the others that came before it...
There is a solution, though, and it is on the lookup side of the equation -- using JCDI Alternatives, we can selectively lookup the DataSource that we're interested in, and then enable that Managed Bean in the beans.xml file... it's definitely not ideal, since we need to actually inject all of our DataSources into JNDI in all scenarios, but it works, it's something I can live with, and is probably easily fixed in a later Java EE release... Update: Looks like it's in the plan, according to this link -- thanks, Gavin :)
Here's how it works -- first the 'common' case, probably for a Development environment:
@RequestScoped
public class DSProvider {
@Resource (lookup="java:global/jdbc/AppDB")
private DataSource normal;
public DataSource getDataSource() {
return normal;
}
}
Simple enough -- has a field that looks up 'jdbc/AppDB' from JNDI, and provides a getter... now for QA:
@RequestScoped @Alternative
public class QADSProvider extends DSProvider{
@Resource (lookup="java:global/jdbc/AppQADB")
private DataSource normal;
public DataSource getDataSource() {
return normal;
}
}
Pretty much the same, except this does the lookup from 'jdbc/AppQADB', and it is annotated with @Alternative... so how do these things work together? Take a look:
@Named
public class Test {
@Inject
private DSProvider dsProvider;
...
}
Again, simple -- we're injecting a DSProvider instance here, and presumably running a few fancy queries... Nothing Dev-ish or QA-ish here at all, which is the beauty of Alternatives... finally, when building the .war file for QA, we turn on our Alternative in the beans.xml, like so:
<beans>
<alternatives>
<class>com.mcorey.alternativedatasource.QADSProvider</class>
</alternatives>
</beans>
You'll notice that this solution requires us to rebuild our .war file for QA, which I obviously don't like -- not to worry, there will be support for this in the Seam 3 Environment Configuration Module, which will effectively create a binding by mapping from one JNDI key to another... I have no idea what the syntax will look like at this point, but it should be pretty straight forward, and will allow us to -- you guessed it -- build our .war one, and copy it from place to place without modification...
M