The redemption part is that I finally got the JBoss classloader to let all of my applications play nicely together. The ugly part is how I got there – and it gives some unusual insight into the JBoss classloader.
Here’s the situation – EAR1 is Liferay Portal, which requires that Hibernate JAR files are in the JBoss instance’s classpath. EAR2 is a set of internal applications that use a different version of Hibernate, and is configured to load in its own classloader. When EAR1 is not deployed, all libraries and components of EAR2 deploy and execute correctly. When EAR1 IS deployed, Liferay behaves fine, but the internal apps break – the log reflects that some of the configuration for EAR2 is being loaded from EAR1, and a ClassCastException ensues.
The code I was using to load Hibernate in EAR2 is as follows:
private void configure() throws HibernateException, IOException {
Configuration conf = new Configuration();
configure(conf);
}
private void configure( Configuration config ) throws HibernateException {
_sessions = config
.addClass(Inventory.class)
.addClass(InventoryLocation.class)
.addClass(SalesObject.class)
.addClass(MerchandiseItem.class)
.addClass(SKU.class)
.buildSessionFactory();
}
In this situation, the default behavior is that Hibernate simply finds hibernate.properties in the classpath. And while the initial configuration showed that it was loading the correct properties and even the correct version of Hibernate, but the final configuration showed that it was using config from the other EAR!
Strangely, using the following code fixed the issue:
private void configure() throws HibernateException, IOException {
Configuration conf = new Configuration();
Properties props = new Properties();
props.load(this.getClass().getResourceAsStream(“/hibernate.properties”));
conf.setProperties(props);
configure(conf);
}
Now it seems logical that net.sf.hibernate.cfg.Configuration probably executes the same code to load the properties, and it’s obvious that I was loading the correct Hibernate JAR based on the version in the log, however, it seems that somewhere along the way, something got delegated to the system classloader FIRST, loading the hibernate.properties from EAR2. By moving the configuration to my class, it appears I forced JBoss to use the scoped classloader I named for EAR2, thus loading the correct properties.
Once again, I have a massive beef with this. I can’t really see any instance where I want a resource loaded in one EAR to be accessible in another EAR. hibernate.properties should NEVER end up in the system classpath when it’s deployed in an EAR. But this seems to be the default behavior of the JBoss Universal ClassLoader.
From the documentation: “If you need to deploy multiple versions of an application the default 3.x class loading model would require that each application be deployed in a separate JBoss server.”
Also: “All UnifiedClassLoader3s. The All UnifiedClassLoader3s node represents the UCLs created by deployers. This covers EARs, jars, WARs, SARs and directories seen by the deployment scanner as well as jars referenced by their manifests and any nested deployment units they may contain. This is a flat namespace and there should not be multiple instances of a class in different deployment jars. If there are, only the first loaded will be used and the results may not be as expected.”
NOT AS EXPECTED? Well duh! If a 3rd party app loads a library, why the blazes would I expect their version of a library to replace mine, or vice versa? J2EE has robust, hierarchal structures in which we can deploy applications. What’s the point of all that if that stucture’s annihilated by loading everything into a flat ClassLoader?