Issue with a relationship to an interface


Page bloom
 

I'm in the very early stages of debugging a unit test that is failing with an exception when establishing a PMF for a model that has classes with references to interfaces.

This exact model works fine in our main application which has me baffled.

I'm seeing 'Creating SerialisedMapping" - which isn't what we want for this interface relationship and we've got the metadata to avoid this (we think):

The interface is ISubscribable and the relationship field in the owning class is called subscribable so metadata for this field is:

 

        <field name="subscribable" persistence-modifier="persistent">

            <extension vendor-name="datanucleus" key="mapping-strategy" value="per-implementation"/>

        </field>

I can't work out why it is trying to create a SerialisedMapping for this and then complains that it can't be cast to PeristableMapping - it seems to be straddling both worlds, maybe due to some weirdness in our metadata.

 
The exception is:

setup really failed: javax.jdo.JDOFatalInternalException: Failed to generate new Mapping of type org.datanucleus.store.rdbms.mapping.java.InterfaceMapping, exception : org.datanucleus.store.rdbms.mapping.java.SerialisedMapping cannot be cast to org.datanucleus.store.rdbms.mapping.java.PersistableMapping
java.lang.ClassCastException: org.datanucleus.store.rdbms.mapping.java.SerialisedMapping cannot be cast to org.datanucleus.store.rdbms.mapping.java.PersistableMapping
        at org.datanucleus.store.rdbms.table.ColumnCreator.createColumnsForField(ColumnCreator.java:261)
        at org.datanucleus.store.rdbms.mapping.java.ReferenceMapping.createPerImplementationColumnsForReferenceField(ReferenceMapping.java:488)
        at org.datanucleus.store.rdbms.mapping.java.ReferenceMapping.prepareDatastoreMapping(ReferenceMapping.java:230)
        at org.datanucleus.store.rdbms.mapping.java.ReferenceMapping.initialize(ReferenceMapping.java:104)
        at org.datanucleus.store.rdbms.mapping.java.InterfaceMapping.initialize(InterfaceMapping.java:52)
        at org.datanucleus.store.rdbms.mapping.RDBMSMappingManager.getMapping(RDBMSMappingManager.java:482)
        at org.datanucleus.store.rdbms.table.ClassTable.manageMembers(ClassTable.java:536)
        at org.datanucleus.store.rdbms.table.ClassTable.manageClass(ClassTable.java:442)
        at org.datanucleus.store.rdbms.table.ClassTable.initializeForClass(ClassTable.java:1270)
        at org.datanucleus.store.rdbms.table.ClassTable.initialize(ClassTable.java:276)
        at org.datanucleus.store.rdbms.RDBMSStoreManager$ClassAdder.initializeClassTables(RDBMSStoreManager.java:3279)
        at org.datanucleus.store.rdbms.RDBMSStoreManager$ClassAdder.run(RDBMSStoreManager.java:2889)
        at org.datanucleus.store.rdbms.AbstractSchemaTransaction.execute(AbstractSchemaTransaction.java:119)
        at org.datanucleus.store.rdbms.RDBMSStoreManager.createSchemaForClasses(RDBMSStoreManager.java:3877)
        at org.datanucleus.store.schema.SchemaTool.createSchemaForClasses(SchemaTool.java:499)
        at org.datanucleus.PersistenceNucleusContextImpl.initialiseSchema(PersistenceNucleusContextImpl.java:953)
        at org.datanucleus.PersistenceNucleusContextImpl.initialise(PersistenceNucleusContextImpl.java:481)
        at org.datanucleus.api.jdo.JDOPersistenceManagerFactory.freezeConfiguration(JDOPersistenceManagerFactory.java:817)
        at org.datanucleus.api.jdo.JDOPersistenceManagerFactory.createPersistenceManagerFactory(JDOPersistenceManagerFactory.java:334)
        at org.datanucleus.api.jdo.JDOPersistenceManagerFactory.getPersistenceManagerFactory(JDOPersistenceManagerFactory.java:213)
        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:498)
        at javax.jdo.JDOHelper$16.run(JDOHelper.java:1975)
        at java.security.AccessController.doPrivileged(Native Method)
        at javax.jdo.JDOHelper.invoke(JDOHelper.java:1970)
        at javax.jdo.JDOHelper.invokeGetPersistenceManagerFactoryOnImplementation(JDOHelper.java:1177)
        at javax.jdo.JDOHelper.getPersistenceManagerFactory(JDOHelper.java:814)
        at javax.jdo.JDOHelper.getPersistenceManagerFactory(JDOHelper.java:702)


Andy
 

So demonstrate it or otherwise get the code for datanucleus-rdbms and debug from RDBMSMappingManager as to why it is choosing that type


Page bloom
 

I'm planning to create a minimalist class model that demonstrates the issue - but, as usual with our massive model, it will take some time to work out the minimalist chunk of it that exhibits the issue.

I was just putting it out there early in case someone has seen this before and dealt with it.

It seems to be when reading/processing the metadata - not when attempting to build the schema because it happens in the unit test's setUp method whether or not

datanucleus.generateSchema.database.mode=create

is present or commented out.

