Search code examples
springhibernateequinox

Equinox (OSGi) and JPA/Hibernate - Finding Entities


I am trying to use Hibernate/Spring in an OSGi (Equinox) environment. It works great if I explicitly point it to the Entity classes in the Persistence.xml:

    <class>com.es.t.eee.domain.StuffSource</class>
    <class>com.es.t.eee.domain.PostalAddress</class>

What I want is for Hibernate to "find" all of the Entity classes just as it does outside of the OSGi environment.

Hibernate is looking in the correct bundle for the @Entities:

Searching mapped entities in jar/par: bundleresource://34/
WARN  27-07 15:30:24,235 (InputStreamZippedJarVisitor.java:doProcessElements:41):
Unable to find file (ignored): bundleresource://34/

It looks like it should work, but when it gets to the point of looking in the Bundle Jar for @Entities, an exception occurs, and I am not sure why. I have included the important parts of the log that Hibernate is spitting out.

Does anyone have any ideas what I am doing wrong or what the issue here is?

I am using:

  • Hibernate Core 3.3.0.SP1
  • Hibernate Annotations 3.4.0.GA
  • Hibernate Commons Annotations 3.1.0.GA
  • Hibernate EntityManager 3.4.0.GA
  • Equinox 3.4
  • Spring Dynamic Modules 1.2.0

Here is where Hibernate parses the Persistence.xml

INFO  27-07 15:30:24,110 (Version.java:<clinit>:15):
Hibernate Annotations 3.4.0.GA
INFO  27-07 15:30:24,110 (Environment.java:<clinit>:543):
Hibernate 3.3.0.SP1
INFO  27-07 15:30:24,110 (Environment.java:<clinit>:576):
hibernate.properties not found
INFO  27-07 15:30:24,126 (Environment.java:buildBytecodeProvider:709):
Bytecode provider name : javassist
INFO  27-07 15:30:24,126 (Environment.java:<clinit>:627):
using JDK 1.4 java.sql.Timestamp handling
INFO  27-07 15:30:24,157 (Version.java:<clinit>:14):
Hibernate Commons Annotations 3.1.0.GA
INFO  27-07 15:30:24,157 (Version.java:<clinit>:16):
Hibernate EntityManager 3.4.0.GA
DEBUG 27-07 15:30:24,219 (Ejb3Configuration.java:configure:312):
Processing PersistenceUnitInfo [
    name: unit.postgresql
    persistence provider classname: null
    classloader: BundleDelegatingClassLoader for [DatabaseObjects (DatabaseObjects)]
    Temporary classloader: org.springframework.instrument.classloading.SimpleThrowawayClassLoader@11b50a1
    excludeUnlistedClasses: true
    JTA datasource: null
    Non JTA datasource: com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 1000, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> 1012f6d821goxcok12zcw86|174f02c, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> org.postgresql.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 1012f6d821goxcok12zcw86|174f02c, idleConnectionTestPeriod -> 0, initialPoolSize -> 5, jdbcUrl -> jdbc:postgresql://localhost:5432/test2, lastAcquisitionFailureDefaultUser -> null, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 100, maxStatements -> 100, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> null, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false ]
    Transaction type: RESOURCE_LOCAL
    PU root URL: bundleresource://34/
    Jar files URLs []
    Managed classes names []
    Mapping files names []
    Properties [
        hibernate.default_batch_fetch_size: 500
        hibernate.cache.provider_class: net.sf.ehcache.hibernate.EhCacheProvider
        hibernate.dialect: org.hibernate.dialect.PostgreSQLDialect
        hibernate.max_fetch_depth: 5
        hibernate.query.factory_class: org.hibernate.hql.ast.ASTQueryTranslatorFactory
        hibernate.format_sql: false
        hibernate.jdbc.batch_size: 1000
        hibernate.use_outer_join: true
        hibernate.archive.autodetection: class
        hibernate.show_sql: false
        hibernate.bytecode.provider: cglib]

Here is where the error occurs, as it tries to find the entities:

DEBUG 27-07 15:30:24,235 (Ejb3Configuration.java:getDetectedArtifacts:562):
Detect class: true; detect hbm: false
DEBUG 27-07 15:30:24,235 (Ejb3Configuration.java:getDetectedArtifacts:562):
Detect class: true; detect hbm: false
DEBUG 27-07 15:30:24,235 (AbstractJarVisitor.java:unqualify:116):
Searching mapped entities in jar/par: bundleresource://34/
WARN  27-07 15:30:24,235 (InputStreamZippedJarVisitor.java:doProcessElements:41):
Unable to find file (ignored): bundleresource://34/
java.lang.NullPointerException: in is null
    at java.util.zip.ZipInputStream.<init>(Unknown Source)
    at java.util.jar.JarInputStream.<init>(Unknown Source)
    at java.util.jar.JarInputStream.<init>(Unknown Source)
    at org.hibernate.ejb.packaging.InputStreamZippedJarVisitor.doProcessElements(InputStreamZippedJarVisitor.java:37)
    at org.hibernate.ejb.packaging.AbstractJarVisitor.getMatchingEntries(AbstractJarVisitor.java:139)
    at org.hibernate.ejb.Ejb3Configuration.addScannedEntries(Ejb3Configuration.java:287)
    at org.hibernate.ejb.Ejb3Configuration.scanForClasses(Ejb3Configuration.java:614)
    at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:360)
    at org.hibernate.ejb.HibernatePersistence.createContainerEntityManagerFactory(HibernatePersistence.java:131)
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:224)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:291)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1369)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1335)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:473)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$1.run(AbstractAutowireCapableBeanFactory.java:409)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:380)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:264)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:261)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:185)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:164)
    at org.springframework.beans.factory.support.AbstractBeanFactory.isSingleton(AbstractBeanFactory.java:366)
    at org.springframework.osgi.service.exporter.support.OsgiServiceFactoryBean.afterPropertiesSet(OsgiServiceFactoryBean.java:235)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1369)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1335)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:473)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$1.run(AbstractAutowireCapableBeanFactory.java:409)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:380)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:264)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:261)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:185)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:164)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:423)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:728)
    at org.springframework.osgi.context.support.AbstractDelegatedExecutionApplicationContext.completeRefresh(AbstractDelegatedExecutionApplicationContext.java:288)
    at org.springframework.osgi.extender.internal.dependencies.startup.DependencyWaiterApplicationContextExecutor$CompleteRefreshTask.run(DependencyWaiterApplicationContextExecutor.java:145)
    at java.lang.Thread.run(Unknown Source)

