/*
* Copyright 2015-2016 OpenCB
*
* 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.opencb.opencga.catalog.db.mongodb;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mongodb.client.model.Aggregates;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Projections;
import com.mongodb.client.model.Updates;
import com.mongodb.client.result.UpdateResult;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.opencb.commons.datastore.core.ObjectMap;
import org.opencb.commons.datastore.core.QueryOptions;
import org.opencb.commons.datastore.core.QueryResult;
import org.opencb.commons.datastore.mongodb.MongoDBCollection;
import org.opencb.opencga.catalog.auth.authentication.CatalogAuthenticationManager;
import org.opencb.opencga.catalog.config.Admin;
import org.opencb.opencga.catalog.config.Configuration;
import org.opencb.opencga.catalog.db.api.CohortDBAdaptor;
import org.opencb.opencga.catalog.db.api.MetaDBAdaptor;
import org.opencb.opencga.catalog.exceptions.CatalogDBException;
import org.opencb.opencga.catalog.exceptions.CatalogException;
import org.opencb.opencga.catalog.models.Metadata;
import org.opencb.opencga.catalog.models.Session;
import org.opencb.opencga.catalog.models.acls.permissions.StudyAclEntry;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;
import static org.opencb.opencga.catalog.db.mongodb.MongoDBUtils.getMongoDBDocument;
import static org.opencb.opencga.catalog.db.mongodb.MongoDBUtils.parseObject;
/**
* Created by imedina on 13/01/16.
*/
public class MetaMongoDBAdaptor extends MongoDBAdaptor implements MetaDBAdaptor {
private final MongoDBCollection metaCollection;
private static final String VERSION = "v0.8";
public MetaMongoDBAdaptor(MongoDBCollection metaMongoDBCollection, MongoDBAdaptorFactory dbAdaptorFactory) {
super(LoggerFactory.getLogger(ProjectMongoDBAdaptor.class));
this.dbAdaptorFactory = dbAdaptorFactory;
this.metaCollection = metaMongoDBCollection;
}
public long getNewAutoIncrementId() {
return getNewAutoIncrementId("idCounter"); //, metaCollection
}
public long getNewAutoIncrementId(String field) { //, MongoDBCollection metaCollection
// QueryResult<BasicDBObject> result = metaCollection.findAndModify(
// new BasicDBObject("_id", CatalogMongoDBAdaptor.METADATA_OBJECT_ID), //Query
// new BasicDBObject(field, true), //Fields
// null,
// new BasicDBObject("$inc", new BasicDBObject(field, 1)), //Update
// new QueryOptions("returnNew", true),
// BasicDBObject.class
// );
Bson query = Filters.eq(PRIVATE_ID, MongoDBAdaptorFactory.METADATA_OBJECT_ID);
Document projection = new Document(field, true);
Bson inc = Updates.inc(field, 1L);
QueryOptions queryOptions = new QueryOptions("returnNew", true);
QueryResult<Document> result = metaCollection.findAndUpdate(query, projection, null, inc, queryOptions);
// return (int) Float.parseFloat(result.getResult().get(0).get(field).toString());
return result.getResult().get(0).getLong(field);
}
// public void createCollections() {
// clean(Collections.singletonList(""));
//// metaCollection.createIndexes()
//// dbAdaptorFactory.getCatalogFileDBAdaptor().getFileCollection().createIndexes()
// }
public void createIndexes() {
InputStream resourceAsStream = getClass().getResourceAsStream("/catalog-indexes.txt");
ObjectMapper objectMapper = new ObjectMapper();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resourceAsStream));
// We store all the indexes that are in the file in the indexes object
Map<String, List<Map<String, ObjectMap>>> indexes = new HashMap<>();
bufferedReader.lines().filter(s -> !s.trim().isEmpty()).forEach(s -> {
try {
HashMap hashMap = objectMapper.readValue(s, HashMap.class);
String collection = (String) hashMap.get("collection");
if (!indexes.containsKey(collection)) {
indexes.put(collection, new ArrayList<>());
}
Map<String, ObjectMap> myIndexes = new HashMap<>();
myIndexes.put("fields", new ObjectMap((Map) hashMap.get("fields")));
myIndexes.put("options", new ObjectMap((Map) hashMap.getOrDefault("options", Collections.emptyMap())));
indexes.get(collection).add(myIndexes);
} catch (IOException e) {
e.printStackTrace();
}
});
try {
bufferedReader.close();
} catch (IOException e) {
logger.error("Error closing the buffer reader", e);
throw new UncheckedIOException(e);
}
createIndexes(dbAdaptorFactory.getCatalogUserDBAdaptor().getUserCollection(), indexes.get("user"));
createIndexes(dbAdaptorFactory.getCatalogStudyDBAdaptor().getStudyCollection(), indexes.get("study"));
createIndexes(dbAdaptorFactory.getCatalogSampleDBAdaptor().getSampleCollection(), indexes.get("sample"));
createIndexes(dbAdaptorFactory.getCatalogIndividualDBAdaptor().getIndividualCollection(), indexes.get("individual"));
createIndexes(dbAdaptorFactory.getCatalogFileDBAdaptor().getFileCollection(), indexes.get("file"));
createIndexes(dbAdaptorFactory.getCatalogCohortDBAdaptor().getCohortCollection(), indexes.get("cohort"));
createIndexes(dbAdaptorFactory.getCatalogDatasetDBAdaptor().getDatasetCollection(), indexes.get("dataset"));
// createIndexes(dbAdaptorFactory.getCatalogUserDBAdaptor().getUserCollection(), indexes.get("job"));
}
private void createIndexes(MongoDBCollection mongoCollection, List<Map<String, ObjectMap>> indexes) {
QueryResult<Document> index = mongoCollection.getIndex();
// We store the existent indexes
Set<String> existingIndexes = index.getResult()
.stream()
.map(document -> (String) document.get("name"))
.collect(Collectors.toSet());
if (index.getNumResults() != indexes.size() + 1) { // It is + 1 because mongo always create the _id index by default
for (Map<String, ObjectMap> userIndex : indexes) {
String indexName = "";
Document keys = new Document();
Iterator fieldsIterator = userIndex.get("fields").entrySet().iterator();
while (fieldsIterator.hasNext()) {
Map.Entry pair = (Map.Entry)fieldsIterator.next();
keys.append((String) pair.getKey(), pair.getValue());
if (!indexName.isEmpty()) {
indexName += "_";
}
indexName += pair.getKey() + "_" + pair.getValue();
}
if (!existingIndexes.contains(indexName)) {
mongoCollection.createIndex(keys, new ObjectMap(userIndex.get("options")));
}
}
}
}
public void clean() {
clean(Collections.singletonList(""));
}
public void clean(List<String> collections) {
for (String collection : collections) {
System.out.println(collection);
}
}
public void initializeMetaCollection(Configuration configuration) throws CatalogException {
Admin admin = configuration.getAdmin();
admin.setPassword(CatalogAuthenticationManager.cypherPassword(admin.getPassword()));
Metadata metadata = new Metadata().setIdCounter(configuration.getCatalog().getOffset()).setVersion(VERSION);
if (configuration.isOpenRegister()) {
metadata.setOpen("public");
} else {
metadata.setOpen("private");
}
Document metadataObject = getMongoDBDocument(metadata, "Metadata");
metadataObject.put(PRIVATE_ID, "METADATA");
Document adminDocument = getMongoDBDocument(admin, "Admin");
adminDocument.put("sessions", new ArrayList<>());
metadataObject.put("admin", adminDocument);
// We store the original configuration file
Document config = getMongoDBDocument(configuration, "CatalogConfiguration");
metadataObject.put("config", config);
List<StudyAclEntry> acls = configuration.getAcl();
List<Document> aclList = new ArrayList<>(acls.size());
for (StudyAclEntry acl : acls) {
aclList.add(getMongoDBDocument(acl, "StudyAcl"));
}
metadataObject.put("acl", aclList);
metaCollection.insert(metadataObject, null);
}
public void checkAdmin(String password) throws CatalogException {
Bson query = Filters.eq("admin.password", CatalogAuthenticationManager.cypherPassword(password));
if (metaCollection.count(query).getResult().get(0) == 0) {
throw new CatalogDBException("The admin password is incorrect.");
}
}
@Override
public boolean isRegisterOpen() {
Document doc = metaCollection.find(new Document(PRIVATE_ID, "METADATA"), new QueryOptions(QueryOptions.INCLUDE, "open")).first();
if (doc.getString("open").equals("public")) {
return true;
}
return false;
}
@Override
public QueryResult<Session> addAdminSession(Session session) throws CatalogDBException {
long startTime = startQuery();
Bson query = new Document(PRIVATE_ID, "METADATA");
Bson updates = Updates.push("admin.sessions",
new Document("$each", Arrays.asList(getMongoDBDocument(session, "Session")))
.append("$slice", -5));
QueryResult<UpdateResult> update = metaCollection.update(query, updates, null);
if (update.first().getModifiedCount() == 0) {
throw new CatalogDBException("An internal error occurred when logging the admin");
}
return endQuery("Login", startTime, Collections.singletonList(session));
}
@Override
public String getAdminPassword() throws CatalogDBException {
Bson query = Filters.eq(PRIVATE_ID, "METADATA");
QueryResult<Document> queryResult = metaCollection.find(query, new QueryOptions(QueryOptions.INCLUDE, "admin"));
return parseObject((Document) queryResult.first().get("admin"), Admin.class).getPassword();
}
@Override
public boolean checkValidAdminSession(String id) {
Document query = new Document(PRIVATE_ID, "METADATA").append("admin.sessions.id", id);
return metaCollection.count(query).first() == 1;
}
@Override
public QueryResult<StudyAclEntry> getDaemonAcl(List<String> members) throws CatalogDBException {
long startTime = startQuery();
Bson match = Aggregates.match(Filters.eq(PRIVATE_ID, "METADATA"));
Bson unwind = Aggregates.unwind("$" + CohortDBAdaptor.QueryParams.ACL.key());
Bson match2 = Aggregates.match(Filters.in(CohortDBAdaptor.QueryParams.ACL_MEMBER.key(), members));
Bson project = Aggregates.project(Projections.include(CohortDBAdaptor.QueryParams.ID.key(),
CohortDBAdaptor.QueryParams.ACL.key()));
QueryResult<Document> aggregate = metaCollection.aggregate(Arrays.asList(match, unwind, match2, project), null);
StudyAclEntry result = null;
if (aggregate.getNumResults() == 1) {
result = parseObject(((Document) aggregate.getResult().get(0).get("acl")), StudyAclEntry.class);
}
return endQuery("get daemon Acl", startTime, Arrays.asList(result));
}
}