JBoss AS 5 and GlassFish support added to seam-gen

Posted by    |      

I finally got around to weaving my modifications for adding GlassFish support to seam-gen (documented here and here) into the Seam project (JBSEAM-1619). While working on integration the changes, I managed to close the few remaining gaps and also add support for JBoss AS 5!

Let me tell you, it was quite a task to fine tune the configuration and build to support all twelve deployment variations; EAR and WAR archives for GlassFish V2/V3, JBoss AS 4.2.x and JBoss AS 5.0.x in both exploded and packaged format. (Double that number if you consider that I added support to both RichFaces and ICEfaces projects). But surprisingly enough, all of them now work out of the box (with the exception of a few small configuration changes you have to make to deploy an EAR project to GlassFish).

The persistence paradox

The biggest challenge was the persistence unit configuration. This stems from the fact that packaging requirements for persistence units differs between EARs and WARs. To complicate matters, there are variations between how application servers interpret and enforce these requirements. I want to spend some time explaining the situation and how I was able to strike a balance, because it's important for understanding how the project build now works.

Let's start by rewinding to the way things were setup. There's an expectation in the JPA specification that the application server will bootstrap the persistence units defined in META-INF/persistence.xml in a Java EE environment. Of course, it would be pretty limiting if that were the only way to use JPA. So an API is provided allowing an application to load a persistence unit manually in a Java SE environment (or when the container doesn't support this expectation). Understandably, it took some time for this expectation to be met as vendors worked to bring their application server into compliance with Java EE 5 (the first specification to include JPA).

For containers that don't support loading persistence units, whether for a specific archive type (EAR or WAR) or in general, Seam can step in and take over this task. That's the purpose of the following component definition, defined in the Seam component descriptor (i.e., components.xml):

<persistence:entity-manager-factory name="entityManagerFactory" persistence-unit-name="example"/>

The value of persistence-unit-name attribute matches the name of one of the persistence units defined in META-INF/persistence.xml.

Versions of JBoss AS prior to JBoss AS 5 do not support loading persistence units in a WAR, so it's necessary to delegate this task to Seam. The same is true for any version of Tomcat. GlassFish is truer to the spec. It will load a persistence unit, but only if it is declared in a persistence unit ref in WEB-INF/web.xml as follows:

<persistence-unit-ref>
    <persistence-unit-ref-name>example/pu</persistence-unit-ref-name>
    <persistence-unit-name>example</persistence-unit-name>
</persistence-unit-ref>

The persistence unit ref has another purpose. After the persistence unit is loaded, the EntityManagerFactory instance will be bound to JNDI using the value of the persistence-unit-ref-name in the java:comp/env namespace (in this example the full JNDI name would be java:comp/env/example/pu). Prior to version 5, JBoss AS did not support this JNDI binding. To provide a temporary workaround, Hibernate can offer to bind the EntityManagerFactory to JNDI itself under the proprietary JBoss AS namespace java:/. This feature is activated if the following property is present in META-INF/persistence.xml:

<property name="jboss.entity.manager.factory.jndi.name" value="java:/exampleEntityManagerFactory"/>

JBoss AS 5 supports the compliant approach to binding the EntityManagerFactory to JNDI, but has another caveat. It automatically loads all persistence units defined in META-INF/persistence.xml regardless of whether they are declared in a persistence unit ref. This fact has confused a lot of people and is the root cause of most broken Seam deployments on JBoss AS 5 right now. Suffice to say, it's best to only define persistence units you intend to load and stand aside and let the container load them.

So far the discussion has applied to WAR deployments. Things get even trickier with EARs. Technically, persistence units (the descriptor and classes) are supposed to be packaged in their own JAR and then packaged in the WAR under the WEB-INF/lib directory or packaged directly in the EAR. However, the persistence units can also be packaged in the EJB JAR. Either way, you really have to know what you are doing to get all the references setup correctly so that the persistence unit is loaded correctly and can be bound to JNDI where the WAR can access it. When the application server works the way it's supposed to, the following persistence unit configuration will suffice:

<persistence-unit-ref>
    <persistence-unit-ref-name>example/pu</persistence-unit-ref-name>
    <persistence-unit-name>../example.jar#example</persistence-unit-name>
</persistence-unit-ref>

Notice how the persistence unit name now includes the relative location of the JAR file within the EAR which contains the persistence unit descriptor and classes. For some reason, GlassFish does not understand this configuration and I've had incorporate a temporary workaround (see resources/WEB-INF/web.xml of a seam-gen EAR project for instructions).

To summarize the various permutations, I have put together a matrix of how persistence units are handled and how they are loaded in seam-gen projects per archive type and environment:

WAR EAR
JBoss AS 4.2 JBoss AS 5.0 GlassFish JBoss AS 4.2 JBoss AS 5.0 GlassFish
Bootstrapped by Seam container container container container container
Depends on persistence unit ref no no yes no no yes
JNDI binding mechanism n/a both standard proprietary both standard
Autodetects entities yes yes yes yes yes no

As you can imaging, dealing with all of these variations requires substantial support from the build. Fortunately, I was able to leverage a feature of Seam that accommodates these needs. You can use Ant-style tokens in the Seam component descriptor and Seam will replace them using the replacement values defined in the components.properties file at the root of the classpath. But even the replacement values have to be dynamic to support the various environments. So I also use Ant-style tokens in that file, which are replaced by the Ant build. So there is a two-phase substitution that occurs. Here's the contents of the components-dev.properties file:

jndiPattern=@ejbJndiPattern@
debug=true
seamBootstrapsPu=@seamBootstrapsPu@
seamEmfRef=@seamEmfRef@
puJndiName=@puJndiName@

Here's the resulting components.properties prepared from this template by a build targeting JBoss AS 5 in an EAR project named example:

jndiPattern=example/#{ejbName}/local
debug=true
seamBootstrapsPu=false
seamEmfRef=#{null}
puJndiName=java:comp/env/example/pu

Here's the components.properties prepared for a build targeting JBoss AS 4 in a WAR project named example:

jndiPattern=example/#{ejbName}/local
debug=true
seamBootstrapsPu=true
seamEmfRef=#{entityManagerFactory}
puJndiName=#{null}

These tokens are applied to the following two component definitions in the Seam component descriptor:

<persistence:managed-persistence-context name="entityManager" auto-create="true"
                       entity-manager-factory="@seamEmfRef@"
                   persistence-unit-jndi-name="@puJndiName@"/>

<persistence:entity-manager-factory name="entityManagerFactory"
                   persistence-unit-name="@projectName@"
                               installed="@seamBootstrapsPu@"/>

Whew! That covers the JPA configuration. For more details on persistence unit configurations in both Java EE and Seam, check out chapter 9 of Seam in Action. I now want to briefly mention the new targets to support deployment to JBoss AS 5 and GlassFish.

JBoss AS 5 and GlassFish targets

As before, the seam-gen project build supports JBoss AS by default. But I'm now able to support both JBoss 4 and JBoss 5 using the same targets by inspecting the JBoss AS 5 installation directory (resolved from jboss.home) to determine which version of JBoss AS is being targeted. I simply check for the presence of a JAR file.

GlassFish does have it's own set of targets. All of the targets that pertain to GlassFish reside in the file glassfish-build.xml at the root of the project. This file is imported into the main Ant build. All of the GlassFish targets are prefixed with gf- and closely mirror the targets for JBoss AS. There are also targets for controlling the GlassFish server. Here's a quick summary of the new targets, which are documented in the glassfish-readme.txt file:

gf-start - Starts GlassFish
gf-debug - Starts GlassFish in debug mode
gf-stop - Stops GlassFish
gf-reboot - Restarts GlassFish
gf-deploy-datasource - Deploys the datasource and connection pool to GlassFish
gf-explode - Deploys the exploded archive to GlassFish (restarts application if already deployed)
gf-hotdeploy - Hot deploys Java classes, Seam components, and web resources
gf-deploy - Deploys the packaged archive to GlassFish
gf-undeploy - Undeploys the exploded or packaged archive from GlassFish
gf-stage - Prepares an exploded archive targeting GlassFish
gf-archive - Prepares a packaged archive targeting GlassFish
gf-prepare - Prepares GlassFish for a seam-gen project deployment (calls gf-deploy-hibernate)
gf-deploy-hibernate - Deploys Hibernate as a JPA provider to GlassFish

You don't have to do anything to enable the support for GlassFish. It's provided right out of the box alongside support for JBoss AS. One of the gaps I was finally able to close as I worked on the GlassFish support was the deployment of an explode EAR. GlassFish kept choking on the jboss-seam.jar, which contains EJBs and is thus considered an EJB module. As it turns out, GlassFish does not support a hybrid of packaged and exploded archives. Thus, when you are deploying an exploded EAR, you need to also explode the jboss-seam.jar file, which the build takes care of for you.

The only trade off I had to make is that Hibernate is expected to be the JPA provider in GlassFish (note the target which deploys Hibernate). It's not that difficult to make the switch to another JPA provider, but suffice to say, Seam works best with Hibernate.

Growing Ivy

Seam projects are know for being pretty old school when it comes to library management. All of the JAR files are just dumped into the lib directory and picked out from there as needed. This makes it very difficult to share a seam-gen project because it's so large. I explained how to use Ivy to manage the dependencies for a seam-gen project in this entry. seam-gen now offers that configuration as an extension. To enable it, you simply run the following seam-gen target:

seam add-ivy

Right now, only RichFaces projects are supported, but supporting ICEfaces projects is just a matter of making a couple of changes to the dependencies.

Wrap up

So there you go, seam-gen is no longer stuck in a JBoss world. And for the early adopters, JBoss AS 5--the pride and joy of JBoss--is now supported. Adding Ivy to the project helps make it more lightweight, provides an easy way to download source JARs, and helps document which versions of the JARs are being used. Just check out Seam from SVN, build and enjoy!


Back to top