/** * @author Guillaume Scheibel <[email protected]> * @author Sanne Grinovero <[email protected]> */ public class MongoDBTestHelper implements GridDialectTestHelper { private static final Log log = LoggerFactory.getLogger(); static { // Read host and port from environment variable // Maven's surefire plugin set it to the string 'null' String mongoHostName = System.getenv("MONGODB_HOSTNAME"); if (isNotNull(mongoHostName)) { System.getProperties().setProperty(OgmProperties.HOST, mongoHostName); } String mongoPort = System.getenv("MONGODB_PORT"); if (isNotNull(mongoPort)) { System.getProperties().setProperty(OgmProperties.PORT, mongoPort); } } private static boolean isNotNull(String mongoHostName) { return mongoHostName != null && mongoHostName.length() > 0 && !"null".equals(mongoHostName); } @Override public long getNumberOfEntities(Session session) { return getNumberOfEntities(session.getSessionFactory()); } @Override public long getNumberOfEntities(SessionFactory sessionFactory) { MongoDBDatastoreProvider provider = MongoDBTestHelper.getProvider(sessionFactory); DB db = provider.getDatabase(); int count = 0; for (String collectionName : getEntityCollections(sessionFactory)) { count += db.getCollection(collectionName).count(); } return count; } private boolean isSystemCollection(String collectionName) { return collectionName.startsWith("system."); } @Override public long getNumberOfAssociations(Session session) { return getNumberOfAssociations(session.getSessionFactory()); } @Override public long getNumberOfAssociations(SessionFactory sessionFactory) { long associationCount = getNumberOfAssociationsFromGlobalCollection(sessionFactory); associationCount += getNumberOfAssociationsFromDedicatedCollections(sessionFactory); associationCount += getNumberOfEmbeddedAssociations(sessionFactory); return associationCount; } public long getNumberOfAssociationsFromGlobalCollection(SessionFactory sessionFactory) { DB db = getProvider(sessionFactory).getDatabase(); return db.getCollection(MongoDBConfiguration.DEFAULT_ASSOCIATION_STORE).count(); } public long getNumberOfAssociationsFromDedicatedCollections(SessionFactory sessionFactory) { DB db = getProvider(sessionFactory).getDatabase(); Set<String> associationCollections = getDedicatedAssociationCollections(sessionFactory); long associationCount = 0; for (String collectionName : associationCollections) { associationCount += db.getCollection(collectionName).count(); } return associationCount; } // TODO Use aggregation framework for a more efficient solution; Given that there will only be a // few // test collections/entities, that's good enough for now public long getNumberOfEmbeddedAssociations(SessionFactory sessionFactory) { DB db = getProvider(sessionFactory).getDatabase(); long associationCount = 0; for (String entityCollection : getEntityCollections(sessionFactory)) { DBCursor entities = db.getCollection(entityCollection).find(); while (entities.hasNext()) { DBObject entity = entities.next(); associationCount += getNumberOfEmbeddedAssociations(entity); } } return associationCount; } private int getNumberOfEmbeddedAssociations(DBObject entity) { int numberOfReferences = 0; for (String fieldName : entity.keySet()) { Object field = entity.get(fieldName); if (isAssociation(field)) { numberOfReferences++; } } return numberOfReferences; } private boolean isAssociation(Object field) { return (field instanceof List); } private Set<String> getEntityCollections(SessionFactory sessionFactory) { DB db = MongoDBTestHelper.getProvider(sessionFactory).getDatabase(); Set<String> names = new HashSet<String>(); for (String collectionName : db.getCollectionNames()) { if (!isSystemCollection(collectionName) && !isDedicatedAssociationCollection(collectionName) && !isGlobalAssociationCollection(collectionName)) { names.add(collectionName); } } return names; } private Set<String> getDedicatedAssociationCollections(SessionFactory sessionFactory) { DB db = MongoDBTestHelper.getProvider(sessionFactory).getDatabase(); Set<String> names = new HashSet<String>(); for (String collectionName : db.getCollectionNames()) { if (isDedicatedAssociationCollection(collectionName)) { names.add(collectionName); } } return names; } private boolean isDedicatedAssociationCollection(String collectionName) { return collectionName.startsWith(MongoDBDialect.ASSOCIATIONS_COLLECTION_PREFIX); } private boolean isGlobalAssociationCollection(String collectionName) { return collectionName.equals(MongoDBConfiguration.DEFAULT_ASSOCIATION_STORE); } @Override @SuppressWarnings("unchecked") public Map<String, Object> extractEntityTuple(Session session, EntityKey key) { MongoDBDatastoreProvider provider = MongoDBTestHelper.getProvider(session.getSessionFactory()); DBObject finder = new BasicDBObject(MongoDBDialect.ID_FIELDNAME, key.getColumnValues()[0]); DBObject result = provider.getDatabase().getCollection(key.getTable()).findOne(finder); replaceIdentifierColumnName(result, key); return result.toMap(); } /** * The MongoDB dialect replaces the name of the column identifier, so when the tuple is extracted * from the db we replace the column name of the identifier with the original one. We are assuming * the identifier is not embedded and is a single property. */ private void replaceIdentifierColumnName(DBObject result, EntityKey key) { Object idValue = result.get(MongoDBDialect.ID_FIELDNAME); result.removeField(MongoDBDialect.ID_FIELDNAME); result.put(key.getColumnNames()[0], idValue); } @Override public boolean backendSupportsTransactions() { return false; } private static MongoDBDatastoreProvider getProvider(SessionFactory sessionFactory) { DatastoreProvider provider = ((SessionFactoryImplementor) sessionFactory) .getServiceRegistry() .getService(DatastoreProvider.class); if (!(MongoDBDatastoreProvider.class.isInstance(provider))) { throw new RuntimeException("Not testing with MongoDB, cannot extract underlying cache"); } return MongoDBDatastoreProvider.class.cast(provider); } @Override public void dropSchemaAndDatabase(SessionFactory sessionFactory) { MongoDBDatastoreProvider provider = getProvider(sessionFactory); try { provider.getDatabase().dropDatabase(); } catch (MongoException ex) { throw log.unableToDropDatabase(ex, provider.getDatabase().getName()); } } @Override public Map<String, String> getEnvironmentProperties() { // read variables from the System properties set in the static initializer Map<String, String> envProps = new HashMap<String, String>(2); copyFromSystemPropertiesToLocalEnvironment(OgmProperties.HOST, envProps); copyFromSystemPropertiesToLocalEnvironment(OgmProperties.PORT, envProps); copyFromSystemPropertiesToLocalEnvironment(OgmProperties.USERNAME, envProps); copyFromSystemPropertiesToLocalEnvironment(OgmProperties.PASSWORD, envProps); return envProps; } private void copyFromSystemPropertiesToLocalEnvironment( String environmentVariableName, Map<String, String> envProps) { String value = System.getProperties().getProperty(environmentVariableName); if (value != null && value.length() > 0) { envProps.put(environmentVariableName, value); } } @Override public long getNumberOfAssociations(SessionFactory sessionFactory, AssociationStorageType type) { switch (type) { case ASSOCIATION_DOCUMENT: return getNumberOfAssociationsFromGlobalCollection(sessionFactory); case IN_ENTITY: return getNumberOfEmbeddedAssociations(sessionFactory); default: throw new IllegalArgumentException("Unexpected association storaget type " + type); } } @Override public GridDialect getGridDialect(DatastoreProvider datastoreProvider) { return new MongoDBDialect((MongoDBDatastoreProvider) datastoreProvider); } public static void assertDbObject( OgmSessionFactory sessionFactory, String collection, String queryDbObject, String expectedDbObject) { assertDbObject(sessionFactory, collection, queryDbObject, null, expectedDbObject); } public static void assertDbObject( OgmSessionFactory sessionFactory, String collection, String queryDbObject, String projectionDbObject, String expectedDbObject) { DBObject finder = (DBObject) JSON.parse(queryDbObject); DBObject fields = projectionDbObject != null ? (DBObject) JSON.parse(projectionDbObject) : null; MongoDBDatastoreProvider provider = MongoDBTestHelper.getProvider(sessionFactory); DBObject actual = provider.getDatabase().getCollection(collection).findOne(finder, fields); assertJsonEquals(expectedDbObject, actual.toString()); } public static Map<String, DBObject> getIndexes( OgmSessionFactory sessionFactory, String collection) { MongoDBDatastoreProvider provider = MongoDBTestHelper.getProvider(sessionFactory); List<DBObject> indexes = provider.getDatabase().getCollection(collection).getIndexInfo(); Map<String, DBObject> indexMap = new HashMap<>(); for (DBObject index : indexes) { indexMap.put(index.get("name").toString(), index); } return indexMap; } public static void dropIndexes(OgmSessionFactory sessionFactory, String collection) { MongoDBDatastoreProvider provider = MongoDBTestHelper.getProvider(sessionFactory); provider.getDatabase().getCollection(collection).dropIndexes(); } public static void assertJsonEquals(String expectedJson, String actualJson) { try { JSONCompareResult result = JSONCompare.compareJSON(expectedJson, actualJson, JSONCompareMode.NON_EXTENSIBLE); if (result.failed()) { throw new AssertionError(result.getMessage() + "; Actual: " + actualJson); } } catch (JSONException e) { Exceptions.<RuntimeException>sneakyThrow(e); } } @Override public Class<? extends DatastoreConfiguration<?>> getDatastoreConfigurationType() { return MongoDB.class; } }
/** * Configuration for {@link MongoDBDatastoreProvider}. * * @author Guillaume Scheibel <*****@*****.**> * @author Gunnar Morling */ public class MongoDBConfiguration extends DocumentStoreConfiguration { public static final String DEFAULT_ASSOCIATION_STORE = "Associations"; /** * The default value used to set the timeout during the connection to the MongoDB instance This * value is set in milliseconds. * * @see MongoDBProperties#TIMEOUT */ private static final int DEFAULT_TIMEOUT = 5000; private static final int DEFAULT_PORT = 27017; private static final Log log = LoggerFactory.getLogger(); private static final TimeoutValidator TIMEOUT_VALIDATOR = new TimeoutValidator(); private final int timeout; private final WriteConcern writeConcern; private final ReadPreference readPreference; /** * Creates a new {@link MongoDBConfiguration}. * * @param configurationValues configuration values given via {@code persistence.xml} etc. * @param globalOptions global settings given via an option configurator */ public MongoDBConfiguration( ConfigurationPropertyReader propertyReader, OptionsContext globalOptions) { super(propertyReader, DEFAULT_PORT); this.timeout = propertyReader .property(MongoDBProperties.TIMEOUT, int.class) .withDefault(DEFAULT_TIMEOUT) .withValidator(TIMEOUT_VALIDATOR) .getValue(); this.writeConcern = globalOptions.getUnique(WriteConcernOption.class); this.readPreference = globalOptions.getUnique(ReadPreferenceOption.class); } /** * Create a {@link MongoClientOptions} using the {@link MongoDBConfiguration}. * * @return the {@link MongoClientOptions} corresponding to the {@link MongoDBConfiguration} */ public MongoClientOptions buildOptions() { MongoClientOptions.Builder optionsBuilder = new MongoClientOptions.Builder(); optionsBuilder.connectTimeout(timeout); optionsBuilder.writeConcern(writeConcern); optionsBuilder.readPreference(readPreference); return optionsBuilder.build(); } private static class TimeoutValidator implements PropertyValidator<Integer> { @Override public void validate(Integer value) throws HibernateException { if (value < 0) { throw log.mongoDBTimeOutIllegalValue(value); } } } }