Hibernate OSGi discovers the wrong entity type

Description

The Aries transaction control integration tests have an XA integration test where two persistence units are created and used in a two phase commit. The persistence units are defined in different bundles, but use the same name for their one and only Entity Type. This name is org.apache.aries.tx.control.itests.entity.Message. The Entity types are private to the bundle, and so each one is loaded by a different class loader. In summary, the types have the same name, but are not the same (i.e. one cannot be cast to another).

These tests work reliably on OpenJPA and EclipseLink, however sometimes when running on Hibernate I see the tests fail. Having step debugged the root cause is the following exception:

org.hibernate.property.access.spi.PropertyAccessException: Error accessing field [public java.lang.Integer org.apache.aries.tx.control.itests.entity.Message.id] by reflection for persistent property [org.apache.aries.tx.control.itests.entity.Message#id] : Message [id=null, message=Hello 1!]
at org.hibernate.property.access.spi.GetterFieldImpl.get(GetterFieldImpl.java:43)
at org.hibernate.tuple.entity.AbstractEntityTuplizer.getIdentifier(AbstractEntityTuplizer.java:223)
at org.hibernate.persister.entity.AbstractEntityPersister.getIdentifier(AbstractEntityPersister.java:4601)
at org.hibernate.persister.entity.AbstractEntityPersister.isTransient(AbstractEntityPersister.java:4313)
at org.hibernate.engine.internal.ForeignKeys.isTransient(ForeignKeys.java:226)
at org.hibernate.event.internal.AbstractSaveEventListener.getEntityState(AbstractSaveEventListener.java:510)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:99)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:58)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:775)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:748)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:753)
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:1146)
at org.apache.aries.tx.control.jpa.common.impl.EntityManagerWrapper.persist(EntityManagerWrapper.java:42)
at org.apache.aries.tx.control.jpa.common.impl.EntityManagerWrapper.persist(EntityManagerWrapper.java:42)
at org.apache.aries.tx.control.itests.XAJPATransactionTest.lambda$0(XAJPATransactionTest.java:292)
at org.apache.aries.tx.control.itests.XAJPATransactionTest$$Lambda$38/892040710.call(Unknown Source)
at org.apache.aries.tx.control.service.common.impl.AbstractTransactionControlImpl$TransactionBuilderImpl.doWork(AbstractTransactionControlImpl.java:155)
at org.apache.aries.tx.control.service.common.impl.AbstractTransactionControlImpl$TransactionBuilderImpl.required(AbstractTransactionControlImpl.java:78)
at org.apache.aries.tx.control.service.common.impl.AbstractTransactionControlImpl.required(AbstractTransactionControlImpl.java:243)
at org.apache.aries.tx.control.itests.XAJPATransactionTest.testTwoPhaseCommit(XAJPATransactionTest.java:285)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.ops4j.pax.exam.invoker.junit.internal.ContainerTestRunner.runChild(ContainerTestRunner.java:68)
at org.ops4j.pax.exam.invoker.junit.internal.ContainerTestRunner.runChild(ContainerTestRunner.java:37)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at org.ops4j.pax.exam.invoker.junit.internal.JUnitProbeInvoker.invokeViaJUnit(JUnitProbeInvoker.java:124)
at org.ops4j.pax.exam.invoker.junit.internal.JUnitProbeInvoker.findAndInvoke(JUnitProbeInvoker.java:97)
at org.ops4j.pax.exam.invoker.junit.internal.JUnitProbeInvoker.call(JUnitProbeInvoker.java:73)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.ops4j.pax.swissbox.framework.RemoteFrameworkImpl.invokeMethodOnService(RemoteFrameworkImpl.java:433)
at org.ops4j.pax.swissbox.framework.RemoteFrameworkImpl.invokeMethodOnService(RemoteFrameworkImpl.java:406)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:323)
at sun.rmi.transport.Transport$1.run(Transport.java:178)
at sun.rmi.transport.Transport$1.run(Transport.java:175)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.Transport.serviceCall(Transport.java:174)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:557)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:812)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:671)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalArgumentException: Can not set java.lang.Integer field org.apache.aries.tx.control.itests.entity.Message.id to org.apache.aries.tx.control.itests.entity.Message
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:58)
at sun.reflect.UnsafeObjectFieldAccessorImpl.get(UnsafeObjectFieldAccessorImpl.java:36)
at java.lang.reflect.Field.get(Field.java:387)
at org.hibernate.property.access.spi.GetterFieldImpl.get(GetterFieldImpl.java:39)
... 65 more

This exception indicates that the EntityManagerFactory has discovered the wrong type. I confirmed this by introspecting the MetaModel, which had loaded the Entity type from persistence unit xa-test-unit-1, not xa-test-unit-2. I have attached the persistence unit descriptors if those are of interest.

My guess at the source of the problem is that the scanner has found the right file in the bundle, but by the time it comes to be loaded Hibernate has lost the context of which persistence unit the type should be loaded from. If Hibernate just uses the OSGiClassLoaderService then it will find the Entity from whichever persistence unit first registered. I am not sure why this only sometimes fails.

