Java tutorial
/********************************************************************** Copyright (c) 2014 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.Properties; import java.util.Set; import org.datanucleus.ClassLoaderResolver; 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.IdentityType; import org.datanucleus.metadata.IndexMetaData; import org.datanucleus.metadata.UniqueMetaData; import org.datanucleus.store.StoreData; import org.datanucleus.store.StoreManager; import org.datanucleus.store.connection.ManagedConnection; import org.datanucleus.store.schema.AbstractStoreSchemaHandler; import org.datanucleus.store.schema.naming.NamingFactory; import org.datanucleus.store.schema.table.Column; import org.datanucleus.store.schema.table.CompleteClassTable; import org.datanucleus.store.schema.table.MemberColumnMapping; import org.datanucleus.store.schema.table.Table; 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; /** * Handler for schema operations with MongoDB datastores. */ public class MongoDBSchemaHandler extends AbstractStoreSchemaHandler { public MongoDBSchemaHandler(StoreManager storeMgr) { super(storeMgr); } /* (non-Javadoc) * @see org.datanucleus.store.schema.AbstractStoreSchemaHandler#createSchemaForClasses(java.util.Set, java.util.Properties, java.lang.Object) */ @Override public void createSchemaForClasses(Set<String> classNames, Properties props, Object connection) { DB db = (DB) connection; ManagedConnection mconn = null; try { if (db == null) { mconn = storeMgr.getConnection(-1); db = (DB) mconn.getConnection(); } Iterator<String> classIter = classNames.iterator(); ClassLoaderResolver clr = storeMgr.getNucleusContext().getClassLoaderResolver(null); while (classIter.hasNext()) { String className = classIter.next(); AbstractClassMetaData cmd = storeMgr.getMetaDataManager().getMetaDataForClass(className, clr); if (cmd != null) { createSchemaForClass(cmd, db); } } } finally { if (mconn != null) { mconn.release(); } } } protected void createSchemaForClass(AbstractClassMetaData cmd, DB db) { if (cmd.isEmbeddedOnly() || cmd.getPersistenceModifier() != ClassPersistenceModifier.PERSISTENCE_CAPABLE) { // No table required here return; } if (cmd instanceof ClassMetaData && ((ClassMetaData) cmd).isAbstract()) { // No table required here return; } StoreData storeData = storeMgr.getStoreDataForClass(cmd.getFullClassName()); Table table = null; if (storeData != null) { table = storeData.getTable(); } else { table = new CompleteClassTable(storeMgr, cmd, null); } String collectionName = table.getName(); DBCollection collection = null; if (isAutoCreateTables() && !db.collectionExists(collectionName)) { // Create collection (if not existing) if (cmd.hasExtension(MongoDBStoreManager.CAPPED_SIZE_EXTENSION_NAME)) { Set<String> collNames = db.getCollectionNames(); if (!collNames.contains(collectionName)) { // Collection specified as "capped" with a size and doesn't exist so create it DBObject options = new BasicDBObject(); options.put("capped", "true"); Long size = Long .valueOf(cmd.getValueForExtension(MongoDBStoreManager.CAPPED_SIZE_EXTENSION_NAME)); options.put("size", size); if (NucleusLogger.DATASTORE_SCHEMA.isDebugEnabled()) { NucleusLogger.DATASTORE_SCHEMA.debug(Localiser.msg("MongoDB.Schema.CreateClass", cmd.getFullClassName(), collectionName)); } db.createCollection(collectionName, options); } else { DBObject options = new BasicDBObject(); if (NucleusLogger.DATASTORE_SCHEMA.isDebugEnabled()) { NucleusLogger.DATASTORE_SCHEMA.debug(Localiser.msg("MongoDB.Schema.CreateClass", cmd.getFullClassName(), collectionName)); } collection = db.createCollection(collectionName, options); } } else { DBObject options = new BasicDBObject(); if (NucleusLogger.DATASTORE_SCHEMA.isDebugEnabled()) { NucleusLogger.DATASTORE_SCHEMA.debug( Localiser.msg("MongoDB.Schema.CreateClass", cmd.getFullClassName(), collectionName)); } collection = db.createCollection(collectionName, options); } } 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); } // Class-level indexes NamingFactory namingFactory = storeMgr.getNamingFactory(); AbstractClassMetaData theCmd = cmd; while (theCmd != null) { IndexMetaData[] clsIdxMds = theCmd.getIndexMetaData(); if (clsIdxMds != null) { for (int i = 0; i < clsIdxMds.length; i++) { IndexMetaData idxmd = clsIdxMds[i]; DBObject idxObj = getDBObjectForIndex(cmd, idxmd, table); String idxName = namingFactory.getConstraintName(theCmd, idxmd, i); if (NucleusLogger.DATASTORE_SCHEMA.isDebugEnabled()) { NucleusLogger.DATASTORE_SCHEMA.debug(Localiser.msg("MongoDB.Schema.CreateClassIndex", idxName, collectionName, idxObj)); } collection.createIndex(idxObj, idxName, idxmd.isUnique()); } } UniqueMetaData[] clsUniMds = theCmd.getUniqueMetaData(); if (clsUniMds != null) { for (int i = 0; i < clsUniMds.length; i++) { UniqueMetaData unimd = clsUniMds[i]; DBObject uniObj = getDBObjectForUnique(cmd, unimd, table); String uniName = namingFactory.getConstraintName(theCmd, unimd, i); if (NucleusLogger.DATASTORE_SCHEMA.isDebugEnabled()) { NucleusLogger.DATASTORE_SCHEMA.debug(Localiser.msg("MongoDB.Schema.CreateClassIndex", uniName, collectionName, uniObj)); } collection.createIndex(uniObj, uniName, true); } } theCmd = theCmd.getSuperAbstractClassMetaData(); } 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 (storeMgr.isStrategyDatastoreAttributed(cmd, pkFieldNumbers[i])) { applyIndex = false; break; } else if (pkMmd.getUniqueMetaData() != null) { applyIndex = false; break; } MemberColumnMapping mapping = table.getMemberColumnMappingForMember(pkMmd); Column[] cols = mapping.getColumns(); String colName = cols[0].getName(); // TODO Support multicolumn PK fields query.append(colName, 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.Schema.CreateClassIndex", pkName, collectionName, query)); } collection.createIndex(query, pkName, true); } } else if (cmd.getIdentityType() == IdentityType.DATASTORE) { if (storeMgr.isStrategyDatastoreAttributed(cmd, -1)) { // Using builtin "_id" field so nothing to do } else { BasicDBObject query = new BasicDBObject(); query.append(table.getDatastoreIdColumn().getName(), 1); String pkName = (cmd.getPrimaryKeyMetaData() != null ? cmd.getPrimaryKeyMetaData().getName() : cmd.getName() + "_PK"); if (NucleusLogger.DATASTORE_SCHEMA.isDebugEnabled()) { NucleusLogger.DATASTORE_SCHEMA.debug( Localiser.msg("MongoDB.Schema.CreateClassIndex", pkName, collectionName, query)); } collection.createIndex(query, pkName, true); } } // Column-level indexes Set<MemberColumnMapping> mappings = table.getMemberColumnMappings(); for (MemberColumnMapping mapping : mappings) { Column column = mapping.getColumn(0); UniqueMetaData unimd = mapping.getMemberMetaData().getUniqueMetaData(); if (unimd != null) { BasicDBObject query = new BasicDBObject(); query.append(column.getName(), 1); if (NucleusLogger.DATASTORE_SCHEMA.isDebugEnabled()) { NucleusLogger.DATASTORE_SCHEMA.debug(Localiser.msg("MongoDB.Schema.CreateClassIndex", unimd.getName(), collectionName, query)); } String idxName = unimd.getName(); if (idxName == null) { idxName = namingFactory.getConstraintName(cmd.getName(), mapping.getMemberMetaData(), unimd); } collection.createIndex(query, idxName, true); } else { IndexMetaData idxmd = mapping.getMemberMetaData().getIndexMetaData(); if (idxmd != null) { BasicDBObject query = new BasicDBObject(); query.append(column.getName(), 1); String idxName = namingFactory.getConstraintName(cmd.getName(), mapping.getMemberMetaData(), idxmd); if (NucleusLogger.DATASTORE_SCHEMA.isDebugEnabled()) { NucleusLogger.DATASTORE_SCHEMA.debug(Localiser.msg("MongoDB.Schema.CreateClassIndex", idxName, collectionName, query)); } collection.createIndex(query, idxName, idxmd.isUnique()); } } } } } private DBObject getDBObjectForIndex(AbstractClassMetaData cmd, IndexMetaData idxmd, Table table) { BasicDBObject idxObj = new BasicDBObject(); if (idxmd.getNumberOfColumns() > 0) { String[] idxcolNames = idxmd.getColumnNames(); for (int j = 0; j < idxcolNames.length; j++) { idxObj.append(idxcolNames[j], 1); } } else if (idxmd.getNumberOfMembers() > 0) { String[] idxMemberNames = idxmd.getMemberNames(); for (int i = 0; i < idxMemberNames.length; i++) { AbstractMemberMetaData mmd = cmd.getMetaDataForMember(idxMemberNames[i]); Column[] cols = table.getMemberColumnMappingForMember(mmd).getColumns(); for (int j = 0; j < cols.length; j++) { String colName = cols[j].getName(); idxObj.append(colName, 1); } } } return idxObj; } private DBObject getDBObjectForUnique(AbstractClassMetaData cmd, UniqueMetaData unimd, Table table) { BasicDBObject uniObj = new BasicDBObject(); if (unimd.getNumberOfColumns() > 0) { String[] unicolNames = unimd.getColumnNames(); for (int j = 0; j < unicolNames.length; j++) { uniObj.append(unicolNames[j], 1); } } else if (unimd.getMemberNames() != null) { String[] uniMemberNames = unimd.getMemberNames(); for (int i = 0; i < uniMemberNames.length; i++) { AbstractMemberMetaData mmd = cmd.getMetaDataForMember(uniMemberNames[i]); Column[] cols = table.getMemberColumnMappingForMember(mmd).getColumns(); for (int j = 0; j < cols.length; j++) { String colName = cols[j].getName(); uniObj.append(colName, 1); } } } return uniObj; } /* (non-Javadoc) * @see org.datanucleus.store.schema.AbstractStoreSchemaHandler#deleteSchemaForClasses(java.util.Set, java.util.Properties, java.lang.Object) */ @Override public void deleteSchemaForClasses(Set<String> classNames, Properties props, Object connection) { DB db = (DB) connection; ManagedConnection mconn = null; try { if (db == null) { mconn = storeMgr.getConnection(-1); db = (DB) mconn.getConnection(); } Iterator<String> classIter = classNames.iterator(); ClassLoaderResolver clr = storeMgr.getNucleusContext().getClassLoaderResolver(null); while (classIter.hasNext()) { String className = classIter.next(); AbstractClassMetaData cmd = storeMgr.getMetaDataManager().getMetaDataForClass(className, clr); if (cmd != null) { StoreData storeData = storeMgr.getStoreDataForClass(cmd.getFullClassName()); Table table = null; if (storeData != null) { table = storeData.getTable(); } else { table = new CompleteClassTable(storeMgr, cmd, null); } DBCollection collection = db.getCollection(table.getName()); collection.dropIndexes(); if (NucleusLogger.DATASTORE_SCHEMA.isDebugEnabled()) { NucleusLogger.DATASTORE_SCHEMA.debug(Localiser.msg("MongoDB.SchemaDelete.Class", cmd.getFullClassName(), table.getName())); } collection.drop(); } } } finally { if (mconn != null) { mconn.release(); } } } /* (non-Javadoc) * @see org.datanucleus.store.schema.AbstractStoreSchemaHandler#validateSchema(java.util.Set, java.util.Properties, java.lang.Object) */ @Override public void validateSchema(Set<String> classNames, Properties props, Object connection) { boolean success = true; DB db = (DB) connection; ManagedConnection mconn = null; try { if (db == null) { mconn = storeMgr.getConnection(-1); db = (DB) mconn.getConnection(); } Iterator<String> classIter = classNames.iterator(); ClassLoaderResolver clr = storeMgr.getNucleusContext().getClassLoaderResolver(null); while (classIter.hasNext()) { String className = classIter.next(); AbstractClassMetaData cmd = storeMgr.getMetaDataManager().getMetaDataForClass(className, clr); if (cmd != null) { // Validate the schema for the class StoreData storeData = storeMgr.getStoreDataForClass(cmd.getFullClassName()); Table table = null; if (storeData != null) { table = storeData.getTable(); } else { table = new CompleteClassTable(storeMgr, cmd, null); } String tableName = table.getName(); if (!db.collectionExists(tableName)) { success = false; String msg = Localiser.msg("MongoDB.SchemaValidate.Class", cmd.getFullClassName(), tableName); System.out.println(msg); NucleusLogger.DATASTORE_SCHEMA.error(msg); continue; } String msg = "Table for class=" + cmd.getFullClassName() + " with name=" + tableName + " validated"; NucleusLogger.DATASTORE_SCHEMA.info(msg); DBCollection dbColl = db.getCollection(tableName); List<DBObject> indices = new ArrayList(dbColl.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], table); DBObject indexObj = getIndexObjectForIndex(indices, idxmds[i].getName(), idxObj, true); if (indexObj != null) { indices.remove(indexObj); msg = "Index for class=" + cmd.getFullClassName() + " with name=" + idxmds[i].getName() + " validated"; NucleusLogger.DATASTORE_SCHEMA.info(msg); } else { success = false; 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], table); DBObject indexObj = getIndexObjectForIndex(indices, unimds[i].getName(), uniObj, true); if (indexObj != null) { indices.remove(indexObj); msg = "Unique index for class=" + cmd.getFullClassName() + " with name=" + unimds[i].getName() + " validated"; NucleusLogger.DATASTORE_SCHEMA.info(msg); } else { success = false; 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]); Column[] cols = table.getMemberColumnMappingForMember(pkMmd).getColumns(); String colName = cols[0].getName(); query.append(colName, 1); // TODO Support multicolumn PK field } String pkName = (cmd.getPrimaryKeyMetaData() != null ? cmd.getPrimaryKeyMetaData().getName() : cmd.getName() + "_PK"); // TODO Do through NamingFactory DBObject indexObj = getIndexObjectForIndex(indices, pkName, query, true); if (indexObj != null) { indices.remove(indexObj); msg = "Index for application-identity with name=" + pkName + " validated"; NucleusLogger.DATASTORE_SCHEMA.info(msg); } else { success = false; 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 (storeMgr.isStrategyDatastoreAttributed(cmd, -1)) { // Using builtin "_id" field so nothing to do } else { // Check unique index on PK BasicDBObject query = new BasicDBObject(); query.append(table.getDatastoreIdColumn().getName(), 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); msg = "Index for datastore-identity with name=" + pkName + " validated"; NucleusLogger.DATASTORE_SCHEMA.info(msg); } else { success = false; 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) { Column[] cols = table.getMemberColumnMappingForMember(mmds[i]).getColumns(); // TODO Check for embedded fields BasicDBObject query = new BasicDBObject(); query.append(cols[0].getName(), 1); DBObject indexObj = getIndexObjectForIndex(indices, idxmd.getName(), query, true); if (indexObj != null) { msg = "Index for field=" + mmds[i].getFullFieldName() + " with name=" + idxmd.getName() + " validated"; NucleusLogger.DATASTORE_SCHEMA.info(msg); indices.remove(indexObj); } else { success = false; 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) { Column[] cols = table.getMemberColumnMappingForMember(mmds[i]).getColumns(); // TODO Check for embedded fields BasicDBObject query = new BasicDBObject(); query.append(cols[0].getName(), 1); DBObject indexObj = getIndexObjectForIndex(indices, unimd.getName(), query, true); if (indexObj != null) { msg = "Unique index for field=" + mmds[i].getFullFieldName() + " with name=" + unimd.getName() + " validated"; NucleusLogger.DATASTORE_SCHEMA.info(msg); indices.remove(indexObj); } else { success = false; 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 { if (mconn != null) { mconn.release(); } } 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; } }