/*
* 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.managers;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.opencb.commons.datastore.core.ObjectMap;
import org.opencb.commons.datastore.core.Query;
import org.opencb.commons.datastore.core.QueryOptions;
import org.opencb.commons.datastore.core.QueryResult;
import org.opencb.opencga.catalog.audit.AuditManager;
import org.opencb.opencga.catalog.audit.AuditRecord;
import org.opencb.opencga.catalog.auth.authorization.AuthorizationManager;
import org.opencb.opencga.catalog.config.Configuration;
import org.opencb.opencga.catalog.db.DBAdaptorFactory;
import org.opencb.opencga.catalog.db.api.CohortDBAdaptor;
import org.opencb.opencga.catalog.db.api.FileDBAdaptor;
import org.opencb.opencga.catalog.db.api.SampleDBAdaptor;
import org.opencb.opencga.catalog.exceptions.CatalogAuthorizationException;
import org.opencb.opencga.catalog.exceptions.CatalogDBException;
import org.opencb.opencga.catalog.exceptions.CatalogException;
import org.opencb.opencga.catalog.io.CatalogIOManagerFactory;
import org.opencb.opencga.catalog.managers.api.ISampleManager;
import org.opencb.opencga.catalog.managers.api.IUserManager;
import org.opencb.opencga.catalog.models.*;
import org.opencb.opencga.catalog.models.acls.permissions.SampleAclEntry;
import org.opencb.opencga.catalog.models.acls.permissions.StudyAclEntry;
import org.opencb.opencga.catalog.utils.AnnotationManager;
import org.opencb.opencga.catalog.utils.CatalogAnnotationsValidator;
import org.opencb.opencga.catalog.utils.ParamUtils;
import org.opencb.opencga.core.common.TimeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author Jacobo Coll <jacobo167@gmail.com>
*/
public class SampleManager extends AbstractManager implements ISampleManager {
protected static Logger logger = LoggerFactory.getLogger(SampleManager.class);
private IUserManager userManager;
@Deprecated
public SampleManager(AuthorizationManager authorizationManager, AuditManager auditManager,
DBAdaptorFactory catalogDBAdaptorFactory, CatalogIOManagerFactory ioManagerFactory,
Properties catalogProperties) {
super(authorizationManager, auditManager, catalogDBAdaptorFactory, ioManagerFactory, catalogProperties);
}
public SampleManager(AuthorizationManager authorizationManager, AuditManager auditManager, CatalogManager catalogManager,
DBAdaptorFactory catalogDBAdaptorFactory, CatalogIOManagerFactory ioManagerFactory,
Configuration configuration) {
super(authorizationManager, auditManager, catalogManager, catalogDBAdaptorFactory, ioManagerFactory,
configuration);
this.userManager = catalogManager.getUserManager();
}
@Override
public Long getStudyId(long sampleId) throws CatalogException {
return sampleDBAdaptor.getStudyId(sampleId);
}
@Override
public Long getId(String userId, String sampleStr) throws CatalogException {
if (StringUtils.isNumeric(sampleStr)) {
return Long.parseLong(sampleStr);
}
ObjectMap parsedSampleStr = parseFeatureId(userId, sampleStr);
List<Long> studyIds = getStudyIds(parsedSampleStr);
String sampleName = parsedSampleStr.getString("featureName");
Query query = new Query()
.append(SampleDBAdaptor.QueryParams.STUDY_ID.key(), studyIds)
.append(SampleDBAdaptor.QueryParams.NAME.key(), sampleName)
.append(SampleDBAdaptor.QueryParams.STATUS_NAME.key(), "!=" + Status.DELETED);
QueryOptions qOptions = new QueryOptions(QueryOptions.INCLUDE, "projects.studies.samples.id");
QueryResult<Sample> queryResult = sampleDBAdaptor.get(query, qOptions);
if (queryResult.getNumResults() > 1) {
throw new CatalogException("Error: More than one sample id found based on " + sampleName);
} else if (queryResult.getNumResults() == 0) {
return -1L;
} else {
return queryResult.first().getId();
}
}
@Deprecated
@Override
public Long getId(String id) throws CatalogException {
if (StringUtils.isNumeric(id)) {
return Long.parseLong(id);
}
Query query = new Query(SampleDBAdaptor.QueryParams.NAME.key(), id);
QueryOptions options = new QueryOptions(QueryOptions.INCLUDE, SampleDBAdaptor.QueryParams.ID.key());
QueryResult<Sample> sampleQueryResult = sampleDBAdaptor.get(query, options);
if (sampleQueryResult.getNumResults() == 1) {
return sampleQueryResult.first().getId();
} else {
return -1L;
}
}
@Override
public QueryResult<Sample> create(String studyStr, String name, String source, String description, boolean somatic,
Individual individual, Map<String, Object> attributes, QueryOptions options, String sessionId)
throws CatalogException {
ParamUtils.checkAlias(name, "name", configuration.getCatalog().getOffset());
source = ParamUtils.defaultString(source, "");
description = ParamUtils.defaultString(description, "");
attributes = ParamUtils.defaultObject(attributes, Collections.<String, Object>emptyMap());
String userId = userManager.getId(sessionId);
long studyId = catalogManager.getStudyManager().getId(userId, studyStr);
authorizationManager.checkStudyPermission(studyId, userId, StudyAclEntry.StudyPermissions.WRITE_SAMPLES);
if (individual != null) {
if (individual.getId() <= 0) {
individual.setId(catalogManager.getIndividualManager().getId(individual.getName(), Long.toString(studyId), sessionId)
.getResourceId());
}
}
Sample sample = new Sample(-1, name, source, individual, description, somatic, Collections.emptyList(), Collections.emptyList(),
attributes);
options = ParamUtils.defaultObject(options, QueryOptions::new);
QueryResult<Sample> queryResult = sampleDBAdaptor.insert(sample, studyId, options);
// auditManager.recordCreation(AuditRecord.Resource.sample, queryResult.first().getId(), userId, queryResult.first(), null, null);
auditManager.recordAction(AuditRecord.Resource.sample, AuditRecord.Action.create, AuditRecord.Magnitude.low,
queryResult.first().getId(), userId, null, queryResult.first(), null, null);
return queryResult;
}
@Override
@Deprecated
public QueryResult<AnnotationSet> annotate(long sampleId, String annotationSetName, long variableSetId, Map<String, Object> annotations,
Map<String, Object> attributes, boolean checkAnnotationSet,
String sessionId) throws CatalogException {
ParamUtils.checkParameter(annotationSetName, "annotationSetName");
ParamUtils.checkObj(annotations, "annotations");
attributes = ParamUtils.defaultObject(attributes, HashMap<String, Object>::new);
String userId = userManager.getId(sessionId);
authorizationManager.checkSamplePermission(sampleId, userId, SampleAclEntry.SamplePermissions.WRITE_ANNOTATIONS);
VariableSet variableSet = studyDBAdaptor.getVariableSet(variableSetId, null).first();
AnnotationSet annotationSet = new AnnotationSet(annotationSetName, variableSetId, new HashSet<>(), TimeUtils.getTime(), attributes);
for (Map.Entry<String, Object> entry : annotations.entrySet()) {
annotationSet.getAnnotations().add(new Annotation(entry.getKey(), entry.getValue()));
}
QueryResult<Sample> sampleQueryResult = sampleDBAdaptor.get(sampleId,
new QueryOptions("include", Collections.singletonList("projects.studies.samples.annotationSets")));
List<AnnotationSet> annotationSets = sampleQueryResult.first().getAnnotationSets();
if (checkAnnotationSet) {
CatalogAnnotationsValidator.checkAnnotationSet(variableSet, annotationSet, annotationSets);
}
QueryResult<AnnotationSet> queryResult = sampleDBAdaptor.annotate(sampleId, annotationSet, false);
auditManager.recordUpdate(AuditRecord.Resource.sample, sampleId, userId,
new ObjectMap("annotationSets", queryResult.first()), "annotate", null);
return queryResult;
}
@Override
public QueryResult<AnnotationSet> updateAnnotation(long sampleId, String annotationSetName, Map<String, Object> newAnnotations, String
sessionId)
throws CatalogException {
ParamUtils.checkParameter(annotationSetName, "annotationSetName");
ParamUtils.checkObj(newAnnotations, "newAnnotations");
String userId = userManager.getId(sessionId);
authorizationManager.checkSamplePermission(sampleId, userId, SampleAclEntry.SamplePermissions.WRITE_ANNOTATIONS);
// Get sample
QueryOptions queryOptions = new QueryOptions("include", Collections.singletonList("projects.studies.samples.annotationSets"));
Sample sample = sampleDBAdaptor.get(sampleId, queryOptions).first();
List<AnnotationSet> annotationSets = sample.getAnnotationSets();
// Get annotation set
AnnotationSet annotationSet = null;
for (AnnotationSet annotationSetAux : sample.getAnnotationSets()) {
if (annotationSetAux.getName().equals(annotationSetName)) {
annotationSet = annotationSetAux;
sample.getAnnotationSets().remove(annotationSet);
break;
}
}
if (annotationSet == null) {
throw CatalogDBException.idNotFound("AnnotationSet", annotationSetName);
}
// Get variable set
VariableSet variableSet = studyDBAdaptor.getVariableSet(annotationSet.getVariableSetId(), null).first();
// Update and validate annotations
CatalogAnnotationsValidator.mergeNewAnnotations(annotationSet, newAnnotations);
CatalogAnnotationsValidator.checkAnnotationSet(variableSet, annotationSet, annotationSets);
// Commit changes
QueryResult<AnnotationSet> queryResult = sampleDBAdaptor.annotate(sampleId, annotationSet, true);
AnnotationSet annotationSetUpdate = new AnnotationSet(annotationSet.getName(), annotationSet.getVariableSetId(),
newAnnotations.entrySet().stream()
.map(entry -> new Annotation(entry.getKey(), entry.getValue()))
.collect(Collectors.toSet()), annotationSet.getCreationDate(), null);
auditManager.recordUpdate(AuditRecord.Resource.sample, sampleId, userId, new ObjectMap("annotationSets",
Collections.singletonList(annotationSetUpdate)), "update annotation", null);
return queryResult;
}
@Override
@Deprecated
public QueryResult<AnnotationSet> deleteAnnotation(long sampleId, String annotationId, String sessionId) throws CatalogException {
String userId = userManager.getId(sessionId);
authorizationManager.checkSamplePermission(sampleId, userId, SampleAclEntry.SamplePermissions.DELETE_ANNOTATIONS);
QueryResult<AnnotationSet> queryResult = sampleDBAdaptor.deleteAnnotation(sampleId, annotationId);
auditManager.recordUpdate(AuditRecord.Resource.sample, sampleId, userId, new ObjectMap("annotationSets", queryResult.first()),
"deleteAnnotation", null);
return queryResult;
}
@Override
public QueryResult<Annotation> load(File file) throws CatalogException {
throw new UnsupportedOperationException();
}
@Deprecated
@Override
public QueryResult<Sample> create(String studyStr, String name, String source, String description,
Map<String, Object> attributes, QueryOptions options, String sessionId)
throws CatalogException {
return create(studyStr, name, source, description, false, null, attributes, options, sessionId);
}
@Override
public MyResourceId getId(String sampleStr, @Nullable String studyStr, String sessionId) throws CatalogException {
if (StringUtils.isEmpty(sampleStr)) {
throw new CatalogException("Missing sample parameter");
}
String userId;
long studyId;
long sampleId;
if (StringUtils.isNumeric(sampleStr) && Long.parseLong(sampleStr) > configuration.getCatalog().getOffset()) {
sampleId = Long.parseLong(sampleStr);
sampleDBAdaptor.exists(sampleId);
studyId = sampleDBAdaptor.getStudyId(sampleId);
userId = userManager.getId(sessionId);
} else {
if (sampleStr.contains(",")) {
throw new CatalogException("More than one sample found");
}
userId = userManager.getId(sessionId);
studyId = catalogManager.getStudyManager().getId(userId, studyStr);
Query query = new Query()
.append(SampleDBAdaptor.QueryParams.STUDY_ID.key(), studyId)
.append(SampleDBAdaptor.QueryParams.NAME.key(), sampleStr);
QueryOptions queryOptions = new QueryOptions(QueryOptions.INCLUDE, SampleDBAdaptor.QueryParams.ID.key());
QueryResult<Sample> sampleQueryResult = sampleDBAdaptor.get(query, queryOptions);
if (sampleQueryResult.getNumResults() == 1) {
sampleId = sampleQueryResult.first().getId();
} else {
if (sampleQueryResult.getNumResults() == 0) {
throw new CatalogException("Sample " + sampleStr + " not found in study " + studyStr);
} else {
throw new CatalogException("More than one sample found under " + sampleStr + " in study " + studyStr);
}
}
}
return new MyResourceId(userId, studyId, sampleId);
}
@Override
public MyResourceIds getIds(String sampleStr, @Nullable String studyStr, String sessionId) throws CatalogException {
if (StringUtils.isEmpty(sampleStr)) {
throw new CatalogException("Missing sample parameter");
}
String userId;
long studyId;
List<Long> sampleIds = new ArrayList<>();
if (StringUtils.isNumeric(sampleStr) && Long.parseLong(sampleStr) > configuration.getCatalog().getOffset()) {
sampleIds = Arrays.asList(Long.parseLong(sampleStr));
sampleDBAdaptor.exists(sampleIds.get(0));
studyId = sampleDBAdaptor.getStudyId(sampleIds.get(0));
userId = userManager.getId(sessionId);
} else {
userId = userManager.getId(sessionId);
studyId = catalogManager.getStudyManager().getId(userId, studyStr);
List<String> sampleSplit = Arrays.asList(sampleStr.split(","));
for (String sampleStrAux : sampleSplit) {
if (StringUtils.isNumeric(sampleStrAux)) {
long sampleId = Long.parseLong(sampleStrAux);
sampleDBAdaptor.exists(sampleId);
sampleIds.add(sampleId);
}
}
Query query = new Query()
.append(SampleDBAdaptor.QueryParams.STUDY_ID.key(), studyId)
.append(SampleDBAdaptor.QueryParams.NAME.key(), sampleSplit);
QueryOptions queryOptions = new QueryOptions(QueryOptions.INCLUDE, SampleDBAdaptor.QueryParams.ID.key());
QueryResult<Sample> sampleQueryResult = sampleDBAdaptor.get(query, queryOptions);
if (sampleQueryResult.getNumResults() > 0) {
sampleIds.addAll(sampleQueryResult.getResult().stream().map(Sample::getId).collect(Collectors.toList()));
}
if (sampleIds.size() < sampleSplit.size()) {
throw new CatalogException("Found only " + sampleIds.size() + " out of the " + sampleSplit.size()
+ " samples looked for in study " + studyStr);
}
}
return new MyResourceIds(userId, studyId, sampleIds);
}
@Override
public QueryResult<Sample> get(Long sampleId, QueryOptions options, String sessionId) throws CatalogException {
String userId = userManager.getId(sessionId);
authorizationManager.checkSamplePermission(sampleId, userId, SampleAclEntry.SamplePermissions.VIEW);
QueryResult<Sample> sampleQueryResult = sampleDBAdaptor.get(sampleId, options);
authorizationManager.filterSamples(userId, getStudyId(sampleId), sampleQueryResult.getResult());
sampleQueryResult.setNumResults(sampleQueryResult.getResult().size());
return sampleQueryResult;
}
@Override
public QueryResult<Sample> get(long studyId, Query query, QueryOptions options, String sessionId) throws CatalogException {
query = ParamUtils.defaultObject(query, Query::new);
options = ParamUtils.defaultObject(options, QueryOptions::new);
String userId = userManager.getId(sessionId);
if (!authorizationManager.memberHasPermissionsInStudy(studyId, userId)) {
throw CatalogAuthorizationException.deny(userId, "view", "samples", studyId, null);
}
query.append(SampleDBAdaptor.QueryParams.STUDY_ID.key(), studyId);
QueryResult<Sample> queryResult = sampleDBAdaptor.get(query, options);
authorizationManager.filterSamples(userId, studyId, queryResult.getResult());
queryResult.setNumResults(queryResult.getResult().size());
return queryResult;
}
@Override
public QueryResult<Sample> search(String studyStr, Query query, QueryOptions options, String sessionId) throws CatalogException {
String userId = userManager.getId(sessionId);
List<Long> studyIds = catalogManager.getStudyManager().getIds(userId, studyStr);
// Check any permission in studies
for (Long studyId : studyIds) {
authorizationManager.memberHasPermissionsInStudy(studyId, userId);
}
// The individuals introduced could be either ids or names. As so, we should use the smart resolutor to do this.
// FIXME: Although the search method is multi-study, we can only use the smart resolutor for one study at the moment.
if (StringUtils.isNotEmpty(query.getString(SampleDBAdaptor.QueryParams.INDIVIDUAL_ID.key()))
&& studyIds.size() == 1) {
MyResourceIds resourceIds = catalogManager.getIndividualManager().getIds(
query.getString(SampleDBAdaptor.QueryParams.INDIVIDUAL_ID.key()), Long.toString(studyIds.get(0)), sessionId);
query.put(SampleDBAdaptor.QueryParams.INDIVIDUAL_ID.key(), resourceIds.getResourceIds());
}
QueryResult<Sample> queryResult = null;
for (Long studyId : studyIds) {
query.append(SampleDBAdaptor.QueryParams.STUDY_ID.key(), studyId);
QueryResult<Sample> queryResultAux = sampleDBAdaptor.get(query, options);
authorizationManager.filterSamples(userId, studyId, queryResultAux.getResult());
if (queryResult == null) {
queryResult = queryResultAux;
} else {
queryResult.getResult().addAll(queryResultAux.getResult());
queryResult.setNumTotalResults(queryResult.getNumTotalResults() + queryResultAux.getNumTotalResults());
queryResult.setDbTime(queryResult.getDbTime() + queryResultAux.getDbTime());
}
}
queryResult.setNumResults(queryResult.getResult().size());
return queryResult;
}
// TODO
// This implementation should be changed and made better. Check the comment in IndividualManager -> delete(). Those changes
// will probably make the delete from individualManager to be changed.
@Override
public List<QueryResult<Sample>> delete(String sampleIdStr, @Nullable String studyStr, QueryOptions options, String sessionId)
throws CatalogException, IOException {
ParamUtils.checkParameter(sampleIdStr, "id");
// options = ParamUtils.defaultObject(options, QueryOptions::new);
MyResourceIds resourceId = getIds(sampleIdStr, studyStr, sessionId);
List<QueryResult<Sample>> queryResultList = new ArrayList<>(resourceId.getResourceIds().size());
for (Long sampleId : resourceId.getResourceIds()) {
QueryResult<Sample> queryResult = null;
try {
MyResourceIds myResourceId = new MyResourceIds(resourceId.getUser(), resourceId.getStudyId(), Arrays.asList(sampleId));
checkCanDeleteSamples(myResourceId);
// Get the sample info before the update
QueryResult<Sample> sampleQueryResult = sampleDBAdaptor.get(sampleId, QueryOptions.empty());
String newSampleName = sampleQueryResult.first().getName() + ".DELETED_" + TimeUtils.getTime();
ObjectMap updateParams = new ObjectMap()
.append(SampleDBAdaptor.QueryParams.NAME.key(), newSampleName)
.append(SampleDBAdaptor.QueryParams.STATUS_NAME.key(), Status.DELETED);
queryResult = sampleDBAdaptor.update(sampleId, updateParams);
auditManager.recordAction(AuditRecord.Resource.sample, AuditRecord.Action.delete, AuditRecord.Magnitude.high, sampleId,
resourceId.getUser(), sampleQueryResult.first(), queryResult.first(), "", null);
// Remove the references to the sample id from the array of files
Query query = new Query()
.append(FileDBAdaptor.QueryParams.STUDY_ID.key(), resourceId.getStudyId());
fileDBAdaptor.extractSampleFromFiles(query, Arrays.asList(sampleId));
} catch (CatalogAuthorizationException e) {
auditManager.recordAction(AuditRecord.Resource.sample, AuditRecord.Action.delete, AuditRecord.Magnitude.high, sampleId,
resourceId.getUser(), null, null, e.getMessage(), null);
queryResult = new QueryResult<>("Delete sample " + sampleId);
queryResult.setErrorMsg(e.getMessage());
} catch (CatalogException e) {
e.printStackTrace();
queryResult = new QueryResult<>("Delete sample " + sampleId);
queryResult.setErrorMsg(e.getMessage());
} finally {
queryResultList.add(queryResult);
}
}
return queryResultList;
}
@Override
public void checkCanDeleteSamples(MyResourceIds resources) throws CatalogException {
for (Long sampleId : resources.getResourceIds()) {
authorizationManager.checkSamplePermission(sampleId, resources.getUser(), SampleAclEntry.SamplePermissions.DELETE);
}
// Check that the samples are not being used in cohorts
Query query = new Query()
.append(CohortDBAdaptor.QueryParams.STUDY_ID.key(), resources.getStudyId())
.append(CohortDBAdaptor.QueryParams.SAMPLES.key(), resources.getResourceIds());
long count = cohortDBAdaptor.count(query).first();
if (count > 0) {
if (resources.getResourceIds().size() == 1) {
throw new CatalogException("The sample " + resources.getResourceIds().get(0) + " is part of " + count + " cohorts. Please, "
+ "first update or delete the cohorts");
} else {
throw new CatalogException("Some samples are part of " + count + " cohorts. Please, first update or delete the cohorts");
}
}
}
@Override
public List<QueryResult<Sample>> delete(Query query, QueryOptions options, String sessionId) throws CatalogException, IOException {
QueryOptions queryOptions = new QueryOptions(QueryOptions.INCLUDE, SampleDBAdaptor.QueryParams.ID.key());
QueryResult<Sample> sampleQueryResult = sampleDBAdaptor.get(query, queryOptions);
List<Long> sampleIds = sampleQueryResult.getResult().stream().map(Sample::getId).collect(Collectors.toList());
String sampleIdStr = StringUtils.join(sampleIds, ",");
return delete(sampleIdStr, null, options, sessionId);
}
@Override
public List<QueryResult<Sample>> restore(String sampleIdStr, QueryOptions options, String sessionId) throws CatalogException {
ParamUtils.checkParameter(sampleIdStr, "id");
options = ParamUtils.defaultObject(options, QueryOptions::new);
String userId = userManager.getId(sessionId);
List<Long> sampleIds = getIds(userId, sampleIdStr);
List<QueryResult<Sample>> queryResultList = new ArrayList<>(sampleIds.size());
for (Long sampleId : sampleIds) {
QueryResult<Sample> queryResult = null;
try {
authorizationManager.checkSamplePermission(sampleId, userId, SampleAclEntry.SamplePermissions.DELETE);
queryResult = sampleDBAdaptor.restore(sampleId, options);
auditManager.recordAction(AuditRecord.Resource.sample, AuditRecord.Action.restore, AuditRecord.Magnitude.medium, sampleId,
userId, Status.DELETED, Status.READY, "Sample restore", new ObjectMap());
} catch (CatalogAuthorizationException e) {
auditManager.recordAction(AuditRecord.Resource.sample, AuditRecord.Action.restore, AuditRecord.Magnitude.high, sampleId,
userId, null, null, e.getMessage(), null);
queryResult = new QueryResult<>("Restore sample " + sampleId);
queryResult.setErrorMsg(e.getMessage());
} catch (CatalogException e) {
e.printStackTrace();
queryResult = new QueryResult<>("Restore sample " + sampleId);
queryResult.setErrorMsg(e.getMessage());
} finally {
queryResultList.add(queryResult);
}
}
return queryResultList;
}
@Override
public List<QueryResult<Sample>> restore(Query query, QueryOptions options, String sessionId) throws CatalogException {
QueryOptions queryOptions = new QueryOptions(QueryOptions.INCLUDE, SampleDBAdaptor.QueryParams.ID.key());
QueryResult<Sample> sampleQueryResult = sampleDBAdaptor.get(query, queryOptions);
List<Long> sampleIds = sampleQueryResult.getResult().stream().map(Sample::getId).collect(Collectors.toList());
String sampleIdStr = StringUtils.join(sampleIds, ",");
return restore(sampleIdStr, options, sessionId);
}
@Override
public void setStatus(String id, String status, String message, String sessionId) throws CatalogException {
throw new NotImplementedException("Project: Operation not yet supported");
}
@Override
public QueryResult<SampleAclEntry> getAcls(String sampleStr, List<String> members, String sessionId) throws CatalogException {
long startTime = System.currentTimeMillis();
String userId = userManager.getId(sessionId);
Long sampleId = getId(userId, sampleStr);
authorizationManager.checkSamplePermission(sampleId, userId, SampleAclEntry.SamplePermissions.SHARE);
Long studyId = getStudyId(sampleId);
// Split and obtain the set of members (users + groups), users and groups
Set<String> memberSet = new HashSet<>();
Set<String> userIds = new HashSet<>();
Set<String> groupIds = new HashSet<>();
for (String member: members) {
memberSet.add(member);
if (!member.startsWith("@")) {
userIds.add(member);
} else {
groupIds.add(member);
}
}
// Obtain the groups the user might belong to in order to be able to get the permissions properly
// (the permissions might be given to the group instead of the user)
// Map of group -> users
Map<String, List<String>> groupUsers = new HashMap<>();
if (userIds.size() > 0) {
List<String> tmpUserIds = userIds.stream().collect(Collectors.toList());
QueryResult<Group> groups = studyDBAdaptor.getGroup(studyId, null, tmpUserIds);
// We add the groups where the users might belong to to the memberSet
if (groups.getNumResults() > 0) {
for (Group group : groups.getResult()) {
for (String tmpUserId : group.getUserIds()) {
if (userIds.contains(tmpUserId)) {
memberSet.add(group.getName());
if (!groupUsers.containsKey(group.getName())) {
groupUsers.put(group.getName(), new ArrayList<>());
}
groupUsers.get(group.getName()).add(tmpUserId);
}
}
}
}
}
List<String> memberList = memberSet.stream().collect(Collectors.toList());
QueryResult<SampleAclEntry> sampleAclQueryResult = sampleDBAdaptor.getAcl(sampleId, memberList);
if (members.size() == 0) {
return sampleAclQueryResult;
}
// For the cases where the permissions were given at group level, we obtain the user and return it as if they were given to the user
// instead of the group.
// We loop over the results and recreate one sampleAcl per member
Map<String, SampleAclEntry> sampleAclHashMap = new HashMap<>();
for (SampleAclEntry sampleAcl : sampleAclQueryResult.getResult()) {
if (memberList.contains(sampleAcl.getMember())) {
if (sampleAcl.getMember().startsWith("@")) {
// Check if the user was demanding the group directly or a user belonging to the group
if (groupIds.contains(sampleAcl.getMember())) {
sampleAclHashMap.put(sampleAcl.getMember(), new SampleAclEntry(sampleAcl.getMember(), sampleAcl.getPermissions()));
} else {
// Obtain the user(s) belonging to that group whose permissions wanted the userId
if (groupUsers.containsKey(sampleAcl.getMember())) {
for (String tmpUserId : groupUsers.get(sampleAcl.getMember())) {
if (userIds.contains(tmpUserId)) {
sampleAclHashMap.put(tmpUserId, new SampleAclEntry(tmpUserId, sampleAcl.getPermissions()));
}
}
}
}
} else {
// Add the user
sampleAclHashMap.put(sampleAcl.getMember(), new SampleAclEntry(sampleAcl.getMember(), sampleAcl.getPermissions()));
}
}
}
// We recreate the output that is in sampleAclHashMap but in the same order the members were queried.
List<SampleAclEntry> sampleAclList = new ArrayList<>(sampleAclHashMap.size());
for (String member : members) {
if (sampleAclHashMap.containsKey(member)) {
sampleAclList.add(sampleAclHashMap.get(member));
}
}
// Update queryResult info
sampleAclQueryResult.setId(sampleStr);
sampleAclQueryResult.setNumResults(sampleAclList.size());
sampleAclQueryResult.setDbTime((int) (System.currentTimeMillis() - startTime));
sampleAclQueryResult.setResult(sampleAclList);
return sampleAclQueryResult;
}
@Override
public QueryResult<Sample> get(Query query, QueryOptions options, String sessionId) throws CatalogException {
ParamUtils.checkObj(query, "query");
QueryResult<Sample> result =
get(query.getInt(SampleDBAdaptor.QueryParams.STUDY_ID.key(), -1), query, options, sessionId);
// auditManager.recordRead(AuditRecord.Resource.sample, , userId, parameters, null, null);
return result;
}
@Override
public QueryResult<Sample> update(Long sampleId, ObjectMap parameters, QueryOptions options, String sessionId) throws
CatalogException {
ParamUtils.checkObj(parameters, "parameters");
// options = ParamUtils.defaultObject(options, QueryOptions::new);
String userId = userManager.getId(sessionId);
authorizationManager.checkSamplePermission(sampleId, userId, SampleAclEntry.SamplePermissions.UPDATE);
for (Map.Entry<String, Object> param : parameters.entrySet()) {
SampleDBAdaptor.QueryParams queryParam = SampleDBAdaptor.QueryParams.getParam(param.getKey());
switch (queryParam) {
case SOURCE:
case NAME:
ParamUtils.checkAlias(parameters.getString(queryParam.key()), "name", configuration.getCatalog().getOffset());
break;
case INDIVIDUAL_ID:
case SOMATIC:
case DESCRIPTION:
case ATTRIBUTES:
break;
default:
throw new CatalogException("Cannot update " + queryParam);
}
}
if (StringUtils.isNotEmpty(parameters.getString(SampleDBAdaptor.QueryParams.INDIVIDUAL_ID.key()))) {
String individualStr = parameters.getString(SampleDBAdaptor.QueryParams.INDIVIDUAL_ID.key());
if (NumberUtils.isCreatable(individualStr) && Long.parseLong(individualStr) <= 0) {
parameters.put(SampleDBAdaptor.QueryParams.INDIVIDUAL_ID.key(), -1);
} else {
long studyId = sampleDBAdaptor.getStudyId(sampleId);
MyResourceId resource = catalogManager.getIndividualManager().getId(individualStr, Long.toString(studyId), sessionId);
parameters.put(SampleDBAdaptor.QueryParams.INDIVIDUAL_ID.key(), resource.getResourceId());
}
}
QueryResult<Sample> queryResult = sampleDBAdaptor.update(sampleId, parameters);
auditManager.recordUpdate(AuditRecord.Resource.sample, sampleId, userId, parameters, null, null);
return queryResult;
}
@Override
public QueryResult rank(long studyId, Query query, String field, int numResults, boolean asc, String sessionId)
throws CatalogException {
query = ParamUtils.defaultObject(query, Query::new);
ParamUtils.checkObj(field, "field");
ParamUtils.checkObj(studyId, "studyId");
ParamUtils.checkObj(sessionId, "sessionId");
String userId = userManager.getId(sessionId);
authorizationManager.checkStudyPermission(studyId, userId, StudyAclEntry.StudyPermissions.VIEW_SAMPLES);
// TODO: In next release, we will have to check the count parameter from the queryOptions object.
boolean count = true;
//query.append(CatalogSampleDBAdaptor.QueryParams.STUDY_ID.key(), studyId);
QueryResult queryResult = null;
if (count) {
// We do not need to check for permissions when we show the count of files
queryResult = sampleDBAdaptor.rank(query, field, numResults, asc);
}
return ParamUtils.defaultObject(queryResult, QueryResult::new);
}
@Override
public QueryResult groupBy(@Nullable String studyStr, Query query, List<String> fields, QueryOptions options, String sessionId)
throws CatalogException {
query = ParamUtils.defaultObject(query, Query::new);
options = ParamUtils.defaultObject(options, QueryOptions::new);
if (fields == null || fields.size() == 0) {
throw new CatalogException("Empty fields parameter.");
}
String userId = userManager.getId(sessionId);
long studyId = catalogManager.getStudyManager().getId(userId, studyStr);
authorizationManager.checkStudyPermission(studyId, userId, StudyAclEntry.StudyPermissions.VIEW_SAMPLES);
// Add study id to the query
query.put(SampleDBAdaptor.QueryParams.STUDY_ID.key(), studyId);
// TODO: In next release, we will have to check the count parameter from the queryOptions object.
boolean count = true;
QueryResult queryResult = null;
if (count) {
// We do not need to check for permissions when we show the count of files
queryResult = sampleDBAdaptor.groupBy(query, fields, options);
}
return ParamUtils.defaultObject(queryResult, QueryResult::new);
}
private long commonGetAllAnnotationSets(String id, @Nullable String studyStr, String sessionId) throws CatalogException {
ParamUtils.checkParameter(id, "id");
MyResourceId resourceId = catalogManager.getSampleManager().getId(id, studyStr, sessionId);
authorizationManager.checkSamplePermission(resourceId.getResourceId(), resourceId.getUser(),
SampleAclEntry.SamplePermissions.VIEW_ANNOTATIONS);
return resourceId.getResourceId();
}
private long commonGetAnnotationSet(String id, @Nullable String studyStr, String annotationSetName, String sessionId)
throws CatalogException {
ParamUtils.checkParameter(id, "id");
ParamUtils.checkAlias(annotationSetName, "annotationSetName", configuration.getCatalog().getOffset());
MyResourceId resourceId = catalogManager.getSampleManager().getId(id, studyStr, sessionId);
authorizationManager.checkSamplePermission(resourceId.getResourceId(), resourceId.getUser(),
SampleAclEntry.SamplePermissions.VIEW_ANNOTATIONS);
return resourceId.getResourceId();
}
@Override
public QueryResult<AnnotationSet> createAnnotationSet(String id, @Nullable String studyStr, long variableSetId,
String annotationSetName, Map<String, Object> annotations,
Map<String, Object> attributes, String sessionId) throws CatalogException {
ParamUtils.checkParameter(annotationSetName, "annotationSetName");
ParamUtils.checkObj(annotations, "annotations");
attributes = ParamUtils.defaultObject(attributes, HashMap<String, Object>::new);
MyResourceId resourceId = catalogManager.getSampleManager().getId(id, studyStr, sessionId);
authorizationManager.checkSamplePermission(resourceId.getResourceId(), resourceId.getUser(),
SampleAclEntry.SamplePermissions.WRITE_ANNOTATIONS);
VariableSet variableSet = studyDBAdaptor.getVariableSet(variableSetId, null).first();
QueryResult<AnnotationSet> annotationSet = AnnotationManager.createAnnotationSet(resourceId.getResourceId(), variableSet,
annotationSetName, annotations, attributes, sampleDBAdaptor);
auditManager.recordUpdate(AuditRecord.Resource.sample, resourceId.getResourceId(), resourceId.getUser(),
new ObjectMap("annotationSets", annotationSet.first()), "annotate", null);
return annotationSet;
}
@Override
public QueryResult<AnnotationSet> getAllAnnotationSets(String id, @Nullable String studyStr, String sessionId) throws CatalogException {
long sampleId = commonGetAllAnnotationSets(id, studyStr, sessionId);
return sampleDBAdaptor.getAnnotationSet(sampleId, null);
}
@Override
public QueryResult<ObjectMap> getAllAnnotationSetsAsMap(String id, @Nullable String studyStr, String sessionId)
throws CatalogException {
long sampleId = commonGetAllAnnotationSets(id, studyStr, sessionId);
return sampleDBAdaptor.getAnnotationSetAsMap(sampleId, null);
}
@Override
public QueryResult<AnnotationSet> getAnnotationSet(String id, @Nullable String studyStr, String annotationSetName, String sessionId)
throws CatalogException {
long sampleId = commonGetAnnotationSet(id, studyStr, annotationSetName, sessionId);
return sampleDBAdaptor.getAnnotationSet(sampleId, annotationSetName);
}
@Override
public QueryResult<ObjectMap> getAnnotationSetAsMap(String id, @Nullable String studyStr, String annotationSetName, String sessionId)
throws CatalogException {
long sampleId = commonGetAnnotationSet(id, studyStr, annotationSetName, sessionId);
return sampleDBAdaptor.getAnnotationSetAsMap(sampleId, annotationSetName);
}
@Override
public QueryResult<AnnotationSet> updateAnnotationSet(String id, @Nullable String studyStr, String annotationSetName,
Map<String, Object> newAnnotations, String sessionId) throws CatalogException {
ParamUtils.checkParameter(id, "id");
ParamUtils.checkParameter(annotationSetName, "annotationSetName");
ParamUtils.checkObj(newAnnotations, "newAnnotations");
MyResourceId resourceId = catalogManager.getSampleManager().getId(id, studyStr, sessionId);
authorizationManager.checkSamplePermission(resourceId.getResourceId(), resourceId.getUser(),
SampleAclEntry.SamplePermissions.WRITE_ANNOTATIONS);
// Update the annotation
QueryResult<AnnotationSet> queryResult =
AnnotationManager.updateAnnotationSet(resourceId.getResourceId(), annotationSetName, newAnnotations, sampleDBAdaptor,
studyDBAdaptor);
if (queryResult == null || queryResult.getNumResults() == 0) {
throw new CatalogException("There was an error with the update");
}
AnnotationSet annotationSet = queryResult.first();
// Audit the changes
AnnotationSet annotationSetUpdate = new AnnotationSet(annotationSet.getName(), annotationSet.getVariableSetId(),
newAnnotations.entrySet().stream()
.map(entry -> new Annotation(entry.getKey(), entry.getValue()))
.collect(Collectors.toSet()), annotationSet.getCreationDate(), null);
auditManager.recordUpdate(AuditRecord.Resource.sample, resourceId.getResourceId(), resourceId.getUser(),
new ObjectMap("annotationSets", Collections.singletonList(annotationSetUpdate)), "update annotation", null);
return queryResult;
}
@Override
public QueryResult<AnnotationSet> deleteAnnotationSet(String id, @Nullable String studyStr, String annotationSetName, String
sessionId) throws CatalogException {
ParamUtils.checkParameter(id, "id");
ParamUtils.checkParameter(annotationSetName, "annotationSetName");
MyResourceId resourceId = catalogManager.getSampleManager().getId(id, studyStr, sessionId);
authorizationManager.checkSamplePermission(resourceId.getResourceId(), resourceId.getUser(),
SampleAclEntry.SamplePermissions.DELETE_ANNOTATIONS);
QueryResult<AnnotationSet> annotationSet = sampleDBAdaptor.getAnnotationSet(resourceId.getResourceId(), annotationSetName);
if (annotationSet == null || annotationSet.getNumResults() == 0) {
throw new CatalogException("Could not delete annotation set. The annotation set with name " + annotationSetName + " could not "
+ "be found in the database.");
}
sampleDBAdaptor.deleteAnnotationSet(resourceId.getResourceId(), annotationSetName);
auditManager.recordDeletion(AuditRecord.Resource.sample, resourceId.getResourceId(), resourceId.getUser(),
new ObjectMap("annotationSets", Collections.singletonList(annotationSet.first())), "delete annotation", null);
return annotationSet;
}
@Override
public QueryResult<ObjectMap> searchAnnotationSetAsMap(String id, @Nullable String studyStr, long variableSetId,
@Nullable String annotation, String sessionId) throws CatalogException {
QueryResult<Sample> sampleQueryResult = commonSearchAnnotationSet(id, studyStr, variableSetId, annotation, sessionId);
List<ObjectMap> annotationSets;
if (sampleQueryResult == null || sampleQueryResult.getNumResults() == 0) {
logger.debug("No samples found");
annotationSets = Collections.emptyList();
} else {
logger.debug("Found {} sample with {} annotationSets", sampleQueryResult.getNumResults(),
sampleQueryResult.first().getAnnotationSets().size());
annotationSets = sampleQueryResult.first().getAnnotationSetAsMap();
}
return new QueryResult<>("Search annotation sets", sampleQueryResult.getDbTime(), annotationSets.size(), annotationSets.size(),
sampleQueryResult.getWarningMsg(), sampleQueryResult.getErrorMsg(), annotationSets);
}
@Override
public QueryResult<AnnotationSet> searchAnnotationSet(String id, @Nullable String studyStr, long variableSetId,
@Nullable String annotation, String sessionId) throws CatalogException {
QueryResult<Sample> sampleQueryResult = commonSearchAnnotationSet(id, null, variableSetId, annotation, sessionId);
List<AnnotationSet> annotationSets;
if (sampleQueryResult == null || sampleQueryResult.getNumResults() == 0) {
logger.debug("No samples found");
annotationSets = Collections.emptyList();
} else {
logger.debug("Found {} sample with {} annotationSets", sampleQueryResult.getNumResults(),
sampleQueryResult.first().getAnnotationSets().size());
annotationSets = sampleQueryResult.first().getAnnotationSets();
}
return new QueryResult<>("Search annotation sets", sampleQueryResult.getDbTime(), annotationSets.size(), annotationSets.size(),
sampleQueryResult.getWarningMsg(), sampleQueryResult.getErrorMsg(), annotationSets);
}
private QueryResult<Sample> commonSearchAnnotationSet(String id, @Nullable String studyStr, long variableSetId,
@Nullable String annotation, String sessionId) throws CatalogException {
ParamUtils.checkParameter(id, "id");
AbstractManager.MyResourceId resourceId = catalogManager.getSampleManager().getId(id, studyStr, sessionId);
authorizationManager.checkSamplePermission(resourceId.getResourceId(), resourceId.getUser(),
SampleAclEntry.SamplePermissions.VIEW_ANNOTATIONS);
Query query = new Query(SampleDBAdaptor.QueryParams.ID.key(), id);
if (variableSetId > 0) {
query.append(SampleDBAdaptor.QueryParams.VARIABLE_SET_ID.key(), variableSetId);
}
if (annotation != null) {
query.append(SampleDBAdaptor.QueryParams.ANNOTATION.key(), annotation);
}
QueryOptions queryOptions = new QueryOptions(QueryOptions.INCLUDE, SampleDBAdaptor.QueryParams.ANNOTATION_SETS.key());
logger.debug("Query: {}, \t QueryOptions: {}", query.safeToString(), queryOptions.safeToString());
QueryResult<Sample> sampleQueryResult = sampleDBAdaptor.get(query, queryOptions);
// Filter out annotation sets only from the variable set id specified
for (Sample sample : sampleQueryResult.getResult()) {
List<AnnotationSet> finalAnnotationSets = new ArrayList<>();
for (AnnotationSet annotationSet : sample.getAnnotationSets()) {
if (annotationSet.getVariableSetId() == variableSetId) {
finalAnnotationSets.add(annotationSet);
}
}
if (finalAnnotationSets.size() > 0) {
sample.setAnnotationSets(finalAnnotationSets);
}
}
return sampleQueryResult;
}
}