Environment

None

Activity

Show:
Brett Meyer
June 24, 2016, 8:52 PM

Ugh, good catch gents.

The original use of OsgiPersistenceProviderService/OsgiSessionFactoryService was unmanaged – application bundles requesting an EMF/SF on their own, with therefore limited "requestingBundle" scope. I now see that the Aries managed JPA path grabbing only a single OsgiPersistenceProvider, and the requestingBundle being the entire container, being extremely problematic.

, I'm leaning on you to decide the "correct" way of doing things, since Aries is our primary provider of managed JPA. I appreciate the fix of ARIES-1575. But, would we be better off in the long run with the fixes Steve suggested? Essentially, the variables in OsgiPersistenceProviderService would be moved to OsgiPersistenceProvider itself and created on-demand, one per EntityManagerFactory. Seems reasonable enough.

Even with with ARIES-1575, I'm wondering if other nasty CL-related side-effects could be hiding...

TimothyW
June 27, 2016, 12:11 PM

The original use of OsgiPersistenceProviderService/OsgiSessionFactoryService was unmanaged – application bundles requesting an EMF/SF on their own, with therefore limited "requestingBundle" scope. I now see that the Aries managed JPA path grabbing only a single OsgiPersistenceProvider, and the requestingBundle being the entire container, being extremely problematic.

Tim Ward, I'm leaning on you to decide the "correct" way of doing things, since Aries is our primary provider of managed JPA. I appreciate the fix of ARIES-1575. But, would we be better off in the long run with the fixes Steve suggested? Essentially, the variables in OsgiPersistenceProviderService would be moved to OsgiPersistenceProvider itself and created on-demand, one per EntityManagerFactory. Seems reasonable enough.

I can categorically state two things:

  • What Hibernate is doing is absolutely acceptable within the OSGi spec, and is following a typical usage pattern for a Service Factory.

  • Any OSGi/JPA code which uses its own bundle context rather than the bundle context of the persistence bundle to get a PersistenceProvider service is doing the wrong thing

Having done some background research I can also say that, sadly, all the OSGi/JPA code out there is doing the wrong thing. This includes the Aries JPA container (until the fix for Aries-1575), the OSGi JPA service RI, the Geronimo spec bundle, basically everything that I could find. I also note that the Hibernate provided JPA API doesn't provide OSGi integration at all.

I have already fixed the Aries JPA container, and I can do some work here to improve things in Aries JPA (e.g. providing a properly working JPA API bundle), but I don't have the ability to make changes to EclipseLink, or anywhere else that's doing this wrong.

There is also one further problem, which comes from the Persistence#getPersistenceUnitUtil() method. This has no scope for the persistence bundle, and so has to use a single PersistenceProvider for everything.

Even with with ARIES-1575, I'm wondering if other nasty CL-related side-effects could be hiding...

Having had another careful look at the Hibernate code, I'm pretty sure that it's safe. I'm also not sure that you want to move away from the Service Factory model because the public Hibernate docs talk about being able to use the PersistenceProvider service directly. I don't see how this will work with a singleton PersistenceProvider.

It should be noted that these problems don't affect other providers because they offer no native OSGi support, they rely on containers like Gemini JPA or Aries JPA for everything.

I think my views can be summarised as follows:

  • Aries JPA should provide a working version of the JPA API. This should fix the bootstrap factory in OSGi when using Hibernate. Using the bootstrap factory is supported (although discouraged) by the OSGi JPA spec.

  • Hibernate should probably not change anything in relation to this. The Hibernate code is following both the letter and the spirit of the OSGi core spec in providing its service. You shouldn't have to lose documented functionality because other libraries get it wrong.

Steve Ebersole
June 27, 2016, 3:51 PM

Yes Persistence#getPersistenceUnitUtil is a shame. It assumes bytecode instrumentation and the caller having the ability to access the proper context from the instrumentation. All in all it is worthless.

So let's reject this as being fixed by ARIES-1575.

if you think it is a good idea to offer support for this other paradigm through a separate provider, etc then let's do that via a new RFE Jira referring to the discussion here.

Steve Ebersole
June 27, 2016, 3:56 PM

Hibernate follows the letter and spirit of the OSGi spec here. It is actually the providers that offer JPA support into OSGi that are doing the wrong thing. provided a fix for this within Aries (see https://issues.apache.org/jira/browse/ARIES-1575). Since Hibernate technically only reports built-in support OSGi through Aries, that is good enough for us for now.

Brett Meyer
June 28, 2016, 5:17 PM

Hey , thanks very much for the detail!

I also note that the Hibernate provided JPA API doesn't provide OSGi integration at all.

It should be supported in 2.1.0. See and JPA-52. Seeing anything off that would suggest otherwise?

Assignee

Brett Meyer

Reporter

TimothyW

Fix versions

None

Labels

None

backPortable

None

Suitable for new contributors

None

Requires Release Note

None

Pull Request

None

backportDecision

None

Components

Affects versions

Priority

Major
Configure