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

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

There is a newer version: 6.0.0-release
Show newest version
/**********************************************************************
Copyright (c) 2011 Andy Jefferson and others. 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:
2012 Chris Rued - patch for MapReduceCount and collection.count()
   ...
**********************************************************************/
package org.datanucleus.store.mongodb;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.bson.types.ObjectId;
import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.FetchPlan;
import org.datanucleus.PropertyNames;
import org.datanucleus.exceptions.NucleusDataStoreException;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.identity.IdentityUtils;
import org.datanucleus.identity.OID;
import org.datanucleus.identity.OIDFactory;
import org.datanucleus.identity.SCOID;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.ColumnMetaData;
import org.datanucleus.metadata.EmbeddedMetaData;
import org.datanucleus.metadata.FieldRole;
import org.datanucleus.metadata.IdentityType;
import org.datanucleus.metadata.MetaDataUtils;
import org.datanucleus.metadata.Relation;
import org.datanucleus.metadata.VersionMetaData;
import org.datanucleus.store.ExecutionContext;
import org.datanucleus.store.FieldValues;
import org.datanucleus.store.ObjectProvider;
import org.datanucleus.store.StoreManager;
import org.datanucleus.store.connection.ManagedConnection;
import org.datanucleus.store.mongodb.fieldmanager.FetchFieldManager;
import org.datanucleus.store.schema.naming.ColumnType;
import org.datanucleus.store.types.converters.TypeConverter;
import org.datanucleus.store.types.sco.SCOUtils;
import org.datanucleus.util.NucleusLogger;

import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.MongoException;
import java.util.LinkedList;

import javax.imageio.ImageIO;

/**
 * Utilities for MongoDB.
 */
public class MongoDBUtils
{
    public static List performMongoCount(DB db, BasicDBObject filterObject, Class candidateClass,
        boolean subclasses, ExecutionContext ec)
    throws MongoException 
    {
        StoreManager storeMgr = ec.getStoreManager();
        long count = 0;
        for (AbstractClassMetaData cmd : MetaDataUtils.getMetaDataForCandidates(candidateClass, subclasses, ec)) 
        {
            String collectionName = storeMgr.getNamingFactory().getTableName(cmd);
            count += db.getCollection(collectionName).count(filterObject);
        }
        List results =  new LinkedList();
        results.add(count);
        if (ec.getStatistics() != null)
        {
            // Add to statistics
            ec.getStatistics().incrementNumReads();
        }
        return results;
    }

    /**
     * Accessor for the default value specified for the provided member.
     * If no defaultValue is provided on the column then returns null.
     * @param mmd Metadata for the member
     * @return The default value
     */
    public static String getDefaultValueForMember(AbstractMemberMetaData mmd)
    {
        ColumnMetaData[] colmds = mmd.getColumnMetaData();
        if (colmds == null || colmds.length < 1)
        {
            return null;
        }
        return colmds[0].getDefaultValue();
    }

    /**
     * Accessor for the MongoDB field for the field of this embedded field.
     * Uses the column name from the embedded definition (if present), otherwise falls back to
     * the column name of the field in its own class definition, otherwise uses its field name.
     * @param mmd Metadata for the owning member
     * @param fieldNumber Member number of the embedded object
     * @return The field name to use
     */
    public static String getFieldName(AbstractMemberMetaData mmd, int fieldNumber)
    {
        String columnName = null;

        // Try the first column if specified
        EmbeddedMetaData embmd = mmd.getEmbeddedMetaData();
        AbstractMemberMetaData embMmd = null;
        if (embmd != null)
        {
            AbstractMemberMetaData[] embmmds = embmd.getMemberMetaData();
            embMmd = embmmds[fieldNumber];
        }

        ColumnMetaData[] colmds = embMmd.getColumnMetaData();
        if (colmds != null && colmds.length > 0)
        {
            columnName = colmds[0].getName();
        }
        if (columnName == null)
        {
            // Fallback to the field/property name
            columnName = embMmd.getName();
        }
        if (columnName == null)
        {
            columnName = embMmd.getName();
        }
        return columnName;
    }

