My requirement is to map various database (particularly SQL Server, MySQl and Postgres) using hibernate ; from db record create a xml file.
For hibernate i am creating hbm files and pojos at run-time using JAssist.
My code works great, for further modularization i implement fragment bundles for each database.so that my host bundle will handle runtime class creation and adding them in classloader, hbm file creation logic, and BL. fragment calls it by passing parameters.
when i create a fragment bundle for each database,
the runtime pojo class created in my host bundle is visible in my fragment bundle,
i checked with "Thread.currentThread().getContextClassLoader().loadClass()"
and able to create its instance,
The Problem is
when i call Hibernate functions from fragment bundle, I am getting "Entity not mapped", AFAIK These exception comes when hibernate is unable to find the mapping class with table.
So i guess Hibernate is not finding my runtime pojo classes. which it can find in host.
Host :
Runtime Pojo creation,
HBM and CFG creation and updation logic
BL
Fragment :
Hibernate layer,
Calling Hibernate function,
XML Creation logic
This problem always appears if you use Hibernate over more than one bundle. In the Hibernate configuration you can't tell in which Bundle the mapping files and the pojo class files can be found. Hibernate does not use the mechanisms which OSGI provides for this. As a result hibernate only finds mapping files and classes which are in the same bundle as the Hibernate library.
I don't know if there is anywhere a professional solution (a third party product) for this problem.
There are two possibilities to solve this problem:
Forget your fragment bundles and put all Hibernate libraries, mapping files, pojos, classes using Hibernate/HQL for all databases into a single bundle. You can switch between different databases when you use different hibernate.cfg.xml files; each database has its own configuration file. These hibernate.cfg.xml files can be outside the bundles.
Write your own Configuration class which extends org.hibernate.cfg.Configuration, In this class you have to
- write your own class loader which finds the pojo classes even in other bundles
- override addResource(String resourceName, ClassLoader classLoader) in a way it finds resources also in other bundles
- override doConfigure and buildSessionFactory so they use your class loader instead of the standard class loader (use Thread.setContextClassLoader and call the method from the super class, i. e. from the standard Hibernate Configuration class).
- override all the other methods which return an instance of Configuration so they return the instance of your Configuration class and not of the Hibernate Configuration class.
We did solution 2. It was a bit of work, but now it is running well. (Thought, when changing the Hibernate version again a bit of work might be necessary.)
have a look on org.hibernate.internal.util.ClassLoaderHelper.
All you have to do, is replacing the ClassLoader with a ClassLoader which is able to resolve your entity classes.
Hibernate-Osgi is also setting it to the OSGI ClassLoader (see org.hibernate.osgi.HibernateBundleActivator). So suggest the following:
BundleWideClassLoader cl = new BundleWideClassLoader();
if (ClassLoaderHelper.overridenClassLoader != null
&& ClassLoaderHelper.overridenClassLoader instanceof OsgiClassLoader)
{
OsgiClassLoader ocl = (OsgiClassLoader)ClassLoaderHelper.overridenClassLoader;
for (Bundle b : cl.getBundles()) {
ocl.addBundle(b);
}
} else {
ClassLoaderHelper.overridenClassLoader = new BundleWideClassLoader();
}
I put this to my HibernateConfiguration class by overriding buildSessionFactory which performs this routine and returns super.buildSessionFactory.
The BundleWideClassLoader looks like this
public class BundleWideClassLoader extends ClassLoader
{
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
for (BundleClassLoader cl : this.getAllClassLoader()) {
try {
Class clazz = cl.findClass(name);
return clazz;
} catch (Exception ex) {
}
}
throw new ClassNotFoundException(name);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class clazz = this.findClass(name);
if (resolve) {
this.resolveClass(clazz);
}
return clazz;
}
@Override
public URL findResource(String name) {
for (BundleClassLoader cl : this.getAllClassLoader()) {
URL ret = cl.findResource(name);
if (ret != null) {
return ret;
}
}
return null;
}
/**
* Returns a list of all available BundleClassLoader.
*
* @return classloader
*/
public HashSet<BundleClassLoader> getAllClassLoader() {
//
// Do some magic here to get your ClassLoaders from all of your Bundles
//
}
/**
* Returns a list of all bundles which are registered in this BundleWideClassLoader.
*
* @return list of managed bundles
*/
public HashSet<Bundle> getBundles() {
HashSet<Bundle> bundles = new HashSet<>();
for (BundleClassLoader cl : this.getAllClassLoader()) {
bundles.add(cl.getBundleContext().getBundle());
}
return bundles;
}
}
And finally the BundleClassLoader:
public class BundleClassLoader extends ClassLoader
{
/**
* Bundle context.
*/
private BundleContext context;
/**
* Constructor.
* @param ctx Bundle Context
*/
public BundleClassLoader(BundleContext ctx) {
this.context = ctx;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return this.context.getBundle().loadClass(name);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class clazz = this.findClass(name);
if (resolve) {
this.resolveClass(clazz);
}
return clazz;
}
@Override
public URL findResource(String name) {
return this.context.getBundle().getResource(name);
}
/**
* Returns bundle context.
* @return bundle context
*/
public BundleContext getBundleContext() {
return this.context;
}
}
I suggest to create a new BundleClassLoader in each of your BundleActivators and add it to some kind of registry so the BundleWideClassLoader can get a list of BundleClassLoader from there. Do not forget to remove the BundleClassLoader, when the bundle is stopped or removed.
Hibernate OSGi currently has several caveats, one of which requires a single persistence unit client bundle. For a variety of reasons, we have to use the "requestingBundle" when we build up the ClassLoader responsible for handling the persistence entities, mappings, and resources.
Have a look at:
https://github.com/hibernate/hibernate-orm/blob/master/hibernate-osgi/src/main/java/org/hibernate/osgi/OsgiClassLoader.java
OsgiPersistenceProviderService and OsgiSessionFactoryService both add the "requestingBundle" to the ClassLoader when the service is called. As Johanna suggested, there's not really a decent way to know which Bundles make up the persistence unit, other than requestingBundle or the location of the persistence.xml file itself.
However, I'd love to hear ideas on how to better support a setup like this. I had originally played around with additional metadata in the manifests to signify "I'm a part of persistence unit x", but never really had time to think it through.
Definitely do not use the solution recommended above using ClassLoaderHelper. That is a completely temporary hack that will be gone in ORM 5. It's there purely due to the static nature of ORM 4.