/* * Copyright 2011 Paul Merlin. * * 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. */ package org.qi4j.entitystore.mongodb; import com.mongodb.BasicDBObject; import com.mongodb.DB; import com.mongodb.DBCollection; import com.mongodb.DBCursor; import com.mongodb.DBObject; import com.mongodb.Mongo; import com.mongodb.ServerAddress; import com.mongodb.WriteConcern; import com.mongodb.util.JSON; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.json.JSONException; import org.json.JSONObject; import org.qi4j.api.configuration.Configuration; import org.qi4j.api.entity.EntityDescriptor; import org.qi4j.api.entity.EntityReference; import org.qi4j.api.injection.scope.This; import org.qi4j.api.service.ServiceActivation; import org.qi4j.io.Input; import org.qi4j.io.Output; import org.qi4j.io.Receiver; import org.qi4j.io.Sender; import org.qi4j.spi.entitystore.EntityNotFoundException; import org.qi4j.spi.entitystore.EntityStoreException; import org.qi4j.spi.entitystore.helpers.MapEntityStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * MongoDB implementation of MapEntityStore. */ public class MongoMapEntityStoreMixin implements ServiceActivation, MapEntityStore, MongoAccessors { private static final Logger LOGGER = LoggerFactory.getLogger( "org.qi4j.entitystore.mongodb" ); private static final String DEFAULT_DATABASE_NAME = "qi4j:entitystore"; private static final String DEFAULT_COLLECTION_NAME = "qi4j:entitystore:entities"; public static final String IDENTITY_COLUMN = "identity"; public static final String STATE_COLUMN = "state"; @This private Configuration<MongoEntityStoreConfiguration> configuration; private List<ServerAddress> serverAddresses; private String databaseName; private String collectionName; private WriteConcern writeConcern; private String username; private char[] password; private Mongo mongo; private DB db; @Override public void activateService() throws Exception { loadConfiguration(); // Create Mongo driver and open the database mongo = new Mongo( serverAddresses ); db = mongo.getDB( databaseName ); // Authenticate if needed if( !username.isEmpty() ) { if( !db.authenticate( username, password ) ) { LOGGER.warn( "Authentication against MongoDB with username '" + username + "' failed. Subsequent requests will be made 'anonymously'." ); } } // Create index if needed db.requestStart(); DBCollection entities = db.getCollection( collectionName ); if( entities.getIndexInfo().isEmpty() ) { entities.createIndex( new BasicDBObject( IDENTITY_COLUMN, 1 ) ); } db.requestDone(); } private void loadConfiguration() throws UnknownHostException { configuration.refresh(); MongoEntityStoreConfiguration config = configuration.get(); // Combine hostname, port and nodes configuration properties serverAddresses = new ArrayList<ServerAddress>(); if( config.hostname().get() != null && !config.hostname().get().isEmpty() ) { serverAddresses.add( new ServerAddress( config.hostname().get(), config.port().get() ) ); } serverAddresses.addAll( config.nodes().get() ); // If database name not configured, set it to qi4j:entitystore databaseName = config.database().get(); if( databaseName == null ) { databaseName = DEFAULT_DATABASE_NAME; } // If collection name not configured, set it to qi4j:entitystore:entities collectionName = config.collection().get(); if( collectionName == null ) { collectionName = DEFAULT_COLLECTION_NAME; } // If write concern not configured, set it to normal switch( config.writeConcern().get() ) { case FSYNC_SAFE: writeConcern = WriteConcern.FSYNC_SAFE; break; case JOURNAL_SAFE: writeConcern = WriteConcern.JOURNAL_SAFE; break; case MAJORITY: writeConcern = WriteConcern.MAJORITY; break; case NONE: writeConcern = WriteConcern.NONE; break; case REPLICAS_SAFE: writeConcern = WriteConcern.REPLICAS_SAFE; break; case SAFE: writeConcern = WriteConcern.SAFE; break; case NORMAL: default: writeConcern = WriteConcern.NORMAL; } // Username and password are defaulted to empty strings username = config.username().get(); password = config.password().get().toCharArray(); } @Override public void passivateService() throws Exception { mongo.close(); mongo = null; databaseName = null; collectionName = null; writeConcern = null; username = null; Arrays.fill( password, ' ' ); password = null; db = null; } @Override public Mongo mongoInstanceUsed() { return mongo; } @Override public DB dbInstanceUsed() { return db; } @Override public String collectionUsed() { return collectionName; } @Override public Reader get( EntityReference entityReference ) throws EntityStoreException { db.requestStart(); DBObject entity = db.getCollection( collectionName ).findOne( byIdentity( entityReference ) ); if( entity == null ) { throw new EntityNotFoundException( entityReference ); } DBObject bsonState = (DBObject) entity.get( STATE_COLUMN ); db.requestDone(); String jsonState = JSON.serialize( bsonState ); return new StringReader( jsonState ); } @Override public void applyChanges( MapChanges changes ) throws IOException { db.requestStart(); final DBCollection entities = db.getCollection( collectionName ); changes.visitMap( new MapChanger() { @Override public Writer newEntity( final EntityReference ref, EntityDescriptor entityDescriptor ) throws IOException { return new StringWriter( 1000 ) { @Override public void close() throws IOException { super.close(); String jsonState = toString(); System.out.println( "############################################" ); try { System.out.println( new JSONObject( jsonState ).toString( 2 ) ); } catch( JSONException ex ) { ex.printStackTrace(); } System.out.println( "############################################" ); DBObject bsonState = (DBObject) JSON.parse( jsonState ); BasicDBObject entity = new BasicDBObject(); entity.put( IDENTITY_COLUMN, ref.identity() ); entity.put( STATE_COLUMN, bsonState ); entities.save( entity, writeConcern ); } }; } @Override public Writer updateEntity( final EntityReference ref, EntityDescriptor entityDescriptor ) throws IOException { return new StringWriter( 1000 ) { @Override public void close() throws IOException { super.close(); DBObject bsonState = (DBObject) JSON.parse( toString() ); BasicDBObject entity = new BasicDBObject(); entity.put( IDENTITY_COLUMN, ref.identity() ); entity.put( STATE_COLUMN, bsonState ); entities.update( byIdentity( ref ), entity, true, false, writeConcern ); } }; } @Override public void removeEntity( EntityReference ref, EntityDescriptor entityDescriptor ) throws EntityNotFoundException { DBObject entity = entities.findOne( byIdentity( ref ) ); if( entity == null ) { throw new EntityNotFoundException( ref ); } entities.remove( entity, writeConcern ); } } ); db.requestDone(); } @Override public Input<Reader, IOException> entityStates() { return new Input<Reader, IOException>() { @Override public <ReceiverThrowableType extends Throwable> void transferTo( Output<? super Reader, ReceiverThrowableType> output ) throws IOException, ReceiverThrowableType { output.receiveFrom( new Sender<Reader, IOException>() { @Override public <ReceiverThrowableType extends Throwable> void sendTo( Receiver<? super Reader, ReceiverThrowableType> receiver ) throws ReceiverThrowableType, IOException { db.requestStart(); DBCursor cursor = db.getCollection( collectionName ).find(); while( cursor.hasNext() ) { DBObject eachEntity = cursor.next(); DBObject bsonState = (DBObject) eachEntity.get( STATE_COLUMN ); String jsonState = JSON.serialize( bsonState ); receiver.receive( new StringReader( jsonState ) ); } db.requestDone(); } } ); } }; } private DBObject byIdentity( EntityReference entityReference ) { return new BasicDBObject( IDENTITY_COLUMN, entityReference.identity() ); } }