Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NPE when trying to find rootTable #25

Closed
nonstans opened this issue Jun 29, 2016 · 7 comments
Closed

NPE when trying to find rootTable #25

nonstans opened this issue Jun 29, 2016 · 7 comments

Comments

@nonstans
Copy link

In org.datanucleus.store.mongodb.MongoDBUtils.getClassNameForIdentity(Object, AbstractClassMetaData, ExecutionContext, ClassLoaderResolver) I get a NullPointerException when it is tried to retrieve the table for an abstract superclass.

Table rootTable = storeMgr.getStoreDataForClass(rootCmd.getFullClassName()).getTable();

getStoreDataForClass comes back with NULL as result for an abstract superclass. I investigated the table of the storeDataManager and it contains only entries for concrete classes, am I doing something wrong in my annotation?

@PersistenceCapable(detachable = "true", identityType = IdentityType.APPLICATION, objectIdClass = Id.class) @Discriminator(strategy = DiscriminatorStrategy.CLASS_NAME) @Inheritance(strategy = InheritanceStrategy.COMPLETE_TABLE) public abstract class SuperClass { ..

@nonstans nonstans changed the title NPE in when trying to find rootTable NPE when trying to find rootTable Jun 29, 2016
@nonstans
Copy link
Author

nonstans commented Jun 29, 2016

I further investigated the problem. I noticed that problem vanishes as soon as I don't fetch a collection in one of the subclasses. So I can successfully read a User (see example) but not a UserGroup containing a list of users.
See my schema example:
Schema.zip

Again I'm not sure whether my annotations are right? To provide a running testcase requires more time. I could prepare something tomorrow, if you need it.

StackTrace:

Caused by: java.lang.NullPointerException
at org.datanucleus.store.mongodb.MongoDBUtils.getClassNameForIdentity(MongoDBUtils.java:199)
at org.datanucleus.store.mongodb.MongoDBStoreManager.getClassNameForObjectID(MongoDBStoreManager.java:132)
at org.datanucleus.ExecutionContextImpl.getClassNameForObjectId(ExecutionContextImpl.java:3503)
at org.datanucleus.ExecutionContextImpl.getClassDetailsForId(ExecutionContextImpl.java:3392)
at org.datanucleus.ExecutionContextImpl.findObject(ExecutionContextImpl.java:3568)
at org.datanucleus.identity.IdentityUtils.getObjectFromIdString(IdentityUtils.java:442)
at org.datanucleus.store.mongodb.fieldmanager.FetchFieldManager.getValueForContainerRelationField(FetchFieldManager.java:970)
at org.datanucleus.store.mongodb.fieldmanager.FetchFieldManager.fetchNonEmbeddedObjectField(FetchFieldManager.java:737)
at org.datanucleus.store.mongodb.fieldmanager.FetchFieldManager.fetchObjectField(FetchFieldManager.java:685)
at org.datanucleus.state.AbstractStateManager.replacingObjectField(AbstractStateManager.java:1588)
at org.datanucleus.state.StateManagerImpl.replacingObjectField(StateManagerImpl.java:1)
at de.mpg.ipp.codac.business.schema.teamwork.UserGroup.dnReplaceField(UserGroup.java)
at de.mpg.ipp.codac.business.schema.common.W7XDbNamedObject.dnReplaceFields(W7XDbNamedObject.java)
at org.datanucleus.state.StateManagerImpl.replaceFields(StateManagerImpl.java:3115)
at org.datanucleus.state.StateManagerImpl.replaceFields(StateManagerImpl.java:3141)
at org.datanucleus.store.mongodb.MongoDBUtils$1.fetchFields(MongoDBUtils.java:761)
at org.datanucleus.state.StateManagerImpl.loadFieldValues(StateManagerImpl.java:2245)
at org.datanucleus.state.StateManagerImpl.initialiseForHollow(StateManagerImpl.java:262)
at org.datanucleus.state.ObjectProviderFactoryImpl.newForHollow(ObjectProviderFactoryImpl.java:112)
at org.datanucleus.ExecutionContextImpl.findObject(ExecutionContextImpl.java:3098)
at org.datanucleus.store.mongodb.MongoDBUtils.getObjectUsingApplicationIdForDBObject(MongoDBUtils.java:756)
at org.datanucleus.store.mongodb.MongoDBUtils.getPojoForDBObjectForCandidate(MongoDBUtils.java:718)
at org.datanucleus.store.mongodb.query.LazyLoadQueryResult.getNextObject(LazyLoadQueryResult.java:324)
at org.datanucleus.store.mongodb.query.LazyLoadQueryResult$QueryResultIterator.next(LazyLoadQueryResult.java:449)
at de.mpg.ipp.codac.persistence.DataNucleusProvider.doGetObjects(DataNucleusProvider.java:293)
... 31 more

@andyjefferson
Copy link
Member

As the documentation says clearly enough, and the issue template, a valid testcase is required for ALL problems. Failure to provide will will mean the issue is closed.

@andyjefferson
Copy link
Member

Closing since no testcase. Attach a testcase here and it can be reopened

@nonstans
Copy link
Author

Please reopen this issue, because this must be a big problem for everyone working with MongoDB and an abstract root class. I don't understand enough of the concepts of the StoreManager and StoreData to fix the problem by myself.

I commented the problems in the code for you (please scroll to see all comments) :

String org.datanucleus.store.mongodb.MongoDBUtils.getClassNameForIdentity(Object id, AbstractClassMetaData rootCmd, ExecutionContext ec, ClassLoaderResolver clr)
{
        Map<String, Set<String>> classNamesByDbCollectionName = new HashMap<>();
        StoreManager storeMgr = ec.getStoreManager();
        Set rootClassNames = new HashSet<String>();
        rootClassNames.add(rootCmd.getFullClassName());
        StoreData storeData = storeMgr.getStoreDataForClass(rootCmd.getFullClassName()); //comment: since my root class is abstract there is no entry in the table storeDataMgr.storeDataByClass, so storeData is null
        Table rootTable = null;
        String rootTableName = "";
        if (storeData != null)
        {
            rootTable = storeData.getTable();
            rootTableName = rootTable.getName();
        }
        else
        {
            // how to access the rootTable via name?
            rootTableName = rootCmd.getTable(); //comment: since storeData is null, rootTable is also null
        }
        classNamesByDbCollectionName.put(rootTableName, rootClassNames);
        Collection<String> subclassNames = storeMgr.getSubClassesForClass(rootCmd.getFullClassName(), true, clr);
        if (subclassNames != null && !subclassNames.isEmpty())
        {
            for (String subclassName : subclassNames)
            {
                AbstractClassMetaData cmd =  ec.getMetaDataManager().getMetaDataForClass(subclassName, clr);
                StoreData subStoreData = storeMgr.getStoreDataForClass(cmd.getFullClassName());
                Table subTable = null;
                String subTableName = "";
                if (subStoreData != null)
                {
                    subTable = subStoreData.getTable();
                    subTableName = subTable.getName();
                }
                else
                {
                    // how to access the subTable via name?
                    subTableName = cmd.getTable();
                }
                // Table subTable = storeMgr.getStoreDataForClass(cmd.getFullClassName()).getTable();
                // String subTableName = subTable.getName();
                Set<String> classNames = classNamesByDbCollectionName.get(subTableName);
                if (classNames == null)
                {
                    classNames = new HashSet<String>();
                    classNamesByDbCollectionName.put(subTableName, classNames);
                }
                classNames.add(cmd.getFullClassName());
            }
        }

        ManagedConnection mconn = storeMgr.getConnection(ec);
        try
        {
            DB db = (DB) mconn.getConnection();

            for (Map.Entry<String, Set<String>> dbCollEntry : classNamesByDbCollectionName.entrySet())
            {
                // Check each DBCollection for the id PK field(s)
                String dbCollName = dbCollEntry.getKey();
                if (dbCollName == null) continue; //comment: I added this. For some reason this is null for some classes, so I have to continue to prevent an NPE
                Set<String> classNames = dbCollEntry.getValue();                
                DBCollection dbColl = db.getCollection(dbCollName);
                BasicDBObject query = new BasicDBObject();
                if (rootCmd.getIdentityType() == IdentityType.DATASTORE)
                {
                    Object key = IdentityUtils.getTargetKeyForDatastoreIdentity(id);
                    if (storeMgr.isStrategyDatastoreAttributed(rootCmd, -1))
                    {
                        query.put("_id", new ObjectId((String) key));
                    }
                    else
                    {
                        query.put(rootTable.getDatastoreIdColumn().getName(), key);
                    }
                }
                else if (rootCmd.getIdentityType() == IdentityType.APPLICATION)
                {
                    if (IdentityUtils.isSingleFieldIdentity(id))
                    {
                        Object key = IdentityUtils.getTargetKeyForSingleFieldIdentity(id);
                        int[] pkNums = rootCmd.getPKMemberPositions();
                        AbstractMemberMetaData pkMmd = rootCmd.getMetaDataForManagedMemberAtAbsolutePosition(pkNums[0]);
                        String pkPropName = rootTable.getMemberColumnMappingForMember(pkMmd).getColumn(0).getName(); //comment: since rootTable is null, this leads to an NPE
                        query.put(pkPropName, key);
                    }
                    else
                    {
                        int[] pkNums = rootCmd.getPKMemberPositions();
                        for (int i = 0; i < pkNums.length; i++)
                        {
                            AbstractMemberMetaData pkMmd = rootCmd.getMetaDataForManagedMemberAtAbsolutePosition(pkNums[i]);
                            String pkPropName = rootTable.getMemberColumnMappingForMember(pkMmd).getColumn(0).getName();
                            Object pkVal = IdentityUtils.getValueForMemberInId(id, pkMmd);
                            query.put(pkPropName, pkVal);
                        }
                    }
                }

@nonstans
Copy link
Author

Added schema and unit tests (needs little modification for connection to your local MongoDB test database):
schema.zip
DN5UnitTest.zip

I'm working with MongoDB 2.4 and the master repository for DataNucleus sources.

@andyjefferson
Copy link
Member

Sorry, but no the DN test suite has abstract root classes and the DN test suite is run before releases, and it works great for me. The onus is on the raiser to demonstrate the problem. Just dumping some class here isn't a demonstration. As the issue template says clearly enough, we provide you with a template for testcases to minimise how much you need to do to reproduce things, and then just type "mvn clean test" and it runs. Here it is again
https://github.com/datanucleus/test-jdo

And when I dump your classes into this template I get the attached "test.zip". I run it. It passes.

If you want something reopened you kindly take this and UPDATE it and repost it here with something that reproduces a problem. Some persistable class not being known about implies metadata is not loaded for it at the time you invoke some operation, and only you know how you create your PMF (persistence-unit?, properties file? using auto-start? etc etc).

test.zip

andyjefferson added a commit that referenced this issue Sep 21, 2016
is happening. May relate to #25 or may not, but unconfirmed since no
testcase still
@nonstans
Copy link
Author

Thank you! Your commit fixed the problem.
I didn't recognize that there is a template for testcases. Sorry! Next time I'll provide a testcase using the template.

@andyjefferson andyjefferson added this to the 5.0.2 milestone Sep 22, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants