All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.datanucleus.store.mongodb.MongoDBPersistenceHandler Maven / Gradle / Ivy

/**********************************************************************
Copyright (c) 2011 Andy Jefferson. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Contributors :
    ...
***********************************************************************/
package org.datanucleus.store.mongodb;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.MongoException;
import com.mongodb.WriteConcern;

import org.bson.types.ObjectId;
import org.datanucleus.ExecutionContext;
import org.datanucleus.exceptions.NucleusDataStoreException;
import org.datanucleus.exceptions.NucleusObjectNotFoundException;
import org.datanucleus.exceptions.NucleusOptimisticException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.identity.IdentityUtils;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.FieldPersistenceModifier;
import org.datanucleus.metadata.IdentityType;
import org.datanucleus.metadata.VersionMetaData;
import org.datanucleus.state.DNStateManager;
import org.datanucleus.store.AbstractPersistenceHandler;
import org.datanucleus.store.StoreData;
import org.datanucleus.store.StoreManager;
import org.datanucleus.store.connection.ManagedConnection;
import org.datanucleus.store.fieldmanager.DeleteFieldManager;
import org.datanucleus.store.mongodb.fieldmanager.FetchFieldManager;
import org.datanucleus.store.mongodb.fieldmanager.StoreFieldManager;
import org.datanucleus.store.schema.table.Column;
import org.datanucleus.store.schema.table.SurrogateColumnType;
import org.datanucleus.store.schema.table.Table;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;

public class MongoDBPersistenceHandler extends AbstractPersistenceHandler
{
    public static final String OP_DB_OBJECT = "DB_OBJECT";

    public MongoDBPersistenceHandler(StoreManager storeMgr)
    {
        super(storeMgr);
    }

    public void close()
    {
    }

