/**********************************************************************
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:
...
**********************************************************************/
package org.datanucleus.store.mongodb;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.bson.types.ObjectId;
import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.FetchPlan;
import org.datanucleus.identity.IdentityUtils;
import org.datanucleus.identity.OID;
import org.datanucleus.identity.OIDFactory;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.ColumnMetaData;
import org.datanucleus.metadata.DiscriminatorMetaData;
import org.datanucleus.metadata.EmbeddedMetaData;
import org.datanucleus.metadata.IdentityMetaData;
import org.datanucleus.metadata.IdentityStrategy;
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.mongodb.fieldmanager.FetchFieldManager;
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;
/**
* Utilities for MongoDB.
*/
public class MongoDBUtils
{
/**
* Accessor for the MongoDB collection name for this class.
* Takes the table name if provided, otherwise uses the (short-form) class name
* @param cmd Metadata for the class
* @return The collection name
*/
public static String getCollectionName(AbstractClassMetaData cmd)
{
if (cmd.getTable() != null)
{
return cmd.getTable();
}
return cmd.getName();
}
/**
* Accessor for the MongoDB "field" name to store this member under. Takes the column name if provided
* otherwise the (short-form) member name.
* @param mmd Metadata for the member
* @return The field name
*/
public static String getFieldName(AbstractMemberMetaData mmd)
{
// Try the first column if specified
ColumnMetaData[] colmds = mmd.getColumnMetaData();
if (colmds != null && colmds.length > 1)
{
return colmds[0].getName();
}
// Fallback to the member name
return mmd.getName();
}
/**
* Accessor for the MongoDB "field" name to store the datastore id under. Takes the column name if provided
* otherwise "IDENTITY".
* @param idmd Metadata for the identity
* @return The field name
*/
public static String getFieldName(IdentityMetaData idmd)
{
if (idmd.getValueStrategy() == IdentityStrategy.IDENTITY)
{
// Make use of MongoDB "_id" field
return "_id";
}
else
{
// Try the first column if specified
ColumnMetaData[] colmds = idmd.getColumnMetaData();
if (colmds != null && colmds.length > 1)
{
return colmds[0].getName();
}
// Fallback to "IDENTITY"
return "IDENTITY";
}
}
/**
* Accessor for the MongoDB "field" name to store the surrogate version under. Takes the column name if provided
* otherwise "VERSION".
* @param mmd Metadata for the version
* @return The field name
*/
public static String getFieldName(VersionMetaData vermd)
{
// Try the first column if specified
ColumnMetaData[] colmds = vermd.getColumnMetaData();
if (colmds != null && colmds.length > 1)
{
return colmds[0].getName();
}
// Fallback to "VERSION"
return "VERSION";
}
/**
* Accessor for the MongoDB "field" name to store the discriminator under. Takes the column name if provided
* otherwise "DISCRIM".
* @param discmd Metadata for the discriminator
* @return The field name
*/
public static String getFieldName(DiscriminatorMetaData discmd)
{
// Try the first column if specified
ColumnMetaData colmd = discmd.getColumnMetaData();
if (colmd != null)
{
return colmd.getName();
}
// Fallback to "DISCRIM"
return "DISCRIM";
}
/**
* 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;
}
/**
* Method to return the DBObject that equates to the provided object.
* @param dbCollection The collection in which it is stored
* @param sm The ObjectProvider
* @param checkVersion Whether to also check for a particular version
* @return The object (or null if not found)
*/
public static DBObject getObjectForObjectProvider(DBCollection dbCollection, ObjectProvider sm, boolean checkVersion)
{
// Build query object to use as template for the find
BasicDBObject query = new BasicDBObject();
AbstractClassMetaData cmd = sm.getClassMetaData();
if (cmd.getIdentityType() == IdentityType.APPLICATION)
{
// Add PK field(s) to the query object
int[] pkPositions = cmd.getPKMemberPositions();
for (int i=0;i<pkPositions.length;i++)
{
AbstractMemberMetaData pkMmd = cmd.getMetaDataForManagedMemberAtAbsolutePosition(pkPositions[i]);
Object value = sm.provideField(pkPositions[i]);
if (value == null && pkMmd.getValueStrategy() == IdentityStrategy.IDENTITY)
{
// PK field not yet set, so return null (needs to be attributed in the datastore)
return null;
}
if (pkMmd.getValueStrategy() == IdentityStrategy.IDENTITY)
{
query.put("_id", new ObjectId((String)value));
}
else
{
query.put(MongoDBUtils.getFieldName(pkMmd), value);
}
}
}
else if (cmd.getIdentityType() == IdentityType.DATASTORE)
{
// Add PK field to the query object
OID oid = (OID) sm.getObjectId();
if (oid == null && cmd.getIdentityMetaData().getValueStrategy() == IdentityStrategy.IDENTITY)
{
// Not yet set, so return null (needs to be attributed in the datastore)
return null;
}
Object value = oid.getKeyValue();
if (cmd.getIdentityMetaData().getValueStrategy() == IdentityStrategy.IDENTITY)
{
query.put("_id", value);
}
else
{
query.put(MongoDBUtils.getFieldName(cmd.getIdentityMetaData()), value);
}
}
else
{
// TODO Support nondurable identity
return null;
}
if (cmd.hasDiscriminatorStrategy())
{
// Add discriminator to the query object
query.put(MongoDBUtils.getFieldName(cmd.getDiscriminatorMetaData()), cmd.getDiscriminatorValue());
}
if (checkVersion && cmd.hasVersionStrategy())
{
// Add version to the query object
Object currentVersion = sm.getTransactionalVersion();
VersionMetaData vermd = cmd.getVersionMetaData();
if (vermd.getFieldName() != null)
{
// Version field in class
AbstractMemberMetaData verMmd = cmd.getMetaDataForMember(cmd.getVersionMetaData().getFieldName());
String fieldName = MongoDBUtils.getFieldName(verMmd);
query.put(fieldName, currentVersion);
}
else
{
// Surrogate version field
String fieldName = MongoDBUtils.getFieldName(cmd.getVersionMetaData());
query.put(fieldName, currentVersion);
}
}
NucleusLogger.DATASTORE_RETRIEVE.debug("Retrieving object for " + query);
return dbCollection.findOne(query);
}
/**
* 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<String, Object> options)
{
List<AbstractClassMetaData> cmds =
MetaDataUtils.getMetaDataForCandidates(candidateClass, subclasses, ec);
Iterator<AbstractClassMetaData> cmdIter = cmds.iterator();
List results = new ArrayList();
while (cmdIter.hasNext())
{
AbstractClassMetaData acmd = cmdIter.next();
results.addAll(getObjectsOfType(acmd, db, ec, ignoreCache, fp, filterObject, options));
}
return results;
}
protected static List getObjectsOfType(AbstractClassMetaData cmd, DB db, ExecutionContext ec, boolean ignoreCache,
FetchPlan fp, BasicDBObject filterObject, Map<String, Object> options)
{
List results = new ArrayList();
ClassLoaderResolver clr = ec.getClassLoaderResolver();
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<fpMembers.length;i++)
{
AbstractMemberMetaData mmd = cmd.getMetaDataForManagedMemberAtAbsolutePosition(fpMembers[i]);
int relationType = mmd.getRelationType(clr);
if (mmd.isEmbedded() && Relation.isRelationSingleValued(relationType))
{
boolean nested = true;
String nestedStr = mmd.getValueForExtension("nested");
if (nestedStr != null && nestedStr.equalsIgnoreCase("false"))
{
nested = false;
}
if (nested)
{
// Nested Embedded field, so include field
String fieldName = MongoDBUtils.getFieldName(mmd);
fieldsSelection.append(fieldName, 1);
}
else
{
// Flat Embedded field, so add all fields of sub-objects
selectAllFieldsOfEmbeddedObject(mmd, fieldsSelection, ec, clr);
}
}
else
{
String fieldName = MongoDBUtils.getFieldName(mmd);
fieldsSelection.append(fieldName, 1);
}
}
}
if (cmd.getIdentityType() == IdentityType.DATASTORE)
{
fieldsSelection.append(MongoDBUtils.getFieldName(cmd.getIdentityMetaData()), 1);
}
if (cmd.hasVersionStrategy())
{
fieldsSelection.append(MongoDBUtils.getFieldName(cmd.getVersionMetaData()), 1);
}
BasicDBObject query = new BasicDBObject();
if (filterObject != null)
{
Iterator<Map.Entry<String, Object>> entryIter = filterObject.entrySet().iterator();
while (entryIter.hasNext())
{
Map.Entry<String, Object> 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(MongoDBUtils.getFieldName(cmd.getDiscriminatorMetaData()), cmd.getDiscriminatorValue());
}
if (NucleusLogger.DATASTORE_RETRIEVE.isDebugEnabled())
{
NucleusLogger.DATASTORE_RETRIEVE.debug("Fetching instances of collection " + MongoDBUtils.getCollectionName(cmd) +
" fields=" + fieldsSelection + " with filter=" + query);
}
DBCollection dbColl = db.getCollection(MongoDBUtils.getCollectionName(cmd));
Object val = (options != null ? options.get("slave-ok") : Boolean.FALSE);
if (val == Boolean.TRUE)
{
dbColl.slaveOk();
}
DBCursor curs = dbColl.find(query, fieldsSelection);
try
{
if (cmd.getIdentityType() == IdentityType.APPLICATION)
{
while (curs.hasNext())
{
final DBObject dbObject = curs.next();
results.add(getObjectUsingApplicationIdForDBObject(dbObject, cmd, ec, ignoreCache, fpMembers));
}
}
else if (cmd.getIdentityType() == IdentityType.DATASTORE)
{
while (curs.hasNext())
{
final DBObject dbObject = curs.next();
results.add(getObjectUsingDatastoreIdForDBObject(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<embmmds.length;i++)
{
int relationType = embmmds[i].getRelationType(clr);
if (embmmds[i].isEmbedded() && Relation.isRelationSingleValued(relationType))
{
selectAllFieldsOfEmbeddedObject(embmmds[i], fieldsSelection, ec, clr);
}
else
{
String embFieldName = MongoDBUtils.getFieldName(mmd, i);
fieldsSelection.append(embFieldName, 1);
}
}
}
protected static Object getObjectUsingApplicationIdForDBObject(final DBObject dbObject, final AbstractClassMetaData cmd,
final ExecutionContext ec, boolean ignoreCache, final int[] fpMembers)
{
Object id = IdentityUtils.getApplicationIdentityForResultSetRow(ec, cmd, null,
false, new FetchFieldManager(ec, dbObject, cmd));
Object pc = ec.findObject(id,
new FieldValues()
{
public void fetchFields(ObjectProvider sm)
{
sm.replaceFields(fpMembers, new FetchFieldManager(ec, dbObject, cmd));
}
public void fetchNonLoadedFields(ObjectProvider sm)
{
sm.replaceNonLoadedFields(fpMembers, new FetchFieldManager(ec, dbObject, cmd));
}
public FetchPlan getFetchPlanForLoading()
{
return null;
}
}, null, ignoreCache);
if (cmd.hasVersionStrategy())
{
// Set the version on the retrieved object
ObjectProvider sm = ec.findObjectProvider(pc);
Object version = null;
if (cmd.getVersionMetaData().getFieldName() != null)
{
// Get the version from the field value
AbstractMemberMetaData verMmd = cmd.getMetaDataForMember(cmd.getVersionMetaData().getFieldName());
version = sm.provideField(verMmd.getAbsoluteFieldNumber());
}
else
{
// Get the surrogate version from the datastore
version = dbObject.get(MongoDBUtils.getFieldName(cmd.getVersionMetaData()));
}
sm.setVersion(version);
}
return pc;
}
protected static Object getObjectUsingDatastoreIdForDBObject(final DBObject dbObject, final AbstractClassMetaData cmd,
final ExecutionContext ec, boolean ignoreCache, final int[] fpMembers)
{
Object idKey = null;
if (cmd.getIdentityMetaData().getValueStrategy() == IdentityStrategy.IDENTITY)
{
idKey = dbObject.get("_id");
}
else
{
idKey = dbObject.get(MongoDBUtils.getFieldName(cmd.getIdentityMetaData()));
}
OID oid = OIDFactory.getInstance(ec.getNucleusContext(), cmd.getFullClassName(), idKey);
Object pc = ec.findObject(oid,
new FieldValues()
{
// StateManager calls the fetchFields method
public void fetchFields(ObjectProvider sm)
{
sm.replaceFields(fpMembers, new FetchFieldManager(ec, dbObject, cmd));
}
public void fetchNonLoadedFields(ObjectProvider sm)
{
sm.replaceNonLoadedFields(fpMembers, new FetchFieldManager(ec, dbObject, cmd));
}
public FetchPlan getFetchPlanForLoading()
{
return null;
}
}, null, ignoreCache);
if (cmd.hasVersionStrategy())
{
// Set the version on the retrieved object
ObjectProvider sm = ec.findObjectProvider(pc);
Object version = null;
if (cmd.getVersionMetaData().getFieldName() != null)
{
// Get the version from the field value
AbstractMemberMetaData verMmd = cmd.getMetaDataForMember(cmd.getVersionMetaData().getFieldName());
version = sm.provideField(verMmd.getAbsoluteFieldNumber());
}
else
{
// Get the surrogate version from the datastore
version = dbObject.get(MongoDBUtils.getFieldName(cmd.getVersionMetaData()));
}
sm.setVersion(version);
}
return pc;
}
}