/*
* 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.storage.mongodb.metadata;
import com.mongodb.DuplicateKeyException;
import com.mongodb.MongoWriteException;
import com.mongodb.ReadPreference;
import com.mongodb.WriteConcern;
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.QueryOptions;
import org.opencb.commons.datastore.core.QueryResult;
import org.opencb.commons.datastore.mongodb.MongoDBCollection;
import org.opencb.commons.datastore.mongodb.MongoDataStore;
import org.opencb.commons.datastore.mongodb.MongoDataStoreManager;
import org.opencb.opencga.storage.core.metadata.StudyConfiguration;
import org.opencb.opencga.storage.core.metadata.StudyConfigurationManager;
import org.opencb.opencga.storage.mongodb.auth.MongoCredentials;
import org.opencb.opencga.storage.mongodb.utils.MongoLock;
import org.opencb.opencga.storage.mongodb.variant.converters.DocumentToStudyConfigurationConverter;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import static com.mongodb.client.model.Updates.set;
import static org.opencb.commons.datastore.mongodb.MongoDBCollection.UPSERT;
/**
* @author Jacobo Coll <jacobo167@gmail.com>
*/
public class MongoDBStudyConfigurationManager extends StudyConfigurationManager {
private final MongoDataStoreManager mongoManager;
private final boolean closeConnection;
private final DocumentToStudyConfigurationConverter studyConfigurationConverter = new DocumentToStudyConfigurationConverter();
private final MongoLock mongoLock;
private final MongoDBCollection collection;
public MongoDBStudyConfigurationManager(MongoCredentials credentials, String collectionName)
throws UnknownHostException {
this(new MongoDataStoreManager(credentials.getDataStoreServerAddresses()), true, credentials, collectionName);
}
public MongoDBStudyConfigurationManager(MongoDataStoreManager mongoManager, MongoCredentials credentials, String collectionName)
throws UnknownHostException {
this(mongoManager, false, credentials, collectionName);
}
private MongoDBStudyConfigurationManager(MongoDataStoreManager mongoManager, boolean closeConnection,
MongoCredentials credentials, String collectionName)
throws UnknownHostException {
super(null);
// Mongo configuration
this.mongoManager = mongoManager;
this.closeConnection = closeConnection;
MongoDataStore db = mongoManager.get(credentials.getMongoDbName(), credentials.getMongoDBConfiguration());
collection = db.getCollection(collectionName)
.withReadPreference(ReadPreference.primary())
.withWriteConcern(WriteConcern.ACKNOWLEDGED);
mongoLock = new MongoLock(collection, "_lock");
}
@Override
protected QueryResult<StudyConfiguration> internalGetStudyConfiguration(String studyName, Long timeStamp, QueryOptions options) {
return internalGetStudyConfiguration(null, studyName, timeStamp, options);
}
@Override
protected QueryResult<StudyConfiguration> internalGetStudyConfiguration(int studyId, Long timeStamp, QueryOptions options) {
return internalGetStudyConfiguration(studyId, null, timeStamp, options);
}
@Override
public long lockStudy(int studyId, long lockDuration, long timeout) throws InterruptedException, TimeoutException {
try {
// Ensure document exists
collection.update(new Document("_id", studyId), set("id", studyId), new QueryOptions(MongoDBCollection.UPSERT, true));
} catch (MongoWriteException e) {
// Duplicated key exception
if (e.getError().getCode() != 11000) {
throw e;
}
} catch (DuplicateKeyException ignore) {
// Ignore this exception.
// With UPSERT=true, this command should never throw DuplicatedKeyException.
// See https://jira.mongodb.org/browse/SERVER-14322
}
return mongoLock.lock(studyId, lockDuration, timeout);
}
@Override
public void unLockStudy(int studyId, long lockId) {
mongoLock.unlock(studyId, lockId);
}
private QueryResult<StudyConfiguration> internalGetStudyConfiguration(Integer studyId, String studyName, Long timeStamp,
QueryOptions options) {
long start = System.currentTimeMillis();
StudyConfiguration studyConfiguration;
Document query = new Document();
if (studyId != null) {
query.append("_id", studyId);
}
if (studyName != null) {
query.append("studyName", studyName);
}
if (timeStamp != null) {
query.append("timeStamp", new Document("$ne", timeStamp));
}
QueryResult<StudyConfiguration> queryResult = collection.find(query, null, studyConfigurationConverter, null);
if (queryResult.getResult().isEmpty()) {
studyConfiguration = null;
} else {
if (queryResult.first().getStudyName() == null) {
// If the studyName is null, it may be only a lock instead of a real study configuration
studyConfiguration = null;
} else {
studyConfiguration = queryResult.first();
}
}
if (studyConfiguration == null) {
return new QueryResult<>(studyName, ((int) (System.currentTimeMillis() - start)), 0, 0, "", "", Collections.emptyList());
} else {
return new QueryResult<>(studyName, ((int) (System.currentTimeMillis() - start)), 1, 1, "", "",
Collections.singletonList(studyConfiguration));
}
}
@Override
public QueryResult internalUpdateStudyConfiguration(StudyConfiguration studyConfiguration, QueryOptions options) {
Document studyMongo = new DocumentToStudyConfigurationConverter().convertToStorageType(studyConfiguration);
// Update field by field, instead of replacing the whole object to preserve existing fields like "_lock"
Document query = new Document("_id", studyConfiguration.getStudyId());
List<Bson> updates = new ArrayList<>(studyMongo.size());
studyMongo.forEach((s, o) -> updates.add(new Document("$set", new Document(s, o))));
QueryResult<UpdateResult> queryResult = collection.update(query, Updates.combine(updates), new QueryOptions(UPSERT, true));
// studyConfigurationMap.put(studyConfiguration.getStudyId(), studyConfiguration);
return queryResult;
}
@Override
public List<String> getStudyNames(QueryOptions options) {
List<String> studyNames = collection.distinct("studyName", null).getResult();
return studyNames.stream().map(Object::toString).collect(Collectors.toList());
}
@Override
public List<Integer> getStudyIds(QueryOptions options) {
return collection.distinct("_id", null, Integer.class).getResult();
}
@Override
public Map<String, Integer> getStudies(QueryOptions options) {
QueryResult<StudyConfiguration> queryResult = collection.find(new Document(), Projections.include("studyId", "studyName"),
studyConfigurationConverter, null);
return queryResult.getResult()
.stream()
.collect(Collectors.toMap(StudyConfiguration::getStudyName, StudyConfiguration::getStudyId));
}
@Override
public void close() {
if (closeConnection) {
mongoManager.close();
}
}
}