/* * Copyright (c) 2017 OBiBa. All rights reserved. * * This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.obiba.magma.datasource.mongodb; import java.util.Date; import java.util.Set; import javax.validation.constraints.NotNull; import org.bson.BSONObject; import org.obiba.magma.MagmaRuntimeException; import org.obiba.magma.Timestamped; import org.obiba.magma.Timestamps; import org.obiba.magma.Value; import org.obiba.magma.ValueTable; import org.obiba.magma.ValueTableWriter; import org.obiba.magma.support.AbstractDatasource; import org.obiba.magma.support.Initialisables; import org.obiba.magma.support.UnionTimestamps; import org.obiba.magma.type.DateTimeType; import com.google.common.collect.ImmutableSet; import com.mongodb.BasicDBObjectBuilder; import com.mongodb.DB; import com.mongodb.DBCollection; import com.mongodb.DBCursor; import com.mongodb.DBObject; import com.mongodb.WriteConcern; public class MongoDBDatasource extends AbstractDatasource { public static final String TYPE = "mongodb"; public static final int MAX_BATCH_SIZE = 1000; static final String TIMESTAMPS_FIELD = "_timestamps"; static final String TIMESTAMPS_CREATED_FIELD = "created"; static final String TIMESTAMPS_UPDATED_FIELD = "updated"; private static final String DATASOURCE_COLLECTION = "datasource"; private static final String VALUE_TABLE_COLLECTION = "value_table"; @NotNull private final MongoDBFactory mongoDBFactory; private DBObject dbObject; private int batchSize = 100; /** * See <a href="http://docs.mongodb.org/manual/reference/connection-string">MongoDB connection string specifications</a>. * * @param name * @param mongoURI */ public MongoDBDatasource(@NotNull String name, @NotNull MongoDBFactory mongoDBFactory) { super(name, TYPE); this.mongoDBFactory = mongoDBFactory; } @NotNull MongoDBFactory getMongoDBFactory() { return mongoDBFactory; } DBCollection getValueTableCollection() { return mongoDBFactory.execute(new MongoDBFactory.MongoDBCallback<DBCollection>() { @Override public DBCollection doWithDB(DB db) { DBCollection collection = db.getCollection(VALUE_TABLE_COLLECTION); collection.createIndex("datasource"); collection.createIndex("name"); return collection; } }); } DBCollection getDatasourceCollection() { return mongoDBFactory.execute(new MongoDBFactory.MongoDBCallback<DBCollection>() { @Override public DBCollection doWithDB(DB db) { DBCollection collection = db.getCollection(DATASOURCE_COLLECTION); collection.createIndex("name"); return collection; } }); } DBObject asDBObject() { if(dbObject == null) { dbObject = getDatasourceCollection().findOne(BasicDBObjectBuilder.start() // .add("name", getName()) // .get()); if(dbObject == null) { dbObject = BasicDBObjectBuilder.start() // .add("name", getName()) // .add(TIMESTAMPS_FIELD, createTimestampsObject()).get(); getDatasourceCollection().insert(dbObject, WriteConcern.ACKNOWLEDGED); } } return dbObject; } void setLastUpdate(Date date) { ((BSONObject) asDBObject().get(TIMESTAMPS_FIELD)).put("updated", date); getDatasourceCollection().save(asDBObject()); } static DBObject createTimestampsObject() { return BasicDBObjectBuilder.start() // .add(TIMESTAMPS_CREATED_FIELD, new Date()) // .add(TIMESTAMPS_UPDATED_FIELD, new Date()).get(); } @Override protected void onInitialise() { mongoDBFactory.getMongoClient(); } @Override protected void onDispose() { mongoDBFactory.close(); } @Override public boolean canDropTable(String tableName) { return hasValueTable(tableName); } @Override public void dropTable(@NotNull String tableName) { MongoDBValueTable valueTable = (MongoDBValueTable) getValueTable(tableName); valueTable.drop(); removeValueTable(tableName); } @Override public boolean canRenameTable(String tableName) { return hasValueTable(tableName); } @Override public void renameTable(String tableName, String newName) { if(hasValueTable(newName)) throw new MagmaRuntimeException("A table already exists with the name: " + newName); MongoDBValueTable valueTable = (MongoDBValueTable) getValueTable(tableName); valueTable.asDBObject().put("name", newName); ((BSONObject) valueTable.asDBObject().get(TIMESTAMPS_FIELD)).put("updated", new Date()); getValueTableCollection().save(valueTable.asDBObject(), WriteConcern.ACKNOWLEDGED); removeValueTable(valueTable); MongoDBValueTable newTable = new MongoDBValueTable(this, newName); Initialisables.initialise(newTable); addValueTable(newTable); } @Override @SuppressWarnings("MethodReturnAlwaysConstant") public boolean canDrop() { return true; } @Override public void drop() { for(String name : getValueTableNames()) { dropTable(name); } getDatasourceCollection().remove(BasicDBObjectBuilder.start().add("_id", getName()).get()); } @NotNull @Override public ValueTableWriter createWriter(@NotNull String tableName, @NotNull String entityType) { MongoDBValueTable valueTable = null; if(getValueTableNames().isEmpty()) { // make sure datasource document exists asDBObject(); } if(hasValueTable(tableName)) { valueTable = (MongoDBValueTable) getValueTable(tableName); } else { addValueTable(valueTable = new MongoDBValueTable(this, tableName, entityType)); setLastUpdate(new Date()); } return new MongoDBValueTableWriter(valueTable); } @Override protected Set<String> getValueTableNames() { ImmutableSet.Builder<String> builder = ImmutableSet.builder(); try(DBCursor cursor = getValueTableCollection() .find(BasicDBObjectBuilder.start().add("datasource", asDBObject().get("_id")).get(), BasicDBObjectBuilder.start().add("name", 1).get())) { while(cursor.hasNext()) { builder.add(cursor.next().get("name").toString()); } } return builder.build(); } @Override protected ValueTable initialiseValueTable(String tableName) { return new MongoDBValueTable(this, tableName); } @NotNull @Override public Timestamps getTimestamps() { ImmutableSet.Builder<Timestamped> builder = ImmutableSet.builder(); builder.addAll(getValueTables()).add(new MongoDBDatasourceTimestamped()); return new UnionTimestamps(builder.build()); } public int getBatchSize() { return batchSize; } public void setBatchSize(int batchSize) { if (batchSize < 1 || batchSize > MAX_BATCH_SIZE) throw new IllegalArgumentException(String.format("batchSize should be between 1 and %s", MAX_BATCH_SIZE)); this.batchSize = batchSize; } private class MongoDBDatasourceTimestamped implements Timestamped { @NotNull @Override public Timestamps getTimestamps() { return new Timestamps() { private final BSONObject timestampsObject = (BSONObject) asDBObject().get(TIMESTAMPS_FIELD); @NotNull @Override public Value getLastUpdate() { return DateTimeType.get().valueOf(timestampsObject.get(TIMESTAMPS_UPDATED_FIELD)); } @NotNull @Override public Value getCreated() { return DateTimeType.get().valueOf(timestampsObject.get(TIMESTAMPS_CREATED_FIELD)); } }; } } }