About Me

My photo
I know the last digit of PI

Tuesday, November 22, 2011

Toplink throws NullPointerException in method _toplink.getXXX_vh()

If you persist JPA entities from another JVM sometimes Toplink throws very strange exception like:
Caused by: javax.persistence.EntityExistsException: 
Exception Description: The method [_toplink_getscore_vh] on the object [com.mycompany.data.GameEntity] triggered an exception.
Internal Exception: java.lang.reflect.InvocationTargetException
Target Invocation Exception: java.lang.NullPointerException
Mapping: oracle.toplink.essentials.mappings.OneToOneMapping[score]
Descriptor: RelationalDescriptor(com.mycompany.data.GameEntity --> [DatabaseTable(GAME_TABLE)])
 at oracle.toplink.essentials.internal.ejb.cmp3.base.EntityManagerImpl.persist(EntityManagerImpl.java:224)
 at com.sun.enterprise.util.EntityManagerWrapper.persist(EntityManagerWrapper.java:440)
 at com.mycompany.MyEjbBean.create(MyEjbBean.java:1647)
 ... 26 more
Caused by: Exception [TOPLINK-99] (Oracle TopLink Essentials - 2.1 (Build b31g-fcs (10/19/2009))): oracle.toplink.essentials.exceptions.DescriptorException
Exception Description: The method [_toplink_getscore_vh] on the object [com.mycompany.data.GameEntity] triggered an exception.
Internal Exception: java.lang.reflect.InvocationTargetException
Target Invocation Exception: java.lang.NullPointerException
 at oracle.toplink.essentials.exceptions.DescriptorException.targetInvocationWhileGettingValueThruMethodAccessor(DescriptorException.java:1598)
 at oracle.toplink.essentials.internal.descriptors.MethodAttributeAccessor.getAttributeValueFromObject(MethodAttributeAccessor.java:98)
 at oracle.toplink.essentials.mappings.DatabaseMapping.getAttributeValueFromObject(DatabaseMapping.java:372)
 at oracle.toplink.essentials.mappings.ForeignReferenceMapping.getAttributeValueFromObject(ForeignReferenceMapping.java:322)
 at oracle.toplink.essentials.mappings.ObjectReferenceMapping.cascadeRegisterNewIfRequired(ObjectReferenceMapping.java:676)
 at oracle.toplink.essentials.internal.descriptors.ObjectBuilder.cascadeRegisterNewForCreate(ObjectBuilder.java:1294)
 at oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl.registerNewObjectForPersist(UnitOfWorkImpl.java:3228)
 at oracle.toplink.essentials.internal.ejb.cmp3.base.EntityManagerImpl.persist(EntityManagerImpl.java:221)
 ... 28 more
Caused by: java.lang.reflect.InvocationTargetException
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at oracle.toplink.essentials.internal.security.PrivilegedAccessHelper.invokeMethod(PrivilegedAccessHelper.java:322)
 at oracle.toplink.essentials.internal.descriptors.MethodAttributeAccessor.getAttributeValueFromObject(MethodAttributeAccessor.java:91)
 ... 34 more
Caused by: java.lang.NullPointerException
 at com.mycompany.data.GameEntity._toplink_getscore_vh(GameEntity.java)
 ... 40 more
javax.ejb.EJBException: nested exception is: java.rmi.ServerException: RemoteException occurred in server thread; nested exception is: 
 java.rmi.RemoteException: null; nested exception is: 
 java.lang.RuntimeException: 
 at com.mycompany._MyEjbBean_Wrapper.create(com/mycompany/_MyEjbBean_Wrapper.java)
 at com.mycompany.GameTest.testGameScores(GameTest.java:62)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
 at java.lang.reflect.Method.invoke(Unknown Source)
 at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
 at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
 at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
 at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
 at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
 at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
 at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:73)
 at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:46)
 at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:180)
 at org.junit.runners.ParentRunner.access$000(ParentRunner.java:41)
 at org.junit.runners.ParentRunner$1.evaluate(ParentRunner.java:173)
 at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
 at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
 at org.junit.runners.ParentRunner.run(ParentRunner.java:220)
 at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:46)
 at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
The solution I found is to change the score property to be eager "@ManyToOne(fetch = FetchType.EAGER)" What I belive happens beneath is that Toplink uses bytecode to creates some additional methods for entity classes - see oracle.toplink.essentials.internal.weaving.TopLinkClassWeaver. For each field that is in relation, Toplink creates a new method called _toplink.getFIELDNAME_vh() and a class field called _toplink.getFIELDNAME_vh. VH stands for ValueHolder. I guess this additional methods/fields are used for fetching related objects later. If you examine the exception more closely it is thrown from inside _toplink.getscore_vh() which according to the javadoc in TopLinkClassWeaver should looks like:
public WeavedAttributeValueHolderInterface _toplink_getscore_vh(){
  if (_toplink_score_vh.isCoordinatedWithProperty() || _toplink_score_vh.isNewlyWeavedValueHolder()){
    EntityC object = getScore();
    if (object != _toplink_score_vh.getValue()){
      setScore(object);
    }
  }
  return _toplink_score_vh;
}
Obviously the only way to throw an exception is if _toplink_score_vh is null which after debuging is confirmed to be null. My guess is that because this is entity is transferred from another JVM (the JUnit test) and you are persisting it inside server JVM the value of _toplink_score_vh is not correctly transfered, which causes the above exception. I didn't dig up too much into the problem but the solution I found is - just make the field (in my case the "score") eager and the exception will go away.