    /* (non-Javadoc)
     * @see org.datanucleus.store.AbstractPersistenceHandler#insertObjects(org.datanucleus.state.DNStateManager[])
     */
    @Override
    public void insertObjects(DNStateManager... sms)
    {
        if (sms.length == 1)
        {
            insertObject(sms[0]);
            return;
        }

        // Process "identity" cases first in case they are referenced
        for (DNStateManager sm : sms)
        {
            AbstractClassMetaData cmd = sm.getClassMetaData();
            if (cmd.pkIsDatastoreAttributed(storeMgr))
            {
                insertObject(sm);
            }
        }

        ExecutionContext ec = sms[0].getExecutionContext();
        ManagedConnection mconn = storeMgr.getConnectionManager().getConnection(ec);
        try
        {
            DB db = (DB)mconn.getConnection();

            // Separate the objects to be persisted into groups, for the "table" in question
            Map> smsByTable = new HashMap<>();
            for (DNStateManager sm : sms)
            {
                AbstractClassMetaData cmd = sm.getClassMetaData();
                if (!cmd.pkIsDatastoreAttributed(storeMgr))
                {
                    StoreData sd = storeMgr.getStoreDataForClass(cmd.getFullClassName());
                    if (sd == null)
                    {
                        // Make sure schema exists, using this connection
                        ((MongoDBStoreManager)storeMgr).manageClasses(new String[] {cmd.getFullClassName()}, ec.getClassLoaderResolver(), db);
                        sd = storeMgr.getStoreDataForClass(cmd.getFullClassName());
                    }
                    Table table = sd.getTable();
                    String tableName = table.getName();
                    Set smsForTable = smsByTable.get(tableName);
                    if (smsForTable == null)
                    {
                        smsForTable = new HashSet<>();
                        smsByTable.put(tableName, smsForTable);
                    }
                    smsForTable.add(sm);
                }
            }

            for (Map.Entry> smsEntry : smsByTable.entrySet())
            {
                String tableName = smsEntry.getKey();
                Set smsForTable = smsEntry.getValue();
                try
                {
                    long startTime = System.currentTimeMillis();

                    DBCollection collection = db.getCollection(tableName);
                    DBObject[] dbObjects = new DBObject[smsForTable.size()];
                    int i=0;
                    for (DNStateManager sm : smsForTable)
                    {
                        assertReadOnlyForUpdateOfObject(sm);

                        if (NucleusLogger.DATASTORE_PERSIST.isDebugEnabled())
                        {
                            NucleusLogger.DATASTORE_PERSIST.debug(Localiser.msg("MongoDB.Insert.Start", sm.getObjectAsPrintable(), sm.getInternalObjectId()));
                        }

                        dbObjects[i] = getDBObjectForStateManagerToInsert(sm, true);

                        if (NucleusLogger.DATASTORE_PERSIST.isDebugEnabled())
                        {
                            NucleusLogger.DATASTORE_PERSIST.debug(Localiser.msg("MongoDB.Insert.ObjectPersisted", sm.getObjectAsPrintable(), sm.getInternalObjectId()));
                        }

                        i++;
                    }

                    if (NucleusLogger.DATASTORE_NATIVE.isDebugEnabled())
                    {
                        NucleusLogger.DATASTORE_NATIVE.debug("Persisting objects using collection.insert(" + StringUtils.objectArrayToString(dbObjects) + ") into table " + tableName);
                    }
                    collection.insert(dbObjects, new WriteConcern(1));
                    if (ec.getStatistics() != null)
                    {
                        ec.getStatistics().incrementNumWrites();
                        for (int j=0;j 0)
                {
                    sm.provideFields(fieldNumbers, fieldManager);
                    NucleusLogger.DATASTORE_NATIVE.debug("Saving object " + sm + " as " + dbObject);
                    collection.save(dbObject);
                    if (ec.getStatistics() != null)
                    {
                        ec.getStatistics().incrementNumWrites();
                    }
                }
            }
            else
            {
                if (NucleusLogger.DATASTORE_PERSIST.isDebugEnabled())
                {
                    NucleusLogger.DATASTORE_PERSIST.debug(Localiser.msg("MongoDB.Insert.ObjectPersisted", sm.getObjectAsPrintable(), sm.getInternalObjectId()));
                }
            }

            if (NucleusLogger.DATASTORE_PERSIST.isDebugEnabled())
            {
                NucleusLogger.DATASTORE_PERSIST.debug(Localiser.msg("MongoDB.ExecutionTime", (System.currentTimeMillis() - startTime)));
            }
            if (ec.getStatistics() != null)
            {
                ec.getStatistics().incrementInsertCount();
            }
        }
        catch (MongoException me)
        {
            NucleusLogger.PERSISTENCE.error("Exception inserting object " + sm, me);
            throw new NucleusDataStoreException("Exception inserting object for " + sm, me);
        }
        finally
        {
            mconn.release();
        }
    }

    /**
     * Convenience method to populate the DBObject for the object managed by StateManager.
     * @param sm StateManager
     * @return The DBObject to persist
     */
    private DBObject getDBObjectForStateManagerToInsert(DNStateManager sm, boolean includeRelationFields)
    {
        DBObject dbObject = new BasicDBObject();
        AbstractClassMetaData cmd = sm.getClassMetaData();
        Table table = storeMgr.getStoreDataForClass(cmd.getFullClassName()).getTable();
        ExecutionContext ec = sm.getExecutionContext();

        if (cmd.getIdentityType() == IdentityType.DATASTORE && !storeMgr.isValueGenerationStrategyDatastoreAttributed(cmd, -1))
        {
            // Add surrogate datastore identity field (if using identity then just uses "_id" MongoDB special)
            String fieldName = table.getSurrogateColumn(SurrogateColumnType.DATASTORE_ID).getName();
            Object key = IdentityUtils.getTargetKeyForDatastoreIdentity(sm.getInternalObjectId());
            dbObject.put(fieldName, key);
        }

        if (cmd.hasDiscriminatorStrategy())
        {
            // Discriminator field
            dbObject.put(table.getSurrogateColumn(SurrogateColumnType.DISCRIMINATOR).getName(), cmd.getDiscriminatorValue());
        }

        Column multitenancyCol = table.getSurrogateColumn(SurrogateColumnType.MULTITENANCY);
        if (multitenancyCol != null)
        {
            String tenantId = ec.getTenantId();
            if (tenantId != null)
            {
                // Multi-tenancy discriminator
                dbObject.put(multitenancyCol.getName(), ec.getTenantId());
            }
        }

        Column softDeleteCol = table.getSurrogateColumn(SurrogateColumnType.SOFTDELETE);
        if (softDeleteCol != null)
        {
            // Soft-delete flag
            dbObject.put(softDeleteCol.getName(), Boolean.FALSE);
        }

        VersionMetaData vermd = cmd.getVersionMetaDataForClass();
        if (vermd != null)
        {
            Object versionValue = ec.getLockManager().getNextVersion(vermd, null);
            if (vermd.getFieldName() != null)
            {
                // Version is stored in a member, so update the member too
                AbstractMemberMetaData verMmd = cmd.getMetaDataForMember(vermd.getFieldName());
                Object verFieldValue = Long.valueOf((Long)versionValue);
                if (verMmd.getType() == int.class || verMmd.getType() == Integer.class)
                {
                    verFieldValue = Integer.valueOf(((Long)versionValue).intValue());
                }
                sm.replaceField(verMmd.getAbsoluteFieldNumber(), verFieldValue);
            }
            else
            {
                String fieldName = table.getSurrogateColumn(SurrogateColumnType.VERSION).getName();
                dbObject.put(fieldName, versionValue);
            }
            sm.setTransactionalVersion(versionValue);
        }

        StoreFieldManager fieldManager = new StoreFieldManager(sm, dbObject, true, table);
        int[] fieldNumbers = cmd.getAllMemberPositions();
        if (!includeRelationFields)
        {
            fieldNumbers = cmd.getNonRelationMemberPositions(ec.getClassLoaderResolver());
        }
        sm.provideFields(fieldNumbers, fieldManager);

        return dbObject;
    }

    public void updateObject(DNStateManager sm, int[] fieldNumbers)
    {
        assertReadOnlyForUpdateOfObject(sm);

        ExecutionContext ec = sm.getExecutionContext();
        ManagedConnection mconn = storeMgr.getConnectionManager().getConnection(ec);
        try
        {
            DB db = (DB)mconn.getConnection();

            long startTime = System.currentTimeMillis();

            AbstractClassMetaData cmd = sm.getClassMetaData();
            StoreData sd = storeMgr.getStoreDataForClass(cmd.getFullClassName());
            if (sd == null)
            {
                // Make sure schema exists, using this connection
                ((MongoDBStoreManager)storeMgr).manageClasses(new String[] {cmd.getFullClassName()}, ec.getClassLoaderResolver(), db);
                sd = storeMgr.getStoreDataForClass(cmd.getFullClassName());
            }
            Table table = sd.getTable();
            if (NucleusLogger.DATASTORE_PERSIST.isDebugEnabled())
            {
                StringBuilder fieldStr = new StringBuilder();
                for (int i=0;i 0)
                    {
                        fieldStr.append(",");
                    }
                    fieldStr.append(cmd.getMetaDataForManagedMemberAtAbsolutePosition(fieldNumbers[i]).getName());
                }
                NucleusLogger.DATASTORE_PERSIST.debug(Localiser.msg("MongoDB.Update.Start", sm.getObjectAsPrintable(), sm.getInternalObjectId(), fieldStr.toString()));
            }

            DBCollection collection = db.getCollection(table.getName());
            DBObject dbObject = MongoDBUtils.getObjectForStateManager(collection, sm, true, true);
            if (dbObject == null)
            {
                if (cmd.isVersioned())
                {
                    throw new NucleusOptimisticException("Object with id " + sm.getInternalObjectId() + " and version " + sm.getTransactionalVersion() + " no longer present");
                }
                throw new NucleusDataStoreException("Could not find object with id " + sm.getInternalObjectId());
            }

            int[] updatedFieldNums = fieldNumbers;
            VersionMetaData vermd = cmd.getVersionMetaDataForClass();
            if (vermd != null)
            {
                // Version object so calculate version to store with
                Object currentVersion = sm.getTransactionalVersion();
                Object nextVersion = ec.getLockManager().getNextVersion(vermd, currentVersion);
                sm.setTransactionalVersion(nextVersion);

                if (vermd.getFieldName() != null)
                {
                    // Update the version field value
                    AbstractMemberMetaData verMmd = cmd.getMetaDataForMember(vermd.getFieldName());
                    sm.replaceField(verMmd.getAbsoluteFieldNumber(), nextVersion);

                    boolean updatingVerField = false;
                    for (int i=0;i 0)
                    {
                        str.append(",");
                    }
                    str.append(cmd.getMetaDataForManagedMemberAtAbsolutePosition(fieldNumbers[i]).getName());
                }
                str.append("]");
                NucleusLogger.DATASTORE_RETRIEVE.debug(str.toString());
            }

            long startTime = System.currentTimeMillis();
            if (NucleusLogger.DATASTORE_RETRIEVE.isDebugEnabled())
            {
                NucleusLogger.DATASTORE_RETRIEVE.debug(Localiser.msg("MongoDB.Fetch.Start", sm.getObjectAsPrintable(), sm.getInternalObjectId()));
            }

            Table table = storeMgr.getStoreDataForClass(cmd.getFullClassName()).getTable();
            DBObject dbObject = (DBObject) sm.getAssociatedValue(OP_DB_OBJECT);
            if (dbObject == null)
            {
                DBCollection collection = db.getCollection(table.getName());
                dbObject = MongoDBUtils.getObjectForStateManager(collection, sm, false, false);
                if (dbObject == null)
                {
                    throw new NucleusObjectNotFoundException("Could not find object with id " + IdentityUtils.getPersistableIdentityForId(sm.getInternalObjectId()));
                }
            }

            // Strip out any non-persistent fields
            Set nonpersistableFields = null;
            for (int i = 0; i < fieldNumbers.length; i++)
            {
                AbstractMemberMetaData mmd = cmd.getMetaDataForManagedMemberAtAbsolutePosition(fieldNumbers[i]);
                if (mmd.getPersistenceModifier() != FieldPersistenceModifier.PERSISTENT)
                {
                    if (nonpersistableFields == null)
                    {
                        nonpersistableFields = new HashSet<>();
                    }
                    nonpersistableFields.add(fieldNumbers[i]);
                }
            }
            if (nonpersistableFields != null)
            {
                // Just go through motions for non-persistable fields
                for (Integer fieldNum : nonpersistableFields)
                {
                    sm.replaceField(fieldNum, sm.provideField(fieldNum));
                }
            }
            if (nonpersistableFields == null || nonpersistableFields.size() != fieldNumbers.length)
            {
                if (nonpersistableFields != null)
                {
                    // Strip out any nonpersistable fields
                    int[] persistableFieldNums = new int[fieldNumbers.length - nonpersistableFields.size()];
                    int pos = 0;
                    for (int i = 0; i < fieldNumbers.length; i++)
                    {
                        if (!nonpersistableFields.contains(fieldNumbers[i]))
                        {
                            persistableFieldNums[pos++] = fieldNumbers[i];
                        }
                    }
                    fieldNumbers = persistableFieldNums;
                }
                FetchFieldManager fieldManager = new FetchFieldManager(sm, dbObject, table);
                sm.replaceFields(fieldNumbers, fieldManager);

                VersionMetaData vermd = cmd.getVersionMetaDataForClass();
                if (vermd != null && sm.getTransactionalVersion() == null)
                {
                    // No version set, so retrieve it (note we do this after the retrieval of fields in case just got version)
                    if (vermd.getFieldName() != null)
                    {
                        // Version stored in a field
                        Object datastoreVersion = sm.provideField(cmd.getAbsolutePositionOfMember(vermd.getFieldName()));
                        sm.setVersion(datastoreVersion);
                    }
                    else
                    {
                        // Surrogate version
                        String fieldName = table.getSurrogateColumn(SurrogateColumnType.VERSION).getName();
                        Object datastoreVersion = dbObject.get(fieldName);
                        sm.setVersion(datastoreVersion);
                    }
                }
            }

            if (NucleusLogger.DATASTORE_RETRIEVE.isDebugEnabled())
            {
                NucleusLogger.DATASTORE_RETRIEVE.debug(Localiser.msg("MongoDB.ExecutionTime", (System.currentTimeMillis() - startTime)));
            }
            if (ec.getStatistics() != null)
            {
                ec.getStatistics().incrementFetchCount();
            }
        }
        finally
        {
            mconn.release();
        }
    }

    public Object findObject(ExecutionContext om, Object id)
    {
        return null;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.store.AbstractPersistenceHandler#locateObjects(org.datanucleus.state.DNStateManager[])
     */
    @Override
    public void locateObjects(DNStateManager[] sms)
    {
        // TODO Implement bulk location of objects. Split into DBCollections, then do bulk get
        super.locateObjects(sms);
    }

    public void locateObject(DNStateManager sm)
    {
        final AbstractClassMetaData cmd = sm.getClassMetaData();
        if (cmd.getIdentityType() == IdentityType.APPLICATION || cmd.getIdentityType() == IdentityType.DATASTORE)
        {
            ExecutionContext ec = sm.getExecutionContext();
            ManagedConnection mconn = storeMgr.getConnectionManager().getConnection(ec);
            try
            {
                DB db = (DB)mconn.getConnection();

                StoreData sd = storeMgr.getStoreDataForClass(cmd.getFullClassName());
                if (sd == null)
                {
                    // Make sure schema exists, using this connection
                    ((MongoDBStoreManager)storeMgr).manageClasses(new String[] {cmd.getFullClassName()}, ec.getClassLoaderResolver(), db);
                    sd = storeMgr.getStoreDataForClass(cmd.getFullClassName());
                }
                Table table = sd.getTable();
                DBCollection collection = db.getCollection(table.getName());
                DBObject dbObject = MongoDBUtils.getObjectForStateManager(collection, sm, false, false);
                if (dbObject == null)
                {
                    throw new NucleusObjectNotFoundException();
                }
            }
            finally
            {
                mconn.release();
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy