/**********************************************************************
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.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.NucleusContext;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.ClassMetaData;
import org.datanucleus.metadata.ClassPersistenceModifier;
import org.datanucleus.metadata.ColumnMetaData;
import org.datanucleus.metadata.IdentityStrategy;
import org.datanucleus.metadata.IdentityType;
import org.datanucleus.metadata.IndexMetaData;
import org.datanucleus.metadata.UniqueMetaData;
import org.datanucleus.store.AbstractStoreManager;
import org.datanucleus.store.StoreData;
import org.datanucleus.store.connection.ManagedConnection;
import org.datanucleus.store.schema.SchemaAwareStoreManager;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.NucleusLogger;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
public class MongoDBStoreManager extends AbstractStoreManager implements SchemaAwareStoreManager
{
protected static final Localiser LOCALISER = Localiser.getInstance(
"org.datanucleus.store.mongodb.Localisation", MongoDBStoreManager.class.getClassLoader());
/**
* Constructor.
* @param clr ClassLoader resolver
* @param nucleusCtx ObjectManagerFactory context
* @param props Properties for the store manager
*/
public MongoDBStoreManager(ClassLoaderResolver clr, NucleusContext nucleusCtx, Map<String, Object> props)
{
super("mongodb", clr, nucleusCtx, props);
// Handler for persistence process
persistenceHandler = new MongoDBPersistenceHandler(this);
logConfiguration();
}
public Collection getSupportedOptions()
{
Set set = new HashSet();
set.add("ApplicationIdentity");
set.add("DatastoreIdentity");
set.add("ORM");
set.add("TransactionIsolationLevel.read-committed");
return set;
}
/**
* Accessor for whether this value strategy is supported.
* Overrides the superclass to allow for "IDENTITY" since we support it and no entry in plugins for it.
* @param strategy The strategy
* @return Whether it is supported.
*/
public boolean supportsValueStrategy(String strategy)
{
if (super.supportsValueStrategy(strategy))
{
return true;
}
// "identity" doesn't have an explicit entry in plugin since uses datastore capabilities
if (strategy.equalsIgnoreCase("IDENTITY"))
{
return true;
}
return false;
}
/* (non-Javadoc)
* @see org.datanucleus.store.AbstractStoreManager#addClasses(java.lang.String[], org.datanucleus.ClassLoaderResolver)
*/
@Override
public void addClasses(String[] classNames, ClassLoaderResolver clr)
{
if (classNames == null)
{
return;
}
// Filter out any "simple" type classes
String[] filteredClassNames =
getNucleusContext().getTypeManager().filterOutSupportedSecondClassNames(classNames);
// Find the ClassMetaData for these classes and all referenced by these classes
ManagedConnection mconn = getConnection(-1);
try
{
DB db = (DB)mconn.getConnection();
Iterator iter = getMetaDataManager().getReferencedClasses(filteredClassNames, clr).iterator();
while (iter.hasNext())
{
ClassMetaData cmd = (ClassMetaData)iter.next();
if (cmd.getPersistenceModifier() == ClassPersistenceModifier.PERSISTENCE_CAPABLE)
{
if (!storeDataMgr.managesClass(cmd.getFullClassName()))
{
StoreData sd = storeDataMgr.get(cmd.getFullClassName());
if (sd == null)
{
registerStoreData(newStoreData(cmd, clr));
}
// Create schema for class
createSchemaForClass(cmd, db);
}
}
}
}
finally
{
mconn.release();
}
}
/* (non-Javadoc)
* @see org.datanucleus.store.schema.SchemaAwareStoreManager#createSchema(java.util.Set, java.util.Properties)
*/
public void createSchema(Set<String> classNames, Properties props)
{
ManagedConnection mconn = getConnection(-1);
try
{
DB db = (DB)mconn.getConnection();
Iterator<String> classIter = classNames.iterator();
ClassLoaderResolver clr = nucleusContext.getClassLoaderResolver(null);
while (classIter.hasNext())
{
String className = classIter.next();
AbstractClassMetaData cmd = getMetaDataManager().getMetaDataForClass(className, clr);
if (cmd != null)
{
createSchemaForClass(cmd, db);
}
}
}
finally
{
mconn.close();
}
}
protected void createSchemaForClass(AbstractClassMetaData cmd, DB db)
{
String collectionName = MongoDBUtils.getCollectionName(cmd);
DBCollection collection = null;
if (autoCreateTables)
{
// Create collection (if not existing)
collection = db.getCollection(collectionName);
// TODO Create fields for this class? but each row will be different so likely not necessary
}
if (autoCreateConstraints)
{
// Create indexes
if (collection == null && !db.getCollectionNames().contains(collectionName))
{
NucleusLogger.DATASTORE_SCHEMA.warn("Cannot create constraints for " + cmd.getFullClassName() +
" since collection of name " + collectionName + " doesn't exist (enable autoCreateTables?)");
return;
}
else if (collection == null)
{
collection = db.getCollection(collectionName);
}
IndexMetaData[] idxmds = cmd.getIndexMetaData();
if (idxmds != null && idxmds.length > 0)
{
for (int i=0;i<idxmds.length;i++)
{
DBObject idxObj = getDBObjectForIndex(cmd, idxmds[i]);
if (NucleusLogger.DATASTORE_SCHEMA.isDebugEnabled())
{
NucleusLogger.DATASTORE_SCHEMA.debug(LOCALISER.msg("MongoDB.SchemaCreate.Class.Index",
idxmds[i].getName(), MongoDBUtils.getCollectionName(cmd), idxObj));
}
collection.ensureIndex(idxObj, idxmds[i].getName(), idxmds[i].isUnique());
}
}
UniqueMetaData[] unimds = cmd.getUniqueMetaData();
if (unimds != null && unimds.length > 0)
{
for (int i=0;i<unimds.length;i++)
{
DBObject uniObj = getDBObjectForUnique(cmd, unimds[i]);
if (NucleusLogger.DATASTORE_SCHEMA.isDebugEnabled())
{
NucleusLogger.DATASTORE_SCHEMA.debug(LOCALISER.msg("MongoDB.SchemaCreate.Class.Index",
unimds[i].getName(), MongoDBUtils.getCollectionName(cmd), uniObj));
}
collection.ensureIndex(uniObj, unimds[i].getName(), true);
}
}
if (cmd.getIdentityType() == IdentityType.APPLICATION)
{
// Add unique index on PK
BasicDBObject query = new BasicDBObject();
int[] pkFieldNumbers = cmd.getPKMemberPositions();
boolean applyIndex = true;
for (int i=0;i<pkFieldNumbers.length;i++)
{
AbstractMemberMetaData pkMmd = cmd.getMetaDataForManagedMemberAtAbsolutePosition(pkFieldNumbers[i]);
if (pkMmd.getValueStrategy() == IdentityStrategy.IDENTITY)
{
applyIndex = false;
break;
}
query.append(MongoDBUtils.getFieldName(pkMmd), 1);
}
if (applyIndex)
{
String pkName = (cmd.getPrimaryKeyMetaData() != null ? cmd.getPrimaryKeyMetaData().getName() : cmd.getName() + "_PK");
if (NucleusLogger.DATASTORE_SCHEMA.isDebugEnabled())
{
NucleusLogger.DATASTORE_SCHEMA.debug(LOCALISER.msg("MongoDB.SchemaCreate.Class.Index",
pkName, MongoDBUtils.getCollectionName(cmd), query));
}
collection.ensureIndex(query, pkName, true);
}
}
else if (cmd.getIdentityType() == IdentityType.DATASTORE)
{
if (cmd.getIdentityMetaData().getValueStrategy() == IdentityStrategy.IDENTITY)
{
// Using builtin "_id" field so nothing to do
}
else
{
BasicDBObject query = new BasicDBObject();
query.append(MongoDBUtils.getFieldName(cmd.getIdentityMetaData()), 1);
String pkName = (cmd.getPrimaryKeyMetaData() != null ? cmd.getPrimaryKeyMetaData().getName() : cmd.getName() + "_PK");
if (NucleusLogger.DATASTORE_SCHEMA.isDebugEnabled())
{
NucleusLogger.DATASTORE_SCHEMA.debug(LOCALISER.msg("MongoDB.SchemaCreate.Class.Index",
pkName, MongoDBUtils.getCollectionName(cmd), query));
}
collection.ensureIndex(query, pkName, true);
}
}
AbstractMemberMetaData[] mmds = cmd.getManagedMembers();
if (mmds != null && mmds.length > 0)
{
for (int i=0;i<mmds.length;i++)
{
IndexMetaData idxmd = mmds[i].getIndexMetaData();
if (idxmd != null)
{
BasicDBObject query = new BasicDBObject();
query.append(MongoDBUtils.getFieldName(mmds[i]), 1);
if (NucleusLogger.DATASTORE_SCHEMA.isDebugEnabled())
{
NucleusLogger.DATASTORE_SCHEMA.debug(LOCALISER.msg("MongoDB.SchemaCreate.Class.Index",
idxmd.getName(), MongoDBUtils.getCollectionName(cmd), query));
}
collection.ensureIndex(query, idxmd.getName(), idxmd.isUnique());
}
UniqueMetaData unimd = mmds[i].getUniqueMetaData();
if (unimd != null)
{
BasicDBObject query = new BasicDBObject();
query.append(MongoDBUtils.getFieldName(mmds[i]), 1);
if (NucleusLogger.DATASTORE_SCHEMA.isDebugEnabled())
{
NucleusLogger.DATASTORE_SCHEMA.debug(LOCALISER.msg("MongoDB.SchemaCreate.Class.Index",
unimd.getName(), MongoDBUtils.getCollectionName(cmd), query));
}
collection.ensureIndex(query, unimd.getName(), true);
}
}
}
}
}
private DBObject getDBObjectForIndex(AbstractClassMetaData cmd, IndexMetaData idxmd)
{
BasicDBObject idxObj = new BasicDBObject();
if (idxmd.getColumnMetaData() != null)
{
ColumnMetaData[] idxcolmds = idxmd.getColumnMetaData();
for (int j=0;j<idxcolmds.length;j++)
{
idxObj.append(idxcolmds[j].getName(), 1);
}
}
else if (idxmd.getMemberMetaData() != null)
{
AbstractMemberMetaData[] idxmmds = idxmd.getMemberMetaData();
for (int j=0;j<idxmmds.length;j++)
{
String memberName = idxmmds[j].getName();
AbstractMemberMetaData mmd = cmd.getMetaDataForMember(memberName);
idxObj.append(MongoDBUtils.getFieldName(mmd), 1);
}
}
return idxObj;
}
private DBObject getDBObjectForUnique(AbstractClassMetaData cmd, UniqueMetaData unimd)
{
BasicDBObject uniObj = new BasicDBObject();
if (unimd.getColumnMetaData() != null)
{
ColumnMetaData[] unicolmds = unimd.getColumnMetaData();
for (int j=0;j<unicolmds.length;j++)
{
uniObj.append(unicolmds[j].getName(), 1);
}
}
else if (unimd.getMemberMetaData() != null)
{
AbstractMemberMetaData[] unimmds = unimd.getMemberMetaData();
for (int j=0;j<unimmds.length;j++)
{
String memberName = unimmds[j].getName();
AbstractMemberMetaData mmd = cmd.getMetaDataForMember(memberName);
uniObj.append(MongoDBUtils.getFieldName(mmd), 1);
}
}
return uniObj;
}
/* (non-Javadoc)
* @see org.datanucleus.store.schema.SchemaAwareStoreManager#deleteSchema(java.util.Set)
*/
public void deleteSchema(Set<String> classNames, Properties props)
{
ManagedConnection mconn = getConnection(-1);
try
{
DB db = (DB)mconn.getConnection();
Iterator<String> classIter = classNames.iterator();
ClassLoaderResolver clr = nucleusContext.getClassLoaderResolver(null);
while (classIter.hasNext())
{
String className = classIter.next();
AbstractClassMetaData cmd = getMetaDataManager().getMetaDataForClass(className, clr);
if (cmd != null)
{
DBCollection collection = db.getCollection(MongoDBUtils.getCollectionName(cmd));
collection.dropIndexes();
collection.drop();
}
}
}
finally
{
mconn.close();
}
}
/* (non-Javadoc)
* @see org.datanucleus.store.schema.SchemaAwareStoreManager#validateSchema(java.util.Set)
*/
public void validateSchema(Set<String> classNames, Properties props)
{
boolean success = true;
ManagedConnection mconn = getConnection(-1);
try
{
DB db = (DB)mconn.getConnection();
Iterator<String> classIter = classNames.iterator();
ClassLoaderResolver clr = nucleusContext.getClassLoaderResolver(null);
while (classIter.hasNext())
{
String className = classIter.next();
AbstractClassMetaData cmd = getMetaDataManager().getMetaDataForClass(className, clr);
if (cmd != null)
{
// Validate the schema for the class
String tableName = MongoDBUtils.getCollectionName(cmd);
if (!db.collectionExists(tableName))
{
success = false;
String msg = "Table doesn't exist for " + cmd.getFullClassName() +
" - should have name="+ tableName;
System.out.println(msg);
NucleusLogger.DATASTORE_SCHEMA.error(msg);
continue;
}
else
{
String msg = "Table for class="+cmd.getFullClassName() + " with name="+tableName + " validated";
NucleusLogger.DATASTORE_SCHEMA.info(msg);
}
DBCollection table = db.getCollection(tableName);
List<DBObject> indices = new ArrayList(table.getIndexInfo());
IndexMetaData[] idxmds = cmd.getIndexMetaData();
if (idxmds != null && idxmds.length > 0)
{
for (int i=0;i<idxmds.length;i++)
{
DBObject idxObj = getDBObjectForIndex(cmd, idxmds[i]);
DBObject indexObj = getIndexObjectForIndex(indices, idxmds[i].getName(), idxObj, true);
if (indexObj != null)
{
indices.remove(indexObj);
String msg = "Index for class="+cmd.getFullClassName() + " with name="+idxmds[i].getName() + " validated";
NucleusLogger.DATASTORE_SCHEMA.info(msg);
}
else
{
success = false;
String msg = "Index missing for class="+cmd.getFullClassName() + " name="+idxmds[i].getName() + " key="+idxObj;
System.out.println(msg);
NucleusLogger.DATASTORE_SCHEMA.error(msg);
}
}
}
UniqueMetaData[] unimds = cmd.getUniqueMetaData();
if (unimds != null && unimds.length > 0)
{
for (int i=0;i<unimds.length;i++)
{
DBObject uniObj = getDBObjectForUnique(cmd, unimds[i]);
DBObject indexObj = getIndexObjectForIndex(indices, unimds[i].getName(), uniObj, true);
if (indexObj != null)
{
indices.remove(indexObj);
String msg = "Unique index for class="+cmd.getFullClassName() + " with name="+unimds[i].getName() + " validated";
NucleusLogger.DATASTORE_SCHEMA.info(msg);
}
else
{
success = false;
String msg = "Unique index missing for class="+cmd.getFullClassName() + " name="+unimds[i].getName() + " key="+uniObj;
System.out.println(msg);
NucleusLogger.DATASTORE_SCHEMA.error(msg);
}
}
}
if (cmd.getIdentityType() == IdentityType.APPLICATION)
{
// Check unique index on PK
BasicDBObject query = new BasicDBObject();
int[] pkFieldNumbers = cmd.getPKMemberPositions();
for (int i=0;i<pkFieldNumbers.length;i++)
{
AbstractMemberMetaData pkMmd = cmd.getMetaDataForManagedMemberAtAbsolutePosition(pkFieldNumbers[i]);
query.append(MongoDBUtils.getFieldName(pkMmd), 1);
}
String pkName = (cmd.getPrimaryKeyMetaData() != null ? cmd.getPrimaryKeyMetaData().getName() : cmd.getName() + "_PK");
DBObject indexObj = getIndexObjectForIndex(indices, pkName, query, true);
if (indexObj != null)
{
indices.remove(indexObj);
String msg = "Index for application-identity with name="+pkName + " validated";
NucleusLogger.DATASTORE_SCHEMA.info(msg);
}
else
{
success = false;
String msg = "Index missing for application id name="+pkName + " key="+query;
System.out.println(msg);
NucleusLogger.DATASTORE_SCHEMA.error(msg);
}
}
else if (cmd.getIdentityType() == IdentityType.DATASTORE)
{
if (cmd.getIdentityMetaData().getValueStrategy() == IdentityStrategy.IDENTITY)
{
// Using builtin "_id" field so nothing to do
}
else
{
// Check unique index on PK
BasicDBObject query = new BasicDBObject();
query.append(MongoDBUtils.getFieldName(cmd.getIdentityMetaData()), 1);
String pkName = (cmd.getPrimaryKeyMetaData() != null ? cmd.getPrimaryKeyMetaData().getName() : cmd.getName() + "_PK");
DBObject indexObj = getIndexObjectForIndex(indices, pkName, query, true);
if (indexObj != null)
{
indices.remove(indexObj);
String msg = "Index for datastore-identity with name="+pkName + " validated";
NucleusLogger.DATASTORE_SCHEMA.info(msg);
}
else
{
success = false;
String msg = "Index missing for datastore id name="+pkName + " key="+query;
System.out.println(msg);
NucleusLogger.DATASTORE_SCHEMA.error(msg);
}
}
}
AbstractMemberMetaData[] mmds = cmd.getManagedMembers();
if (mmds != null && mmds.length > 0)
{
for (int i=0;i<mmds.length;i++)
{
IndexMetaData idxmd = mmds[i].getIndexMetaData();
if (idxmd != null)
{
BasicDBObject query = new BasicDBObject();
query.append(MongoDBUtils.getFieldName(mmds[i]), 1);
DBObject indexObj = getIndexObjectForIndex(indices, idxmd.getName(), query, true);
if (indexObj != null)
{
String msg = "Index for field=" + mmds[i].getFullFieldName() + " with name="+idxmd.getName() + " validated";
NucleusLogger.DATASTORE_SCHEMA.info(msg);
indices.remove(indexObj);
}
else
{
success = false;
String msg = "Index missing for field="+mmds[i].getFullFieldName() + " name="+idxmd.getName() + " key="+query;
System.out.println(msg);
NucleusLogger.DATASTORE_SCHEMA.error(msg);
}
}
UniqueMetaData unimd = mmds[i].getUniqueMetaData();
if (unimd != null)
{
BasicDBObject query = new BasicDBObject();
query.append(MongoDBUtils.getFieldName(mmds[i]), 1);
DBObject indexObj = getIndexObjectForIndex(indices, unimd.getName(), query, true);
if (indexObj != null)
{
String msg = "Unique index for field=" + mmds[i].getFullFieldName() + " with name="+unimd.getName() + " validated";
NucleusLogger.DATASTORE_SCHEMA.info(msg);
indices.remove(indexObj);
}
else
{
success = false;
String msg = "Unique index missing for field="+mmds[i].getFullFieldName() + " name="+unimd.getName() + " key="+query;
System.out.println(msg);
NucleusLogger.DATASTORE_SCHEMA.error(msg);
}
}
}
}
// TODO Could extend this and check that we account for all MongoDB generated indices too
}
}
}
finally
{
mconn.close();
}
if (!success)
{
throw new NucleusException("Errors were encountered during validation of MongoDB schema");
}
}
private DBObject getIndexObjectForIndex(List<DBObject> indices, String idxName, DBObject idxObj, boolean unique)
{
if (indices == null || indices.isEmpty())
{
return null;
}
Iterator<DBObject> idxIter = indices.iterator();
while (idxIter.hasNext())
{
DBObject index = idxIter.next();
DBObject obj = null;
String name = (String) index.get("name");
if (name.equals(idxName))
{
obj = index;
if (unique)
{
boolean flag = (Boolean) index.get("unique");
if (!flag)
{
continue;
}
}
boolean equal = true;
DBObject key = (DBObject)index.get("key");
if (key.toMap().size() != idxObj.toMap().size())
{
equal = false;
}
else
{
Iterator<String> indicKeyIter = key.keySet().iterator();
while (indicKeyIter.hasNext())
{
String fieldKey = indicKeyIter.next();
Object fieldValue = key.get(fieldKey);
if (!idxObj.containsField(fieldKey))
{
equal = false;
}
else
{
Object idxObjValue = idxObj.get(fieldKey);
if (!idxObjValue.equals(fieldValue))
{
equal = false;
}
}
}
}
if (equal)
{
return obj;
}
}
}
return null;
}
}