This happens with 5.1.6 and 5.1.7 and so I rolled back to 4.x and it happens with that also.


Page bloom
 

On Wed, Mar 21, 2018 at 12:00 am, Andy wrote:
RDBMSMappingManager
I can't find that specific class. Did you mean MappingManagerImpl in the *.rdbms package?


Andy
 

I meant the class that YOUR stack trace quotes. In current codebase there is no such class (but there is MappingManagerImpl). In whatever version you're using there is that class.


Page bloom
 

Ah doh!

That stack dump must have been when I reverted to 4.x to test it in that version and I've now gone back to 5.x.

RDBMSMappingManager is obviously a Version 4.x class and renamed to something else in 5.x.

Sorry, my bad!


Page bloom
 

I'm now actually getting this exception in the main app during start up.

I'm not sure if I've got the logging setup correctly (recently switched to Log4j 2 and unsure how to use the DataNucleus categories in that version) but the exception is thrown without giving any indication which class or attribute is causing the issue:

Failed to generate new Mapping of type org.datanucleus.store.rdbms.mapping.java.InterfaceMapping, exception : org.datanucleus.store.rdbms.mapping.java.SerialisedMapping cannot be cast to org.datanucleus.store.rdbms.mapping.java.PersistableMapping
java.lang.ClassCastException: org.datanucleus.store.rdbms.mapping.java.SerialisedMapping cannot be cast to org.datanucleus.store.rdbms.mapping.java.PersistableMapping
at org.datanucleus.store.rdbms.table.ColumnCreator.createColumnsForField(ColumnCreator.java:308)
at org.datanucleus.store.rdbms.mapping.java.ReferenceMapping.createPerImplementationColumnsForReferenceField(ReferenceMapping.java:488)
at org.datanucleus.store.rdbms.mapping.java.ReferenceMapping.prepareDatastoreMapping(ReferenceMapping.java:230)
at org.datanucleus.store.rdbms.mapping.java.ReferenceMapping.initialize(ReferenceMapping.java:104)
at org.datanucleus.store.rdbms.mapping.java.InterfaceMapping.initialize(InterfaceMapping.java:52)
at org.datanucleus.store.rdbms.mapping.MappingManagerImpl.getMapping(MappingManagerImpl.java:667)
at org.datanucleus.store.rdbms.table.ClassTable.manageMembers(ClassTable.java:549)
at org.datanucleus.store.rdbms.table.ClassTable.manageClass(ClassTable.java:455)
at org.datanucleus.store.rdbms.RDBMSStoreManager$ClassAdder.initializeClassTables(RDBMSStoreManager.java:3347)
at org.datanucleus.store.rdbms.RDBMSStoreManager$ClassAdder.run(RDBMSStoreManager.java:2961)
at org.datanucleus.store.rdbms.AbstractSchemaTransaction.execute(AbstractSchemaTransaction.java:118)
at org.datanucleus.store.rdbms.RDBMSStoreManager.manageClasses(RDBMSStoreManager.java:1672)
 
I am thinking of ways to change the DN source to give an indication of the class/field that is causing the issue to make it easier for people to debug their particular issues.

The code where the above exception is raised is in:

ColumnCreator#createColumnsForField (line 308)

                    ((PersistableMapping)container).addJavaTypeMapping(refDatastoreMapping);   <--- Cast fails

Once the exception is raised it is handled at a higher level where, presumably, specific field information is no longer available.

Would it be viable to test if container was an instanceof 'PersistableMapping' and throw a DN exception if not, passing in the field data?

eg 

if (container instanceof PersistableMapping) 
    ((PersistableMapping)container).addJavaTypeMapping(refDatastoreMapping);
else
    throw new NucleusUserException("Cannot create column for field " + mmd.getFullFieldName());

or is it best to wrap it in a try/catch?

try
{
    ((PersistableMapping)container).addJavaTypeMapping(refDatastoreMapping);
}
catch(ClassCastException cce)
{
    throw new NucleusUserException("Cannot create column for field " + mmd.getFullFieldName(), cce);
}


Andy
 

It would be best to work out where the problem is in your mapping+classes and hence why you get some problem. Once you see that then you can put a trap at the root cause. Aka work backwards from where you are looking


Page bloom
 

When I ran my app against a version of dn-rdbms with this change (pull request created) it revealed, for the first time, which PC class + attribute was causing the issue that resulted in the exception.

So at least this change enables me to drastically reduce the scope of my search for the root cause :)  (which is still a bit of a mystery at the moment)


Page bloom
 
Edited

The problem seemed to be intermittent when we had the implementing class in a different package. Once we moved it into the same package it didn't happen again - but on another dev machine it worked fine while in the different package - strange.

Anyway, the DataNucleus docs do mention some non deterministic behaviour if the metadata of implementations is not experienced prior to experiencing the class with the relationship to the interface. The docs say to avoid this non deterministic behaviour specify the implementation classes in the metadata (which we have now done)

eg.
field-type="com.sas.blah.blah.ImplementationClass"

Everything seems to go just swimmingly now :) (because terms from the 1920s need a revival...)

I still think the change in the pull request would be useful to anyone else who finds themselves in this situation as it helps to narrow down the exact class+attribute causing the issue.