Tuesday, December 29, 2009

@DataSourceDefinition -- A Hidden Gem from Java EE 6

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



10 comments:

Anonymous said...

I guess this...

@RequestScoped @Alternative
public class QADSProvider extends DSProvider{
@Resource (lookup="java:global/jdbc/AppQADB")
private DataSource normal;

public DataSource getDataSource() {
return normal;
}
}

...is equivalent to...

@RequestScoped @Alternative
public class QADSProvider extends DSProvider{
@Produces @Resource (lookup="java:global/jdbc/AppQADB")
private DataSource normal;
}

?

Simon Haslam said...

Interesting post. However I'm not convinced that, from an application server administrator's perspective, we really want data source definitions (e.g. production passwords) embedded as annotations in code. I agree that the multiple, non-standardised ways of defining data sources must be inconvenient in environments running multiple brands of application servers though.

Anonymous said...

Intersting. You call it a hidden gem, I would call it a design-by-comittee not-completely-thought-through solution.


If JEE6 were useful there would not be a need for any such hacks that require you to rebuild your war file. with different @Alternative annotated Configuration classes.


The reason for that is the fact that this stuff has been designed to be a standard instead of coming out of a real project.


That is the reason why those Ruby applications are generally so much simpler, because they dont give a s**t about standards and just build it in a way that works great and is simple from the first look at it.


New standards have to be "fixed" with new frameworks after having been applied for two years. Thats the same reason Seam was created, namely in order to bridge the insanity-gap between EJB3 and JSF.

Until something as dead simple as Django for JEE6 comes along, I wont touch that thing at all. I dont want to wast any more time on half-ass standards that have been created for the standard-implementors but not for the real developer. And that as well is why Spring is not going anywhere.

Matt Corey said...

@Anonymous #1 -- I seem to remember something in the JCDI spec that prevents you from using @Resource and @Produces on the same field, although I could be wrong (I don't have the spec available to me right now)... I could go either way with it, although I like the separation of annotations on the field and the accessor...

@Simon -- point taken, we certainly wouldn't want to put production passwords into code like this... perhaps in this case it would be most wize to configure that in the traditional manner, and then map from one JNDI key to a known key that your code uses -- there are a couple of ways to do this, but I plan on incorporating this ability into the Environment Configuration module in some way...

@Anonymous #2 -- I love it when people say things like JEE isn't useful... the fact of the matter is that JEE is the trusted solution for thousands of business, corporations and governments for mission critical applications -- I think that qualifies it as pretty damn useful...

Is it perfect? Of course not, and I've documented many cases of this, but I still stand by the comment that both the product and culture of the last two specs has improved dramatically... specs are now being created based on real projects -- JCDI is a prime example of this, having been derived from both Seam and Guice... EJB 3 and JSF 2 have also been heavily corrected from their past failures and problems by looking at real-world examples of success...

That being said, platforms like Ruby, Grails, etc. always up the ante by adding more competition... I say bring it on -- the more players out there who are creating innovative solutions to solve actual problems, the better our industry will be overall

M

Gavin said...

Anon #1: no, they're not equivalent. Your second code snippet exposes the DataSource as a CDI resource for direct injection into other beans. Your first code snippet doesn't - you would need a @Produces annotation somewhere, either on the field or getter method. Matt is injecting DSProvider and calling the getter, instead of injecting DataSource (not sure why).

Simon: well, it's no worse than putting the password on an XML file or "Groovy configuration file", which the competing solutions use.

Anon #2: you probably should try getting over your emotional issues. *All* software has wrinkles and even bugs. In fact, it is extremely common for new features of software frameworks to not work properly when used together. This pretty minor wrinkle is one that we should be able to address in a maintenance release of CDI. After Matt brought this to our attention, I emailed the EG about it with a proposed solution. Of course, I don't have a solution for your mental health problems.

Matt: you can definitely use @Resource and @Produces together. In fact, it has a special interpretation in CDI. See:

http://docs.jboss.org/weld/reference/1.0.0/en-US/html/resources.html

Matt Corey said...

Gavin -- As for using @Resource and @Produces together, perhaps I was thinking about @Inject and @Produces... that one doesn't seem to make a lot of sense...

As for putting your password in code as opposed to XML, at least with that solution, you have mechanisms for protecting files that wouldn't likely be in place for source code... I'm not actually sure if Groovy has a solution to this one, but I had it on my list to look it up :)

M

Anonymous said...

Hi, for the last couple of hours I've been searching about @DataSourceDefinition- A Hidden Gem from Java EE 6 and finally I stumble into your blog, it has great info on what I'm looking and is going to be quite useful on my paperwork for the university.
BTW is amazing how many generic viagra blogs I manage to dodge in order to get the right site and the right information...lol
Thanks for the post and have a nice day

Anonymous said...

@Matt

The @DataSourceDefinition can be mirrored in XML. Like nearly every Java EE annotation, there's an XML equivalent. In this case it's the data-source element in web.xml or application.xml.

Anna said...

Great and Useful Article.

J2EE Training

Java EE course

Java EE training

J2EE training in chennai

Java J2EE Training Institutes in Chennai

Java J2EE Training in Chennai

Java EE training

Java Interview Questions

Suseela said...





Great and useful article. Creating content regularly is very tough. Your points are motivated me to move on.


SEO Company in Chennai