2011/01/23

How to run a Spring 3+JPA in JBoss 6 ?

To integrate to JBoss 6 a Spring application tested in Tomcat, we face to some JEE and CL issues. We won't discuss about solutions synchronizing the JEE Server or application dependencies. If you accept that, then you can find a solution here.

The JEE issues

All JEE APIs are brought by the JEE server (Servlet - as Tomcat does -, Java Persistence, JAXB, ...) : they must be removed from the application WAR/EAR.
This could be performed by a profile in your Maven build.

The main JPA issue is the file "persistence.xml" watched by JBoss. We have to rename it to "persistence-spring.xml". This way JBoss (and other JEE servers) will not detect JPA API usage in your application. We agree ... , this is a huge fault regarding an expected JEE Application using JPA and so, expecting a JPA implementation to be provided by the JEE Server with a total abstraction. Anyway I've never seen any application does not use some Hibernate or Toplink hints/properties/references in the "persistence.xml".

Also, to accomplish this, we have to update Spring PU reference from the EntityManagerFactory :

<bean id="emf-pu" 
  class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" 
  p:persistence-xml-location="classpath:META-INF/persistence-spring.xml" 
  p:persistenceUnitName="pu">
 ...
</bean>

As we'll see in the CL tuning, the libraries brought by JBoss are not hidden; there is just a preference to the Application's ones. So when any library does a Class.forName( X ), if X is not present in the application's CL, but in the JEE Server's CL, this library will be able to load X, and to cause some unexpected behaviors since this does not happen in Tomcat environment (or in another JEE servers). This is the case of Hibernate trying to locate dynamically some Hibernate-Validator classes brought by JBoss. To avoid this, you'll have to add these properties in your old "persistence.xml" :

<property name="hibernate.validator.apply_to_ddl" value="false" />
<property name="hibernate.validator.autoregister_listeners" value="false"/>

A similar issue for JAXB with a JDK 6u4+ with <2.2.1 (as in JBoss 6.0.0) requires -Dcom.sun.xml.bind.v2.bytecode.ClassTailor.noOptimize=true in System properties/JAVA_OPTS/...

ClassLoading issues

File "/WEB-INF/jboss-classloading.xml" needs to be added :

<classloading xmlns="urn:jboss:classloading:1.0"
 parent-first="false"
 name="myApp.war"
 domain="myApp.war"
 export-all="NON_EMPTY"
 import-all="true"/>

A JBoss 5+'s specific problem is brought by the new VFS protocol causes to all class/file scanners to return an empty list. Spring ORM uses this process to discover @Entity classes, JAR containing the file "persistence.xml" has an URL like vfs:/usr/.../myEnties.jar/. As the VFS protocol in not shared to application's CL, we have to switch to our specific scanner. Add this property to "persistence.xml" :

<property name="hibernate.ejb.resource_scanner" value="com.company.ResourceScanner"/>

The corresponding implementation :

public class ResourceScanner extends NativeScanner {

 @Override
 public Set<Class<?>> getClassesInJar(final URL jarToScan,
   final Set<Class<? extends Annotation>> annotationsToLookFor) {
  return super.getClassesInJar(patchUrl(jarToScan), annotationsToLookFor);
 }

 @Override
 public Set<NamedInputStream> getFilesInJar(final URL jarToScan, final Set<String> filePatterns) {
  return super.getFilesInJar(patchUrl(jarToScan), filePatterns);
 }

 @Override
 public Set<Package> getPackagesInJar(final URL jarToScan,
   final Set<Class<? extends Annotation>> annotationsToLookFor) {
  return super.getPackagesInJar(patchUrl(jarToScan), annotationsToLookFor);
 }

 @Override
 public String getUnqualifiedJarName(final URL jarToScan) {
  return super.getUnqualifiedJarName(patchUrl(jarToScan));
 }

 /**
  * Patch the VFS URL to a FILE protocol URL.
  * 
  * @param url
  *            original URL.
  * @return either the original, either the corresponding FILE protocol of given VFS URL.
  */
 protected URL patchUrl(final URL url) {
  if (url.getProtocol().equals("vfs")) {
   try {
    return new File(url.getFile()).toURI().toURL();
   } catch (final MalformedURLException e) {
    return url;
   }
  }
  return url;
 }
}

Take care, VFS is not always a simple replacement of protocol "FILE". This tip assumes there is no VFS configuration in JBoss (mapping, etc.).
Also there is the jboss-vfs maven artifact that can help to handle more seriously this file system.

Note : don't forget the standard JBoss configuration files "jboss-web.xml" (context-root, resource-ref, ...) and "*-ds.xml" in your EAR or deplyoment location.

3 comments:

Michol said...
This comment has been removed by the author.
Michol said...

Great Article, and very helpful. Why do you not use jee tag in applicationContext? Do you get problems with that tag?

Fabrice Daugan said...

Usually I do, but the related piece of XML is from a more complexe configuration using abstract bean that I cannot manage with jee templates.