/*
* Hibernate OGM, Domain model persistence for NoSQL datastores
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.ogm.datastore.mongodb.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.MongoCredential;
import com.mongodb.MongoException;
import com.mongodb.ServerAddress;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.ogm.cfg.spi.Hosts;
import org.hibernate.ogm.datastore.mongodb.MongoDBDialect;
import org.hibernate.ogm.datastore.mongodb.configuration.impl.MongoDBConfiguration;
import org.hibernate.ogm.datastore.mongodb.logging.impl.Log;
import org.hibernate.ogm.datastore.mongodb.logging.impl.LoggerFactory;
import org.hibernate.ogm.datastore.mongodb.query.parsing.impl.MongoDBBasedQueryParserService;
import org.hibernate.ogm.datastore.spi.BaseDatastoreProvider;
import org.hibernate.ogm.datastore.spi.SchemaDefiner;
import org.hibernate.ogm.dialect.spi.GridDialect;
import org.hibernate.ogm.options.spi.OptionsService;
import org.hibernate.ogm.query.spi.QueryParserService;
import org.hibernate.ogm.util.configurationreader.spi.ConfigurationPropertyReader;
import org.hibernate.service.spi.Configurable;
import org.hibernate.service.spi.ServiceRegistryAwareService;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.service.spi.Startable;
import org.hibernate.service.spi.Stoppable;
/**
* Provides access to a MongoDB instance
*
* @author Guillaume Scheibel <guillaume.scheibel@gmail.com>
* @author Gunnar Morling
*/
public class MongoDBDatastoreProvider extends BaseDatastoreProvider implements Startable, Stoppable, Configurable, ServiceRegistryAwareService {
private static final Log log = LoggerFactory.getLogger();
private ServiceRegistryImplementor serviceRegistry;
private MongoClient mongo;
private MongoDatabase mongoDb;
private MongoDBConfiguration config;
public MongoDBDatastoreProvider() {
}
/**
* Only used in tests.
*
* @param mongoClient the client to connect to mongodb
*/
public MongoDBDatastoreProvider(MongoClient mongoClient) {
this.mongo = mongoClient;
}
@Override
public void configure(Map configurationValues) {
OptionsService optionsService = serviceRegistry.getService( OptionsService.class );
ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class );
ConfigurationPropertyReader propertyReader = new ConfigurationPropertyReader( configurationValues, classLoaderService );
try {
this.config = new MongoDBConfiguration( propertyReader, optionsService.context().getGlobalOptions() );
}
catch (Exception e) {
// Wrap Exception in a ServiceException to make the stack trace more friendly
// Otherwise a generic unable to request service is thrown
throw log.unableToConfigureDatastoreProvider( e );
}
}
@Override
public void injectServices(ServiceRegistryImplementor serviceRegistry) {
this.serviceRegistry = serviceRegistry;
}
@Override
public Class<? extends GridDialect> getDefaultDialect() {
return MongoDBDialect.class;
}
@Override
public Class<? extends QueryParserService> getDefaultQueryParserServiceType() {
return MongoDBBasedQueryParserService.class;
}
@Override
public Class<? extends SchemaDefiner> getSchemaDefinerType() {
return MongoDBSchemaDefiner.class;
}
@Override
public boolean allowsTransactionEmulation() {
return true;
}
@Override
public void start() {
try {
if ( mongo == null ) {
mongo = createMongoClient( config );
}
mongoDb = extractDatabase( mongo, config );
}
catch (Exception e) {
// Wrap Exception in a ServiceException to make the stack trace more friendly
// Otherwise a generic unable to request service is thrown
throw log.unableToStartDatastoreProvider( e );
}
}
protected MongoClient createMongoClient(MongoDBConfiguration config) {
MongoClientOptions clientOptions = config.buildOptions();
List<MongoCredential> credentials = config.buildCredentials();
log.connectingToMongo( config.getHosts().toString(), clientOptions.getConnectTimeout() );
try {
List<ServerAddress> serverAddresses = new ArrayList<>( config.getHosts().size() );
for ( Hosts.HostAndPort hostAndPort : config.getHosts() ) {
serverAddresses.add( new ServerAddress( hostAndPort.getHost(), hostAndPort.getPort() ) );
}
return credentials == null
? new MongoClient( serverAddresses, clientOptions )
: new MongoClient( serverAddresses, credentials, clientOptions );
}
catch (RuntimeException e) {
throw log.unableToInitializeMongoDB( e );
}
}
@Override
public void stop() {
log.disconnectingFromMongo();
mongo.close();
}
public MongoDatabase getDatabase() {
return mongoDb;
}
private MongoDatabase extractDatabase(MongoClient mongo, MongoDBConfiguration config) {
try {
String databaseName = config.getDatabaseName();
log.connectingToMongoDatabase( databaseName );
Boolean containsDatabase = containsDatabase( mongo, databaseName );
if ( containsDatabase == Boolean.FALSE ) {
if ( config.isCreateDatabase() ) {
log.creatingDatabase( databaseName );
}
else {
throw log.databaseDoesNotExistException( config.getDatabaseName() );
}
}
MongoDatabase db = mongo.getDatabase( databaseName );
if ( containsDatabase == null ) {
// force a connection to make sure we do have read access
// otherwise the connection failure happens during the first flush
MongoCursor<String> it = db.listCollectionNames().iterator();
while ( it.hasNext() ) {
if ( ( it.next().equalsIgnoreCase( "WeDoNotCareWhatItIsWeNeedToConnect" ) ) ) {
containsDatabase = true;
break;
}
}
}
return mongo.getDatabase( databaseName );
}
catch (MongoException me) {
// The Mongo driver allows not to determine the cause of the error, eg failing authentication, anymore
// by error code. At best the message contains some more information.
// See also http://stackoverflow.com/questions/30455152/check-mongodb-authentication-with-java-3-0-driver
throw log.unableToConnectToDatastore( me.getMessage(), me );
}
}
private Boolean containsDatabase(MongoClient mongo, String databaseName) {
try {
for ( String existingName : mongo.listDatabaseNames() ) {
if ( existingName.equals( databaseName ) ) {
return Boolean.TRUE;
}
}
return Boolean.FALSE;
}
catch (MongoException me) {
// we don't have enough privileges, ignore the database creation
return null;
}
}
}