/* * 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.mongodb.client.model.Accumulators; import com.mongodb.client.model.Aggregates; import com.mongodb.client.model.Projections; import com.mongodb.client.model.Sorts; import org.bson.Document; import org.bson.conversions.Bson; import org.opencb.commons.datastore.core.Query; import org.opencb.commons.datastore.core.QueryOptions; import org.opencb.commons.datastore.core.QueryParam; import org.opencb.commons.datastore.core.QueryResult; import org.opencb.commons.datastore.mongodb.MongoDBCollection; import org.opencb.commons.datastore.mongodb.MongoDBQueryUtils; import org.opencb.opencga.catalog.db.AbstractDBAdaptor; import org.slf4j.Logger; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Created by jacobo on 12/09/14. */ public class MongoDBAdaptor extends AbstractDBAdaptor { static final String PRIVATE_ID = "_id"; static final String PRIVATE_PROJECT_ID = "_projectId"; static final String PRIVATE_STUDY_ID = "_studyId"; static final String FILTER_ROUTE_PROJECTS = "projects."; static final String FILTER_ROUTE_STUDIES = "projects.studies."; static final String FILTER_ROUTE_COHORTS = "projects.studies.cohorts."; static final String FILTER_ROUTE_DATASETS = "projects.studies.datasets."; static final String FILTER_ROUTE_INDIVIDUALS = "projects.studies.individuals."; static final String FILTER_ROUTE_SAMPLES = "projects.studies.samples."; static final String FILTER_ROUTE_FILES = "projects.studies.files."; static final String FILTER_ROUTE_JOBS = "projects.studies.jobs."; static final String FILTER_ROUTE_PANELS = "projects.studies.panels."; protected MongoDBAdaptorFactory dbAdaptorFactory; public MongoDBAdaptor(Logger logger) { super(logger); } protected long getNewId() { // return CatalogMongoDBUtils.getNewAutoIncrementId(metaCollection); return dbAdaptorFactory.getCatalogMetaDBAdaptor().getNewAutoIncrementId(); } @Deprecated protected void addIntegerOrQuery(String mongoDbField, String queryParam, Query query, List<Bson> andBsonList) { addQueryFilter(mongoDbField, queryParam, query, QueryParam.Type.INTEGER, MongoDBQueryUtils.ComparisonOperator.EQUALS, MongoDBQueryUtils.LogicalOperator.OR, andBsonList); } @Deprecated protected void addStringOrQuery(String mongoDbField, String queryParam, Query query, List<Bson> andBsonList) { addQueryFilter(mongoDbField, queryParam, query, QueryParam.Type.TEXT, MongoDBQueryUtils.ComparisonOperator.EQUALS, MongoDBQueryUtils.LogicalOperator.OR, andBsonList); } @Deprecated protected void addStringOrQuery(String mongoDbField, String queryParam, Query query, MongoDBQueryUtils.ComparisonOperator comparisonOperator, List<Bson> andBsonList) { addQueryFilter(mongoDbField, queryParam, query, QueryParam.Type.TEXT, comparisonOperator, MongoDBQueryUtils.LogicalOperator.OR, andBsonList); } /** * It will add a filter to andBsonList based on the query object. The operator will always be an EQUAL. * @param mongoDbField The field used in the mongoDB. * @param queryParam The key by which the parameter is stored in the query. Normally, it will be the same as in the data model, * although it might be some exceptions. * @param query The object containing the key:values of the query. * @param paramType The type of the object to be looked up. See {@link QueryParam}. * @param andBsonList The list where created filter will be added to. */ protected void addOrQuery(String mongoDbField, String queryParam, Query query, QueryParam.Type paramType, List<Bson> andBsonList) { addQueryFilter(mongoDbField, queryParam, query, paramType, MongoDBQueryUtils.ComparisonOperator.EQUALS, MongoDBQueryUtils.LogicalOperator.OR, andBsonList); } /** * It will check for the proper comparator based on the query value and create the correct query filter. * It could be a regular expression, >, < ... or a simple equals. * @param mongoDbField The field used in the mongoDB. * @param queryParam The key by which the parameter is stored in the query. Normally, it will be the same as in the data model, * although it might be some exceptions. * @param query The object containing the key:values of the query. * @param paramType The type of the object to be looked up. See {@link QueryParam}. * @param andBsonList The list where created filter will be added to. */ protected void addAutoOrQuery(String mongoDbField, String queryParam, Query query, QueryParam.Type paramType, List<Bson> andBsonList) { if (query != null && query.getString(queryParam) != null) { Bson filter = MongoDBQueryUtils.createAutoFilter(mongoDbField, queryParam, query, paramType); if (filter != null) { andBsonList.add(filter); } } } protected void addQueryFilter(String mongoDbField, String queryParam, Query query, QueryParam.Type paramType, MongoDBQueryUtils.ComparisonOperator comparisonOperator, MongoDBQueryUtils.LogicalOperator operator, List<Bson> andBsonList) { if (query != null && query.getString(queryParam) != null) { Bson filter = MongoDBQueryUtils.createFilter(mongoDbField, queryParam, query, paramType, comparisonOperator, operator); if (filter != null) { andBsonList.add(filter); } } } protected QueryResult rank(MongoDBCollection collection, Bson query, String groupByField, String idField, int numResults, boolean asc) { if (groupByField == null || groupByField.isEmpty()) { return new QueryResult(); } if (groupByField.contains(",")) { // call to multiple rank if commas are present return rank(collection, query, Arrays.asList(groupByField.split(",")), idField, numResults, asc); } else { Bson match = Aggregates.match(query); Bson project = Aggregates.project(Projections.include(groupByField, idField)); Bson group = Aggregates.group("$" + groupByField, Accumulators.sum("count", 1)); Bson sort; if (asc) { sort = Aggregates.sort(Sorts.ascending("count")); } else { sort = Aggregates.sort(Sorts.descending("count")); } Bson limit = Aggregates.limit(numResults); return collection.aggregate(Arrays.asList(match, project, group, sort, limit), new QueryOptions()); } } protected QueryResult rank(MongoDBCollection collection, Bson query, List<String> groupByField, String idField, int numResults, boolean asc) { if (groupByField == null || groupByField.isEmpty()) { return new QueryResult(); } if (groupByField.size() == 1) { // if only one field then we call to simple rank return rank(collection, query, groupByField.get(0), idField, numResults, asc); } else { Bson match = Aggregates.match(query); // add all group-by fields to the projection together with the aggregation field name List<String> groupByFields = new ArrayList<>(groupByField); groupByFields.add(idField); Bson project = Aggregates.project(Projections.include(groupByFields)); // _id document creation to have the multiple id Document id = new Document(); for (String s : groupByField) { id.append(s, "$" + s); } Bson group = Aggregates.group(id, Accumulators.sum("count", 1)); Bson sort; if (asc) { sort = Aggregates.sort(Sorts.ascending("count")); } else { sort = Aggregates.sort(Sorts.descending("count")); } Bson limit = Aggregates.limit(numResults); return collection.aggregate(Arrays.asList(match, project, group, sort, limit), new QueryOptions()); } } protected QueryResult groupBy(MongoDBCollection collection, Bson query, String groupByField, String idField, QueryOptions options) { if (groupByField == null || groupByField.isEmpty()) { return new QueryResult(); } if (groupByField.contains(",")) { // call to multiple groupBy if commas are present return groupBy(collection, query, Arrays.asList(groupByField.split(",")), idField, options); } else { return groupBy(collection, query, Arrays.asList(groupByField), idField, options); // Bson match = Aggregates.match(query); // List<Bson> projections = new ArrayList<>(); // addDateProjection(projections, Arrays.asList(groupByField)); // projections.add(Projections.include(groupByField, idField)); // Bson project = Aggregates.project(Projections.fields(projections)); //// Bson project = Aggregates.project(Projections.include(groupByField, idField)); // Bson group; // if (options.getBoolean("count", false)) { // group = Aggregates.group("$" + groupByField, Accumulators.sum("count", 1)); // } else { // group = Aggregates.group("$" + groupByField, Accumulators.addToSet("features", "$" + idField)); // } // return collection.aggregate(Arrays.asList(match, project, group), options); } } protected QueryResult groupBy(MongoDBCollection collection, Bson query, List<String> groupByField, String idField, QueryOptions options) { if (groupByField == null || groupByField.isEmpty()) { return new QueryResult(); } List<String> groupByFields = new ArrayList<>(groupByField); // if (groupByField.size() == 1) { // // if only one field then we call to simple groupBy // return groupBy(collection, query, groupByField.get(0), idField, options); // } else { Bson match = Aggregates.match(query); // add all group-by fields to the projection together with the aggregation field name List<String> includeGroupByFields = new ArrayList<>(groupByField); includeGroupByFields.add(idField); List<Bson> projections = new ArrayList<>(); addDateProjection(projections, includeGroupByFields, groupByFields); projections.add(Projections.include(includeGroupByFields)); Bson project = Aggregates.project(Projections.fields(projections)); // Bson project = Aggregates.project(Projections.include(groupByFields)); // _id document creation to have the multiple id Document id = new Document(); for (String s : groupByFields) { id.append(s, "$" + s); } Bson group; if (options.getBoolean("count", false)) { group = Aggregates.group(id, Accumulators.sum("count", 1)); } else { group = Aggregates.group(id, Accumulators.addToSet("features", "$" + idField)); } return collection.aggregate(Arrays.asList(match, project, group), options); // } } /** * Adds the corresponding date projections to the projections list (if any), removes the date fields from includeGroupByFields and * add them to groupByFields if not there. * Only for groupBy methods. * * @param projections List of Bson containing the projections to be done. * @param includeGroupByFields List containing the fields to be included in the projection. * @param groupByFields List containing the fields by which the group by will be done. */ private void addDateProjection(List<Bson> projections, List<String> includeGroupByFields, List<String> groupByFields) { Document dateProjection = new Document(); Document year = new Document("$substr", Arrays.asList("$creationDate", 0, 4)); Document month = new Document("$substr", Arrays.asList("$creationDate", 4, 2)); Document day = new Document("$substr", Arrays.asList("$creationDate", 6, 2)); if (includeGroupByFields.contains("day")) { dateProjection.append("day", day).append("month", month).append("year", year); projections.add(dateProjection); includeGroupByFields.remove("day"); if (!includeGroupByFields.remove("month")) { groupByFields.add("month"); } if (!includeGroupByFields.remove("year")) { groupByFields.add("year"); } } else if (includeGroupByFields.contains("month")) { dateProjection.append("month", month).append("year", year); projections.add(dateProjection); includeGroupByFields.remove("month"); if (!includeGroupByFields.remove("year")) { groupByFields.add("year"); } } else if (includeGroupByFields.contains("year")) { dateProjection.append("year", year); projections.add(dateProjection); includeGroupByFields.remove("year"); } } }