    /**
     * Convenience method that tries to find the object with the specified identity from all DBCollection objects
     * from the rootCmd and subclasses. Returns the class name of the object with this identity (or null if not found).
     * @param id The identity
     * @param rootCmd ClassMetaData for the root class in the inheritance tree
     * @param ec ExecutionContext
     * @param clr ClassLoader resolver
     * @return The class name of the object with this id
     */
    public static String getClassNameForIdentity(Object id, AbstractClassMetaData rootCmd, ExecutionContext ec,
            ClassLoaderResolver clr)
    {
        Map> classNamesByDbCollectionName = new HashMap>();
        StoreManager storeMgr = ec.getStoreManager();
        Set rootClassNames = new HashSet();
        rootClassNames.add(rootCmd.getFullClassName());
        classNamesByDbCollectionName.put(storeMgr.getNamingFactory().getTableName(rootCmd), rootClassNames);
        String[] subclassNames = ec.getMetaDataManager().getSubclassesForClass(rootCmd.getFullClassName(), true);
        if (subclassNames != null)
        {
            for (int i=0;i classNames = classNamesByDbCollectionName.get(collName);
                if (classNames == null)
                {
                    classNames = new HashSet();
                    classNamesByDbCollectionName.put(collName, classNames);
                }
                classNames.add(cmd.getFullClassName());
            }
        }

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

            for (String dbCollName : classNamesByDbCollectionName.keySet())
            {
                // Check each DBCollection for the id PK field(s)
                Set classNames = classNamesByDbCollectionName.get(dbCollName);
                DBCollection dbColl = db.getCollection(dbCollName);
                BasicDBObject query = new BasicDBObject();
                if (id instanceof OID)
                {
                    // TODO Really ought to use cmd of the class using this dbCollection
                    Object key = ((OID)id).getKeyValue();
                    if (storeMgr.isStrategyDatastoreAttributed(rootCmd, -1))
                    {
                        query.put("_id", new ObjectId((String)key));
                    }
                    else
                    {
                        query.put(storeMgr.getNamingFactory().getColumnName(rootCmd, ColumnType.DATASTOREID_COLUMN), key);
                    }
                }
                else if (ec.getApiAdapter().isSingleFieldIdentity(id))
                {
                    Object key = ec.getApiAdapter().getTargetKeyForSingleFieldIdentity(id);
                    int[] pkNums = rootCmd.getPKMemberPositions();
                    AbstractMemberMetaData pkMmd = rootCmd.getMetaDataForManagedMemberAtAbsolutePosition(pkNums[0]);
                    String pkPropName = storeMgr.getNamingFactory().getColumnName(pkMmd, ColumnType.COLUMN);
                    query.put(pkPropName, key);
                }
                else
                {
                    int[] pkNums = rootCmd.getPKMemberPositions();
                    for (int i=0;i options)
    {
        return getObjectsOfCandidateType(ec, db, candidateClass, subclasses, ignoreCache, fp, filterObject, options, null, null);
    }
    
    /**
     * Convenience method to return all objects of the candidate type (optionally allowing subclasses).
     * @param ec Execution context
     * @param db Mongo DB
     * @param candidateClass Candidate
     * @param subclasses Whether to include subclasses
     * @param ignoreCache Ignore the cache?
     * @param fp FetchPlan when retrieving the objects
     * @param filterObject Optional filter object
     * @param options Set of options for controlling this query
     * @return List of all candidate objects
     */
    public static List getObjectsOfCandidateType(ExecutionContext ec, DB db, Class candidateClass, 
            boolean subclasses, boolean ignoreCache, FetchPlan fp, BasicDBObject filterObject,
            Map options, Integer skip, Integer limit)
    {
        List cmds =
            MetaDataUtils.getMetaDataForCandidates(candidateClass, subclasses, ec);

        // TODO Allow for subclasses sharing the same table so just doing a single query but with discriminator filtering
        Iterator cmdIter = cmds.iterator();
        List results = new ArrayList();
        StoreManager storeMgr = ec.getStoreManager();
        ClassLoaderResolver clr = ec.getClassLoaderResolver();
        while (cmdIter.hasNext())
        {
            AbstractClassMetaData cmd = cmdIter.next();
            fp.manageFetchPlanForClass(cmd);
            int[] fpMembers = fp.getFetchPlanForClass(cmd).getMemberNumbers();
            BasicDBObject fieldsSelection = new BasicDBObject();
            if (fpMembers != null && fpMembers.length > 0)
            {
                fieldsSelection = new BasicDBObject();
                for (int i=0;i> entryIter = filterObject.entrySet().iterator();
                while (entryIter.hasNext())
                {
                    Map.Entry entry = entryIter.next();
                    query.put(entry.getKey(), entry.getValue());
                }
            }

            if (cmd.hasDiscriminatorStrategy())
            {
                // Discriminator present : Add restriction on the discriminator value for this class
                query.put(storeMgr.getNamingFactory().getColumnName(cmd, ColumnType.DISCRIMINATOR_COLUMN), cmd.getDiscriminatorValue());
            }

            if (storeMgr.getStringProperty(PropertyNames.PROPERTY_TENANT_ID) != null)
            {
                // Multitenancy discriminator present : Add restriction for this tenant
                if ("true".equalsIgnoreCase(cmd.getValueForExtension("multitenancy-disable")))
                {
                    // Don't bother with multitenancy for this class
                }
                else
                {
                    String fieldName = storeMgr.getNamingFactory().getColumnName(cmd, ColumnType.MULTITENANCY_COLUMN);
                    String value = storeMgr.getStringProperty(PropertyNames.PROPERTY_TENANT_ID);
                    query.put(fieldName, value);
                }
            }

            String collectionName = storeMgr.getNamingFactory().getTableName(cmd);
            if (NucleusLogger.DATASTORE_RETRIEVE.isDebugEnabled())
            {
                NucleusLogger.DATASTORE_RETRIEVE.debug("Fetching instances of collection " + collectionName +
                    " fields=" + fieldsSelection + " with filter=" + query);
            }

            DBCollection dbColl = db.getCollection(collectionName);
            Object val = (options != null ? options.get("slave-ok") : Boolean.FALSE);
            if (val == Boolean.TRUE)
            {
                dbColl.slaveOk();
            }

            DBCursor curs = dbColl.find(query, fieldsSelection);
            if (ec.getStatistics() != null)
            {
                // Add to statistics
                ec.getStatistics().incrementNumReads();
            }

            // Add results from this DBCursor and close it
            // TODO Pass this into a MongoDBCandidateList rather than manually loading all here
            if (cmds.size() == 1)
            {
                // If we have a single DBCursor then apply the range specification directly
                if (skip != null && skip > 0)
                {
                    curs = curs.skip(skip);
                }
                if (limit != null && limit > 0)
                {
                    curs = curs.limit(limit);
                }
            }

            try
            {
                while (curs.hasNext())
                {
                    final DBObject dbObject = curs.next();
                    if (cmd.getIdentityType() == IdentityType.APPLICATION)
                    {
                        results.add(getObjectUsingApplicationIdForDBObject(dbObject, cmd, ec, ignoreCache, fpMembers));
                    }
                    else if (cmd.getIdentityType() == IdentityType.DATASTORE)
                    {
                        results.add(getObjectUsingDatastoreIdForDBObject(dbObject, cmd, ec, ignoreCache, fpMembers));
                    }
                    else
                    {
                        results.add(getObjectUsingNondurableIdForDBObject(dbObject, cmd, ec, ignoreCache, fpMembers));
                    }
                }
            }
            finally
            {
                curs.close();
            }
        }

        return results;
    }

    protected static void selectAllFieldsOfEmbeddedObject(AbstractMemberMetaData mmd, BasicDBObject fieldsSelection,
            ExecutionContext ec, ClassLoaderResolver clr)
    {
        EmbeddedMetaData embmd = mmd.getEmbeddedMetaData();
        AbstractMemberMetaData[] embmmds = embmd.getMemberMetaData();
        for (int i=0;i 0)
            {
                colmd = mmd.getColumnMetaData()[0];
            }
            boolean useNumeric = MetaDataUtils.persistColumnAsNumeric(colmd);
            return useNumeric ? ((Enum)value).ordinal() : ((Enum)value).name();
        }
        else if (java.sql.Time.class.isAssignableFrom(type))
        {
            // Will be processed by type converters below
        }
        else if (java.sql.Date.class.isAssignableFrom(type))
        {
            // Will be processed by type converters below
        }
        else if (Date.class.isAssignableFrom(type))
        {
            // store as-is
            return value;
        }
        else if (Calendar.class.isAssignableFrom(type))
        {
            return ((Calendar)value).getTime();
        }
        else if (Character.class.isAssignableFrom(type))
        {
            // store as String
            return "" + value;
        }
        else if (char.class.isAssignableFrom(type))
        {
            // store as String
            return "" + value;
        }
        else if (BufferedImage.class.isAssignableFrom(type))
        {
            // store serialised
            byte[] bytes = null;
            try
            {
                ByteArrayOutputStream baos = new ByteArrayOutputStream(8192);
                ImageIO.write((BufferedImage) value, "jpg", baos);
                bytes = baos.toByteArray();
                baos.close();
            }
            catch (IOException e)
            {
            }
            return bytes;
        }

        // Fallback to built-in type converters
        TypeConverter strConv = ec.getTypeManager().getTypeConverterForType(type, String.class);
        TypeConverter longConv = ec.getTypeManager().getTypeConverterForType(type, Long.class);
        if (strConv != null)
        {
            // store as a String
            return strConv.toDatastoreType(value);
        }
        else if (longConv != null)
        {
            // store as a Long
            return longConv.toDatastoreType(value);
        }

        // store as the raw value
        return value;
    }

    /**
     * Convenience method to convert the stored value for an object field into the value that will be held
     * in the object. Note that this does not cater for relation fields, just basic fields.
     * @param ec ExecutionContext
     * @param mmd Metadata for the field holding this value
     * @param value The stored value for the field
     * @param role The role of this value for the field
     * @return The value to put in the field
     */
    public static Object getFieldValueFromStored(ExecutionContext ec, AbstractMemberMetaData mmd, Object value, int fieldRole)
    {
        if (value == null)
        {
            return null;
        }

        Class type = value.getClass();
        if (mmd != null)
        {
            if (fieldRole == FieldRole.ROLE_COLLECTION_ELEMENT)
            {
                type = ec.getClassLoaderResolver().classForName(mmd.getCollection().getElementType());
            }
            else if (fieldRole == FieldRole.ROLE_ARRAY_ELEMENT)
            {
                type = ec.getClassLoaderResolver().classForName(mmd.getArray().getElementType());
            }
            else if (fieldRole == FieldRole.ROLE_MAP_KEY)
            {
                type = ec.getClassLoaderResolver().classForName(mmd.getMap().getKeyType());
            }
            else if (fieldRole == FieldRole.ROLE_MAP_VALUE)
            {
                type = ec.getClassLoaderResolver().classForName(mmd.getMap().getValueType());
            }
            else
            {
                type = mmd.getType();
            }
        }

        if (mmd.hasCollection() && fieldRole == FieldRole.ROLE_FIELD)
        {
            Collection coll;
            try
            {
                Class instanceType = SCOUtils.getContainerInstanceType(mmd.getType(), mmd.getOrderMetaData() != null);
                coll = (Collection) instanceType.newInstance();
            }
            catch (Exception e)
            {
                throw new NucleusDataStoreException(e.getMessage(), e);
            }

            Collection rawColl = (Collection)value;
            for (Object elem : rawColl)
            {
                Object storeElem = getFieldValueFromStored(ec, mmd, elem, FieldRole.ROLE_COLLECTION_ELEMENT);
                coll.add(storeElem);
            }
            return coll;
        }
        else if (mmd.hasArray() && fieldRole == FieldRole.ROLE_FIELD)
        {
            Collection rawColl = (Collection)value;
            Object array = Array.newInstance(mmd.getType().getComponentType(), rawColl.size());
            int i=0;
            for (Object elem : rawColl)
            {
                Object storeElem = getFieldValueFromStored(ec, mmd, elem, FieldRole.ROLE_ARRAY_ELEMENT);
                Array.set(array, i++, storeElem);
            }
            return array;
        }

        if (Character.class.isAssignableFrom(type))
        {
            if (value instanceof Character)
            {
                return value;
            }
            return ((String)value).charAt(0);
        }
        else if (Short.class.isAssignableFrom(type))
        {
            if (value instanceof Short)
            {
                return value;
            }
            return ((Number)value).shortValue();
        }
        else if (Integer.class.isAssignableFrom(type))
        {
            if (value instanceof Integer)
            {
                return value;
            }
            return ((Number)value).intValue();
        }
        else if (Long.class.isAssignableFrom(type))
        {
            if (value instanceof Long)
            {
                return value;
            }
            return ((Number)value).longValue();
        }
        else if (Float.class.isAssignableFrom(type))
        {
            if (value instanceof Float)
            {
                return value;
            }
            return ((Number)value).floatValue();
        }
        else if (Double.class.isAssignableFrom(type))
        {
            if (value instanceof Double)
            {
                return value;
            }
            return ((Number)value).doubleValue();
        }
        else if (Byte.class.isAssignableFrom(type))
        {
            if (value instanceof Byte)
            {
                return value;
            }
            return ((Number)value).byteValue();
        }
        else if (Enum.class.isAssignableFrom(type))
        {
            ColumnMetaData colmd = null;
            if (mmd != null && mmd.getColumnMetaData() != null && mmd.getColumnMetaData().length > 0)
            {
                colmd = mmd.getColumnMetaData()[0];
            }
            if (MetaDataUtils.persistColumnAsNumeric(colmd))
            {
                return type.getEnumConstants()[((Number)value).intValue()];
            }
            else
            {
                return Enum.valueOf(type, (String)value);
            }
        }
        else if (java.sql.Date.class.isAssignableFrom(type) && value instanceof Date)
        {
            java.sql.Date sqlDate = null;
            if (value instanceof java.sql.Date)
            {
                sqlDate = (java.sql.Date) value;
            }
            else
            {
                sqlDate = new java.sql.Date(((Date)value).getTime());
            }
            return sqlDate;
        }
        else if (java.sql.Time.class.isAssignableFrom(type) && value instanceof Date)
        {
            java.sql.Time sqlTime = null;
            if (value instanceof java.sql.Time)
            {
                sqlTime = (java.sql.Time) value;
            }
            else
            {
                sqlTime = new java.sql.Time(((Date)value).getTime());
            }
            return sqlTime;
        }
        else if (java.sql.Timestamp.class.isAssignableFrom(type) && value instanceof Date)
        {
            java.sql.Timestamp sqlTs = null;
            if (value instanceof java.sql.Time)
            {
                sqlTs = (java.sql.Timestamp) value;
            }
            else
            {
                sqlTs = new java.sql.Timestamp(((Date)value).getTime());
            }
            return sqlTs;
        }
        else if (Date.class.isAssignableFrom(type) && value instanceof Date)
        {
            // Date could have been persisted as String (in 3.0m4) so this just handles the Date case
            return value;
        }
        else if (Calendar.class.isAssignableFrom(type))
        {
            Calendar cal = Calendar.getInstance();
            cal.setTime((Date)value);
            return cal;
        }
        else if (BufferedImage.class.isAssignableFrom(type))
        {
            Object returnValue = null;
            try
            {
                returnValue = ImageIO.read(new ByteArrayInputStream((byte[])value));
            }
            catch (IOException e)
            {
            }
            return returnValue;
        }

        TypeConverter strConv = ec.getTypeManager().getTypeConverterForType(type, String.class);
        TypeConverter longConv = ec.getTypeManager().getTypeConverterForType(type, Long.class);
        if (strConv != null)
        {
            // Persisted as a String, so convert back
            String strValue = (String)value;
            return strConv.toMemberType(strValue);
        }
        else if (longConv != null)
        {
            // Persisted as a Long, so convert back
            Long longValue = (Long)value;
            return longConv.toMemberType(longValue);
        }

        return value;
    }

    public static byte[] getStoredValueForSerialisedField(AbstractMemberMetaData mmd, Object value)
    {
        byte[] storeValue = null;
        try
        {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(value);
            storeValue = bos.toByteArray();
            oos.close();
            bos.close();
        }
        catch (IOException e)
        {
            throw new NucleusException("Exception thrown serialising value for field " + mmd.getFullFieldName(), e);
        }
        return storeValue;
    }

    public static Object getFieldValueForSerialisedField(AbstractMemberMetaData mmd, Object value)
    {
        Object returnValue = null;
        try
        {
            if (value != null)
            {
                byte[] bytes = (byte[])value;
                ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
                ObjectInputStream ois = new ObjectInputStream(bis);
                returnValue = ois.readObject();
                ois.close();
                bis.close();
            }
        }
        catch (Exception e)
        {
            throw new NucleusUserException("Exception thrown deserialising field at " + mmd.getFullFieldName(), e);
        }
        return returnValue;
    }

}