package cz.cuni.mff.d3s.been.objectrepository.mongo; import java.net.UnknownHostException; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.mongodb.*; import com.mongodb.util.JSON; import cz.cuni.mff.d3s.been.core.persistence.EntityCarrier; import cz.cuni.mff.d3s.been.core.persistence.EntityID; import cz.cuni.mff.d3s.been.persistence.*; import cz.cuni.mff.d3s.been.storage.*; /** * A MongoDB adapter for BEEN result persistence layer. * * @author darklight * */ public final class MongoStorage implements Storage { private static final Logger log = LoggerFactory.getLogger(MongoStorage.class); private final boolean authenticate; private final String username; private final String password; private final String dbname; private final MongoClient client; private final MongoQueryRedactorFactory queryRedactorFactory; private final QueryTranslator queryTranslator; private DB db; private QueryExecutorFactory queryExecutorFactory; private MongoStorage(MongoClient client, String dbname) { this.client = client; this.dbname = dbname; this.authenticate = false; this.username = null; this.password = null; this.queryRedactorFactory = new MongoQueryRedactorFactory(); this.queryTranslator = new QueryTranslator(); } private MongoStorage(MongoClient client, String dbname, String username, String password) { this.client = client; this.dbname = dbname; this.authenticate = true; this.username = username; this.password = password; this.queryRedactorFactory = new MongoQueryRedactorFactory(); this.queryTranslator = new QueryTranslator(); } /** * Creates MongoStorage. * * @param hostname * DB's hostname * @param dbname * DB's name * @param opts * DB's options * @return new MongoStorages * @throws UnknownHostException * when the DB cannot be reached */ public static MongoStorage create(String hostname, String dbname, MongoClientOptions opts) throws UnknownHostException { final MongoClient client = new MongoClient(hostname, opts); return new MongoStorage(client, dbname); } /** * * Creates MongoStorage. * * @param seeds * list of server addresses to connect to * @param dbname * DB's name * @param opts * DB's options * @return new MongoStorage */ public static MongoStorage create(List<ServerAddress> seeds, String dbname, MongoClientOptions opts) { final MongoClient client = new MongoClient(seeds, opts); return new MongoStorage(client, dbname); } /** * * Creates MongoStorage. * * @param hostname * DB's hostname * @param dbname * DB's name * @param username * DB's user * @param password * user's DB password * @param opts * DB's options * @return new MongoStorage * @throws UnknownHostException */ public static MongoStorage create(String hostname, String dbname, String username, String password, MongoClientOptions opts) throws UnknownHostException { final MongoClient client = new MongoClient(hostname, opts); return new MongoStorage(client, dbname, username, password); } /** * Creates MongoStorage. * * @param seeds * list of server addresses to connect to * @param dbname * DB's name * @param username * DB's user * @param password * user's DB password * @param opts * DB's options * @return new MongoStorage */ public static MongoStorage create(List<ServerAddress> seeds, String dbname, String username, String password, MongoClientOptions opts) { final MongoClient client = new MongoClient(seeds, opts); return new MongoStorage(client, dbname, username, password); } @Override public void start() throws StorageException { db = client.getDB(dbname); if (authenticate && !db.authenticate(username, password.toCharArray())) { throw new StorageException("Failed to authenticate against BEEN database"); } if (!isConnected()) { throw new StorageException("Error getting DB stats. Mongo database is probably not running."); } queryExecutorFactory = new MongoQueryExecutorFactory(db); } @Override public void stop() { client.close(); } @Override public void store(EntityID entityId, String entityJSON) throws DAOException { try { final WriteResult wr = mapEntity(entityId).insert((DBObject) JSON.parse(entityJSON)); if (wr.getError() != null) { throw new DAOException(String.format( "Write on Entity ID %s resulted in the following error: %s.", entityId.toString(), wr.getError())); } } catch (MongoException e) { throw new DAOException(String.format( "Failed to store persistent object (%s, %s)", entityId.toString(), entityJSON), e); } } @Override public SuccessAction<EntityCarrier> createPersistAction() { return StoragePersistAction.createForStore(this); } @Override public QueryAnswer query(Query query) { QueryRedactor queryRedactor; switch (query.getType()) { case FETCH: queryRedactor = queryRedactorFactory.fetch(query.getEntityID()); break; case DELETE: queryRedactor = queryRedactorFactory.delete(query.getEntityID()); break; default: return QueryAnswerFactory.badQuery(); } try { queryTranslator.interpret(query, queryRedactor); } catch (DAOException e) { log.error("Unsupported query '{}'", query, e); return QueryAnswerFactory.badQuery(); } final QueryExecutor queryExecutor; try { queryExecutor = queryExecutorFactory.createExecutor(queryRedactor); } catch (DAOException e) { log.error("Query '{}' is invalid.", query, e); return QueryAnswerFactory.badQuery(); } try { return queryExecutor.execute(); } catch (QueryExecutionException e) { log.error("Failed to execute query '{}': {}", query, e); return QueryAnswerFactory.queryExecutionFailed(); } catch (MongoException e) { if (e instanceof MongoException.Network) { return QueryAnswerFactory.persistenceDown(); } else { log.error("Unknown MongoDB exception processing query '{}'", query, e); return QueryAnswerFactory.unknownError(); } } catch (Throwable t) { log.error("Unknown error processing query '{}'", query, t); return QueryAnswerFactory.unknownError(); } } @Override public boolean isConnected() { try { CommandResult cr = db.getStats(); log.debug("MongoDB connected with stats {}", cr.toString()); return true; } catch (MongoException e) { if (e instanceof MongoException.Network) { log.warn("MongoDB disconnected"); return false; } else { return true; } } } @Override public boolean isIdle() { // FIXME return heuristics about current DB work load return true; } private final DBCollection mapEntity(EntityID eid) throws DAOException { return db.getCollection(eid.getKind()).getCollection(eid.getGroup()); } }