Solution

  • This blog shows how it can be handled with Spring's Dynamic Modules. Based on that blog, you'd create bean definitions for DAO and Service beans in your application context. The DAO has a reference to the hibernate sessionFactory bean:

    <bean id="stuffDao" class="com.es.t.eee.domain.dao.StuffSourceDaoImpl">
      <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <bean id="stuffService" 
      class="com.es.t.eee.domain.dao.StuffServiceImpl">
      <property name="stuffDao" ref="stuffDao"/>
    </bean>
    
    <bean id="stuffSource" 
      class="com.es.t.eee.domain.StuffSource" scope="prototype"/>
    

    The StuffSourceDaoImpl would implement the store() method to persist the StuffSource, it passes the StuffSource to the HibernateTemplate to actually do the persistence.

    To avoid having to define persistence.xml files or specifying every Entity, you can use the AnnotationSessionFactoryBean and a wildcard to include all the types in the package(s) containing your entities (you may instead want to set the packagesToScan property, I've not tried this and it might take you back to your original problem):

    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
      <property name="hibernateProperties">
        <props>
          <!--etc-->
        </props>
      </property>
      <!-- set either one of these properties -->
      <property name="annotatedClasses">
        <list>
          <value>com.es.t.eee.domain.StuffSource</value>
          <value>com.es.t.eee.domain.PostalAddress</value>
        </list>
      </property>
      <property name="packagesToScan">
        <list>
          <value>com.es.t.eee.domain</value>
        </list>
      </property>
    </bean>
    

    To use this you'd get the BundleContext, get the service and bean from the context, then carry on as normal:

    String serviceName = StuffService .class.getName();
    StuffService service = 
      (StuffService )context.getService(context.getServiceReference(serviceName));
    
    String stuffName = StuffSource.class.getName();
    StuffSource stuffSource = 
      (StuffSource) context.getService(context.getServiceReference(stuffName ));
    
    //do whatever with StuffSource
    ...
    
    service.store(stuffSource );
    

    You may also want to look at this OSGi Alliance blog that discusses some of the issues involved. From the OSGi Alliance blog:

    Hibernate manipulates the classpath, and programs like that usually do not work well together with OSGi based systems. The reason is that in many systems the class visibility between modules is more or less unrestricted. In OSGi frameworks, the classpath is well defined and restricted. This gives us a lot of good features but it also gives us pain when we want to use a library that has aspirations to become a classloader when it grows up. ...

    To work with Hibernate, you need a Session object. You can get a Session object from a SessionFactory. To get the the SessionFactory, you need to create it with a Configuration object. The Configuration object is created from a configuration XML file. By default, Hibernate loads this from the root of your JAR file, however, you can add classes manually to the configuration if so desired.