/* * 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.*; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import com.mongodb.ErrorCategory; import com.mongodb.MongoWriteException; 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 com.mongodb.util.JSON; import org.bson.Document; import org.bson.conversions.Bson; import org.opencb.commons.datastore.core.*; import org.opencb.commons.datastore.mongodb.MongoDBCollection; import org.opencb.commons.datastore.mongodb.MongoDBQueryUtils; import org.opencb.opencga.catalog.db.AbstractDBAdaptor; import org.opencb.opencga.catalog.db.DBAdaptorFactory; import org.opencb.opencga.catalog.db.api.DBAdaptor; import org.opencb.opencga.catalog.db.api.FileDBAdaptor; import org.opencb.opencga.catalog.db.api.SampleDBAdaptor; import org.opencb.opencga.catalog.exceptions.CatalogDBException; import org.opencb.opencga.catalog.models.*; import org.opencb.opencga.catalog.models.acls.permissions.AbstractAclEntry; import org.slf4j.Logger; import java.io.IOException; import java.util.*; import java.util.function.Function; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import static org.opencb.opencga.catalog.db.mongodb.MongoDBAdaptor.PRIVATE_ID; /** * Created by imedina on 21/11/14. */ class MongoDBUtils { // Special queryOptions keys /** * SKIP_CHECK is used when deleting a document. If SKIP_CHECK is set to false, the document will be deleted no matter if other * documents might depend on that one. * @deprecated Use {@link DBAdaptor#SKIP_CHECK} */ @Deprecated public static final String SKIP_CHECK = DBAdaptor.SKIP_CHECK; /** * @deprecated Use {@link DBAdaptor#FORCE} instead. */ @Deprecated public static final String FORCE = DBAdaptor.FORCE; /** * KEEP_OUTPUT_FILES is used when deleting/removing a job. If it is set to true, it will mean that the output files that have been * generated with the job going to be deleted/removed will be kept. Otherwise, those files will be also deleted/removed. */ public static final String KEEP_OUTPUT_FILES = "keepOutputFiles"; public static final Set<String> DATASTORE_OPTIONS = Arrays.asList("include", "exclude", "sort", "limit", "skip").stream() .collect(Collectors.toSet()); public static final Set<String> OTHER_OPTIONS = Arrays.asList("of", "sid", "sessionId", "metadata", "includeProjects", "includeStudies", "includeFiles", "includeJobs", "includeSamples").stream().collect(Collectors.toSet()); // public static final Pattern OPERATION_PATTERN = Pattern.compile("^([^=<>~!]*)(<=?|>=?|!=|!?=?~|==?)([^=<>~!]+.*)$"); public static final Pattern OPERATION_PATTERN = Pattern.compile("^()(<=?|>=?|!=|!?=?~|==?)([^=<>~!]+.*)$"); public static final Pattern ANNOTATION_PATTERN = Pattern.compile("^([a-zA-Z\\\\.]+)([\\^=<>~!$]+.*)$"); static final String TO_REPLACE_DOTS = "."; private static ObjectMapper jsonObjectMapper; private static ObjectWriter jsonObjectWriter; private static Map<Class, ObjectReader> jsonReaderMap; static { jsonObjectMapper = new ObjectMapper(); jsonObjectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false); jsonObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); jsonObjectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false); jsonObjectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); jsonObjectMapper.configure(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS, true); jsonObjectWriter = jsonObjectMapper.writer(); jsonReaderMap = new HashMap<>(); } @Deprecated static 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("_id", MongoDBAdaptorFactory.METADATA_OBJECT_ID); Document projection = new Document(field, true); Bson inc = Updates.inc(field, 1); 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).getInteger(field); } //--------------- ACL operations -------------------------/ static void createAcl(long id, AbstractAclEntry acl, MongoDBCollection collection, String clazz) throws CatalogDBException { // Push the new acl to the list of acls. Document queryDocument = new Document(PRIVATE_ID, id); Document update = new Document("$push", new Document(FileDBAdaptor.QueryParams.ACL.key(), getMongoDBDocument(acl, clazz))); QueryResult<UpdateResult> updateResult = collection.update(queryDocument, update, null); if (updateResult.first().getModifiedCount() == 0) { throw new CatalogDBException("create Acl: An error occurred when trying to create acl for " + id + " for " + acl.getMember()); } } static QueryResult<Document> getAcl(long id, List<String> members, MongoDBCollection collection, Logger logger) throws CatalogDBException { List<Bson> aggregation = new ArrayList<>(); aggregation.add(Aggregates.match(Filters.eq(PRIVATE_ID, id))); aggregation.add(Aggregates.project(Projections.include(FileDBAdaptor.QueryParams.ID.key(), FileDBAdaptor.QueryParams.ACL.key()))); aggregation.add(Aggregates.unwind("$" + FileDBAdaptor.QueryParams.ACL.key())); List<Bson> filters = new ArrayList<>(); if (members != null && members.size() > 0) { filters.add(Filters.in(FileDBAdaptor.QueryParams.ACL_MEMBER.key(), members)); } if (filters.size() > 0) { Bson filter = filters.size() == 1 ? filters.get(0) : Filters.and(filters); aggregation.add(Aggregates.match(filter)); } for (Bson bson : aggregation) { logger.debug("Get Acl: {}", bson.toBsonDocument(Document.class, com.mongodb.MongoClient.getDefaultCodecRegistry())); } return collection.aggregate(aggregation, null); } static void removeAcl(long id, String member, MongoDBCollection collection) throws CatalogDBException { Document query = new Document() .append(PRIVATE_ID, id) .append(FileDBAdaptor.QueryParams.ACL_MEMBER.key(), member); Bson update = new Document() .append("$pull", new Document("acl", new Document("member", member))); QueryResult<UpdateResult> updateResult = collection.update(query, update, null); if (updateResult.first().getModifiedCount() == 0) { throw new CatalogDBException("remove ACL: An error occurred when trying to remove the ACL defined for " + member); } } static void setAclsToMember(long id, String member, List<String> permissions, MongoDBCollection collection) throws CatalogDBException { Document query = new Document() .append(PRIVATE_ID, id) .append(FileDBAdaptor.QueryParams.ACL_MEMBER.key(), member); Document update = new Document("$set", new Document("acl.$.permissions", permissions)); QueryResult<UpdateResult> queryResult = collection.update(query, update, null); if (queryResult.first().getModifiedCount() != 1) { throw new CatalogDBException("Unable to set the new permissions to " + member); } } static void addAclsToMember(long id, String member, List<String> permissions, MongoDBCollection collection) throws CatalogDBException { Document query = new Document() .append(PRIVATE_ID, id) .append(FileDBAdaptor.QueryParams.ACL_MEMBER.key(), member); Document update = new Document("$addToSet", new Document("acl.$.permissions", new Document("$each", permissions))); QueryResult<UpdateResult> queryResult = collection.update(query, update, null); if (queryResult.first().getModifiedCount() != 1) { throw new CatalogDBException("Unable to add new permissions to " + member + ". Maybe the member already had those" + " permissions?"); } } static void removeAclsFromMember(long id, String member, List<String> permissions, MongoDBCollection collection) throws CatalogDBException { Document query = new Document() .append(PRIVATE_ID, id) .append(FileDBAdaptor.QueryParams.ACL_MEMBER.key(), member); Bson pull = Updates.pullAll("acl.$.permissions", permissions); QueryResult<UpdateResult> update = collection.update(query, pull, null); if (update.first().getModifiedCount() != 1) { throw new CatalogDBException("Unable to remove the permissions from " + member + ". Maybe it didn't have those permissions?"); } } //--------------- End ACL operations ---------------------/ /* * Helper methods ********************/ /** * Checks if the field {@link AclEntry#userId} is valid. * * The "userId" can be: * - '*' referring to all the users. See {@link AclEntry#USER_OTHERS_ID} * - '@{groupId}' referring to a {@link Group}. See {@link AclEntry#USER_OTHERS_ID} * - '{userId}' referring to a specific user. * * @param dbAdaptorFactory dbAdaptorFactory * @param userId userId * @param studyId studyId * @throws CatalogDBException CatalogDBException */ @Deprecated public static void checkAclUserId(DBAdaptorFactory dbAdaptorFactory, String userId, long studyId) throws CatalogDBException { if (userId.equals(AclEntry.USER_OTHERS_ID)) { return; } else if (userId.startsWith("@")) { String groupId = userId.substring(1); QueryResult<Group> queryResult = dbAdaptorFactory.getCatalogStudyDBAdaptor().getGroup(studyId, null, groupId, null); if (queryResult.getNumResults() == 0) { throw CatalogDBException.idNotFound("Group", groupId); } } else { dbAdaptorFactory.getCatalogUserDBAdaptor().checkId(userId); } } static User parseUser(QueryResult<Document> result) throws CatalogDBException { return parseObject(result, User.class); } static List<Study> parseStudies(QueryResult<Document> result) throws CatalogDBException { return parseObjects(result, Study.class); } static List<File> parseFiles(QueryResult<Document> result) throws CatalogDBException { return parseObjects(result, File.class); } static Job parseJob(QueryResult<Document> result) throws CatalogDBException { return parseObject(result, Job.class); } static List<Job> parseJobs(QueryResult<Document> result) throws CatalogDBException { return parseObjects(result, Job.class); } static List<Sample> parseSamples(QueryResult<Document> result) throws CatalogDBException { return parseObjects(result, Sample.class); } static <T> List<T> parseObjects(QueryResult<Document> result, Class<T> tClass) throws CatalogDBException { LinkedList<T> objects = new LinkedList<>(); ObjectReader objectReader = getObjectReader(tClass); try { for (Document document : result.getResult()) { // document.remove("_id"); // document.remove("_projectId"); objects.add(objectReader.<T>readValue(restoreDotsInKeys(jsonObjectWriter.writeValueAsString(document)))); } } catch (IOException e) { throw new CatalogDBException("Error parsing " + tClass.getName(), e); } return objects; } static <T> T parseObject(QueryResult<Document> result, Class<T> tClass) throws CatalogDBException { if (result.getResult().isEmpty()) { return null; } try { // result.first().remove("_id"); // result.first().remove("_studyId"); String s = jsonObjectWriter.writeValueAsString(result.first()); // return getObjectReader(tClass).readValue(restoreDotsInKeys(result.first().toJson())); return getObjectReader(tClass).readValue(restoreDotsInKeys(s)); } catch (IOException e) { throw new CatalogDBException("Error parsing " + tClass.getName(), e); } } static <T> T parseObject(Document result, Class<T> tClass) throws CatalogDBException { try { return getObjectReader(tClass).readValue(restoreDotsInKeys(result).toJson()); } catch (IOException e) { throw new CatalogDBException("Error parsing " + tClass.getName(), e); } } public static <T> ObjectReader getObjectReader(Class<T> tClass) { if (!jsonReaderMap.containsKey(tClass)) { jsonReaderMap.put(tClass, jsonObjectMapper.reader(tClass)); } return jsonReaderMap.get(tClass); } @Deprecated static DBObject getDbObject(Object object, String objectName) throws CatalogDBException { DBObject dbObject; String jsonString = null; try { jsonString = jsonObjectWriter.writeValueAsString(object); dbObject = (DBObject) JSON.parse(jsonString); dbObject = replaceDotsInKeys(dbObject); } catch (Exception e) { throw new CatalogDBException("Error while writing to Json : " + objectName + (jsonString == null ? "" : (" -> " + jsonString) ), e); } return dbObject; } static Document getMongoDBDocument(Object object, String objectName) throws CatalogDBException { Document document; String jsonString = null; try { jsonString = jsonObjectWriter.writeValueAsString(object); document = Document.parse(jsonString); document = replaceDotsInKeys(document); } catch (Exception e) { throw new CatalogDBException("Error while writing to Json : " + objectName + (jsonString == null ? "" : (" -> " + jsonString)), e); } return document; } // static final String TO_REPLACE_DOTS = "\uff0e"; /*** * Scan all the DBObject and replace all the dots in keys with. * @param object object * @param <T> T * @return T */ static <T> T replaceDotsInKeys(T object) { return replaceInKeys(object, ".", TO_REPLACE_DOTS); } static <T> T restoreDotsInKeys(T object) { return replaceInKeys(object, TO_REPLACE_DOTS, "."); } static <T> T replaceInKeys(T object, String target, String replacement) { if (object instanceof Document) { Document document = (Document) object; List<String> keys = new ArrayList<>(); for (String s : document.keySet()) { if (s.contains(target)) { keys.add(s); } replaceInKeys(document.get(s), target, replacement); } for (String key : keys) { Object value = document.remove(key); key = key.replace(target, replacement); document.put(key, value); } } else if (object instanceof List) { for (Object o : ((List) object)) { replaceInKeys(o, target, replacement); } } return object; } /* */ /** * Filter "include" and "exclude" options. * <p> * Include and Exclude options are as absolute routes. This method removes all the values that are not in the * specified route. For the values in the route, the route is removed. * <p> * [ * name, * projects.id, * projects.studies.id, * projects.studies.alias, * projects.studies.name * ] * <p> * with route = "projects.studies.", then * <p> * [ * id, * alias, * name * ] * * @param options options * @param route route * @return QueryOptions */ static QueryOptions filterOptions(QueryOptions options, String route) { if (options == null) { return null; } QueryOptions filteredOptions = new QueryOptions(options); //copy queryOptions String[] filteringLists = {"include", "exclude"}; for (String listName : filteringLists) { List<String> list = filteredOptions.getAsStringList(listName); List<String> filteredList = new LinkedList<>(); int length = route.length(); if (list != null && !list.isEmpty()) { for (String s : list) { if (s.startsWith(route)) { filteredList.add(s.substring(length)); } else { filteredList.add(s); } } if (listName.equals("include")) { filteredList.add("id"); filteredList.add(PRIVATE_ID); } else if (listName.equals("exclude")) { filteredList.remove("id"); filteredList.remove(PRIVATE_ID); } filteredOptions.put(listName, filteredList); } } return filteredOptions; } static void filterBooleanParams(ObjectMap parameters, Map<String, Object> filteredParams, String[] acceptedParams) { for (String s : acceptedParams) { if (parameters.containsKey(s)) { filteredParams.put(s, parameters.getBoolean(s)); } } } static void filterStringParams(ObjectMap parameters, Map<String, Object> filteredParams, String[] acceptedParams) { for (String s : acceptedParams) { if (parameters.containsKey(s)) { filteredParams.put(s, parameters.getString(s)); } } } static void filterEnumParams(ObjectMap parameters, Map<String, Object> filteredParams, Map<String, Class<? extends Enum>> acceptedParams) throws CatalogDBException { for (Map.Entry<String, Class<? extends Enum>> e : acceptedParams.entrySet()) { if (parameters.containsKey(e.getKey())) { String parameterValue = parameters.getString(e.getKey()); Set<String> set = (Set<String>) EnumSet.allOf(e.getValue()).stream().map(Object::toString).collect(Collectors.toSet()); if (!set.contains(parameterValue)) { throw new CatalogDBException("Invalid parameter { " + e.getKey() + ": \"" + parameterValue + "\" }. Accepted values " + "from Enum " + e.getValue() + " " + EnumSet.allOf(e.getValue())); } filteredParams.put(e.getKey(), parameterValue); } } } static void filterIntegerListParams(ObjectMap parameters, Map<String, Object> filteredParams, String[] acceptedIntegerListParams) { for (String s : acceptedIntegerListParams) { if (parameters.containsKey(s)) { filteredParams.put(s, parameters.getAsIntegerList(s)); } } } static void filterLongListParams(ObjectMap parameters, Map<String, Object> filteredParams, String[] acceptedLongListParams) { for (String s : acceptedLongListParams) { if (parameters.containsKey(s)) { filteredParams.put(s, parameters.getAsLongList(s)); } } } static void filterMapParams(ObjectMap parameters, Map<String, Object> filteredParams, String[] acceptedMapParams) { for (String s : acceptedMapParams) { if (parameters.containsKey(s)) { ObjectMap map; if (parameters.get(s) instanceof Map) { map = new ObjectMap(parameters.getMap(s)); } else { map = new ObjectMap(parameters.getString(s)); } try { Document document = getMongoDBDocument(map, s); if (map.size() > 0) { for (Map.Entry<String, Object> entry : map.entrySet()) { filteredParams.put(s + "." + entry.getKey(), document.get(entry.getKey())); } } else { filteredParams.put(s, document); } } catch (CatalogDBException e) { e.printStackTrace(); } } } } static void filterObjectParams(ObjectMap parameters, Map<String, Object> filteredParams, String[] acceptedMapParams) { for (String s : acceptedMapParams) { if (parameters.containsKey(s)) { Document document = null; try { document = getMongoDBDocument(parameters.get(s), s); filteredParams.put(s, document); } catch (CatalogDBException e) { e.printStackTrace(); } } } } static void filterIntParams(ObjectMap parameters, Map<String, Object> filteredParams, String[] acceptedIntParams) { for (String s : acceptedIntParams) { if (parameters.containsKey(s)) { int anInt = parameters.getInt(s, Integer.MIN_VALUE); if (anInt != Integer.MIN_VALUE) { filteredParams.put(s, anInt); } } } } static void filterLongParams(ObjectMap parameters, Map<String, Object> filteredParams, String[] acceptedLongParams) { for (String s : acceptedLongParams) { if (parameters.containsKey(s)) { long aLong = parameters.getLong(s, Long.MIN_VALUE); if (aLong != Long.MIN_VALUE) { filteredParams.put(s, aLong); } } } } static boolean isDataStoreOption(String key) { return DATASTORE_OPTIONS.contains(key); } static boolean isOtherKnownOption(String key) { return OTHER_OPTIONS.contains(key); } /** * Changes the format of the queries. Queries retrieved from the WS come as "annotation": "nestedKey.subkey=5,sex=male". * That will be changed to "annotation.nestedKey.subkey" : "=5"; "annotation.sex": "=male" * * @param query queryObject */ public static void fixAnnotationQuery(Query query) { if (!query.containsKey(SampleDBAdaptor.QueryParams.ANNOTATION.key())) { return; } List<String> valueList = query.getAsStringList(SampleDBAdaptor.QueryParams.ANNOTATION.key(), ";"); for (String annotation : valueList) { Matcher matcher = ANNOTATION_PATTERN.matcher(annotation); String key; String queryValueString; if (matcher.find()) { key = matcher.group(1); if (!key.startsWith(SampleDBAdaptor.QueryParams.ANNOTATION.key() + ".")) { key = SampleDBAdaptor.QueryParams.ANNOTATION.key() + "." + key; } queryValueString = matcher.group(2); query.append(key, queryValueString); } } // Remove the current query query.remove(SampleDBAdaptor.QueryParams.ANNOTATION.key()); } /** * Parse the query values from the ontology terms to bson format. * At this point, any value that we find in the query (as comma separated or as a list), will be looked for in the id and name * attributes of the ontologyTerm java bean. * * Example: ontologyTerms: X,Y * This will be transformed to something like ontologyTerms.id == X || ontologyTerms.name == X || ontologyTerms.id == Y || * ontologyTerms.name == Y) * * @param mongoKey Key corresponding to the data model to know how it is stored in mongoDB. (If not altered, ontologyTerms). * @param queryKey Key by which the values will be retrieved from the query. * @param query Query object containing all the query keys and values to parse. Only to get the ones regarding ontology terms. * @param bsonList List to which we will add the ontology terms search. */ public static void addOntologyQueryFilter(String mongoKey, String queryKey, Query query, List<Bson> bsonList) { List<String> ontologyValues = query.getAsStringList(queryKey); Bson ontologyId = MongoDBQueryUtils.createFilter(mongoKey + ".id", ontologyValues); Bson ontologyName = MongoDBQueryUtils.createFilter(mongoKey + ".name", ontologyValues); bsonList.add(Filters.or(ontologyId, ontologyName)); } public static void addAnnotationQueryFilter(String optionKey, Query query, Map<String, Variable> variableMap, List<Bson> annotationSetFilter) throws CatalogDBException { // Annotation Filter final String sepOr = ","; String annotationKey; if (optionKey.startsWith("annotation.")) { annotationKey = optionKey.substring("annotation.".length()); } else { throw new CatalogDBException("Wrong annotation query. Expects: {\"annotation.<variable>\" , <operator><value> } "); } String annotationValue = query.getString(optionKey); final String variableId; final String route; if (annotationKey.contains(".")) { String[] variableIdRoute = annotationKey.split("\\.", 2); variableId = variableIdRoute[0]; route = "." + variableIdRoute[1]; } else { variableId = annotationKey; route = ""; } String[] values = annotationValue.split(sepOr); QueryParam.Type type = QueryParam.Type.TEXT; if (variableMap != null) { Variable variable = variableMap.get(variableId); if (variable == null) { throw new CatalogDBException("Variable \"" + variableId + "\" not found in variableSet "); } Variable.VariableType variableType = variable.getType(); if (variable.getType() == Variable.VariableType.OBJECT) { String[] routes = route.split("\\."); for (String r : routes) { if (variable.getType() != Variable.VariableType.OBJECT) { throw new CatalogDBException("Unable to query variable " + annotationKey); } if (variable.getVariableSet() != null) { Map<String, Variable> subVariableMap = variable.getVariableSet().stream() .collect(Collectors.toMap(Variable::getName, Function.<Variable>identity())); if (subVariableMap.containsKey(r)) { variable = subVariableMap.get(r); variableType = variable.getType(); } } else { variableType = Variable.VariableType.TEXT; break; } } } if (variableType == Variable.VariableType.BOOLEAN) { type = QueryParam.Type.BOOLEAN; } else if (variableType == Variable.VariableType.NUMERIC) { type = QueryParam.Type.DECIMAL; } } List<Bson> valueList = addCompQueryFilter(type, "value" + route, Arrays.asList(values), new ArrayList<>()); annotationSetFilter.add( Filters.elemMatch("annotations", Filters.and( Filters.eq("name", variableId), valueList.get(0) )) ); } @Deprecated public static void addAnnotationQueryFilter(String optionKey, QueryOptions options, List<DBObject> annotationSetFilter, Map<String, Variable> variableMap) throws CatalogDBException { // Annotation Filters final String sepAnd = ";"; final String sepOr = ","; final String sepIs = ":"; for (String annotation : options.getAsStringList(optionKey, sepAnd)) { String[] split = annotation.split(sepIs, 2); if (split.length != 2) { throw new CatalogDBException("Malformed annotation query : " + annotation); } final String variableId; final String route; if (split[0].contains(".")) { String[] variableIdRoute = split[0].split("\\.", 2); variableId = variableIdRoute[0]; route = "." + variableIdRoute[1]; } else { variableId = split[0]; route = ""; } String[] values = split[1].split(sepOr); AbstractDBAdaptor.FilterOption.Type type = AbstractDBAdaptor.FilterOption.Type.TEXT; if (variableMap != null) { Variable variable = variableMap.get(variableId); Variable.VariableType variableType = variable.getType(); if (variable.getType() == Variable.VariableType.OBJECT) { String[] routes = route.split("\\."); for (String r : routes) { if (variable.getType() != Variable.VariableType.OBJECT) { throw new CatalogDBException("Unable to query variable " + split[0]); } if (variable.getVariableSet() != null) { Map<String, Variable> subVariableMap = variable.getVariableSet().stream() .collect(Collectors.toMap(Variable::getName, Function.<Variable>identity())); if (subVariableMap.containsKey(r)) { variable = subVariableMap.get(r); variableType = variable.getType(); } } else { variableType = Variable.VariableType.TEXT; break; } } } if (variableType == Variable.VariableType.BOOLEAN) { type = AbstractDBAdaptor.FilterOption.Type.BOOLEAN; } else if (variableType == Variable.VariableType.NUMERIC) { type = AbstractDBAdaptor.FilterOption.Type.NUMERICAL; } } List<DBObject> queryValues = addCompQueryFilter(type, Arrays.asList(values), "value" + route, new LinkedList<DBObject>()); annotationSetFilter.add( new BasicDBObject("annotations", new BasicDBObject("$elemMatch", new BasicDBObject(queryValues.get(0).toMap()).append("name", variableId) ) ) ); } } static List<Bson> addCompQueryFilter(QueryParam option, String optionKey, String queryKey, ObjectMap options, List<Bson> andQuery) throws CatalogDBException { List<String> optionsList = options.getAsStringList(optionKey); if (queryKey == null) { queryKey = ""; } return addCompQueryFilter(option.type(), queryKey, optionsList, andQuery); } private static List<Bson> addCompQueryFilter(QueryParam.Type type, String queryKey, List<String> optionsList, List<Bson> andQuery) throws CatalogDBException { ArrayList<Bson> or = new ArrayList<>(optionsList.size()); for (String option : optionsList) { Matcher matcher = OPERATION_PATTERN.matcher(option); String operator; String key; String filter; if (!matcher.find()) { operator = ""; key = queryKey; filter = option; } else { operator = matcher.group(2); // if (queryKey.isEmpty()) { // key = matcher.group(1); // } else { // String separatorDot = matcher.group(1).isEmpty() ? "" : "."; // key = queryKey + separatorDot + matcher.group(1); // } key = queryKey; filter = matcher.group(3); } if (key.isEmpty()) { throw new CatalogDBException("Unknown filter operation: " + option + " . Missing key"); } switch (type) { case DECIMAL: case DOUBLE: case DECIMAL_ARRAY: try { double doubleValue = Double.parseDouble(filter); or.add(addNumberOperationQueryFilter(key, operator, doubleValue)); } catch (NumberFormatException e) { throw new CatalogDBException(e); } break; case TEXT: case TEXT_ARRAY: or.add(addStringOperationQueryFilter(key, operator, filter)); break; case BOOLEAN: or.add(addBooleanOperationQueryFilter(key, operator, Boolean.parseBoolean(filter), new Document())); break; default: break; } } if (or.isEmpty()) { return andQuery; } else if (or.size() == 1) { andQuery.add(or.get(0)); } else { andQuery.add(new Document("$or", or)); } return andQuery; } @Deprecated static List<DBObject> addCompQueryFilter(AbstractDBAdaptor.FilterOption option, String optionKey, ObjectMap options, String queryKey, List<DBObject> andQuery) throws CatalogDBException { List<String> optionsList = options.getAsStringList(optionKey); if (queryKey == null) { queryKey = ""; } return addCompQueryFilter(option.getType(), optionsList, queryKey, andQuery); } @Deprecated private static List<DBObject> addCompQueryFilter(AbstractDBAdaptor.FilterOption.Type type, List<String> optionsList, String queryKey, List<DBObject> andQuery) throws CatalogDBException { ArrayList<DBObject> or = new ArrayList<>(optionsList.size()); for (String option : optionsList) { Matcher matcher = OPERATION_PATTERN.matcher(option); String operator; String key; String filter; if (!matcher.find()) { operator = ""; key = queryKey; filter = option; } else { operator = matcher.group(2); // if (queryKey.isEmpty()) { // key = matcher.group(1); // } else { // String separatorDot = matcher.group(1).isEmpty() ? "" : "."; // key = queryKey + separatorDot + matcher.group(1); // } key = queryKey; filter = matcher.group(3); } if (key.isEmpty()) { throw new CatalogDBException("Unknown filter operation: " + option + " . Missing key"); } switch (type) { case NUMERICAL: try { double doubleValue = Double.parseDouble(filter); or.add(addNumberOperationQueryFilter(key, operator, doubleValue, new BasicDBObject())); } catch (NumberFormatException e) { throw new CatalogDBException(e); } break; case TEXT: or.add(addStringOperationQueryFilter(key, operator, filter, new BasicDBObject())); break; case BOOLEAN: or.add(addBooleanOperationQueryFilter(key, operator, Boolean.parseBoolean(filter), new BasicDBObject())); break; default: break; } } if (or.isEmpty()) { return andQuery; } else if (or.size() == 1) { andQuery.add(or.get(0)); } else { andQuery.add(new BasicDBObject("$or", or)); } return andQuery; } @Deprecated static DBObject addStringOperationQueryFilter(String queryKey, String op, String filter, DBObject query) throws CatalogDBException { switch (op) { case "<": query.put(queryKey, new BasicDBObject("$lt", filter)); break; case "<=": query.put(queryKey, new BasicDBObject("$lte", filter)); break; case ">": query.put(queryKey, new BasicDBObject("$gt", filter)); break; case ">=": query.put(queryKey, new BasicDBObject("$gte", filter)); break; case "!=": query.put(queryKey, new BasicDBObject("$ne", filter)); break; case "": case "=": case "==": query.put(queryKey, filter); break; case "~": case "=~": query.put(queryKey, new BasicDBObject("$regex", filter)); break; default: throw new CatalogDBException("Unknown numerical query operation " + op); } return query; } static Document addStringOperationQueryFilter(String queryKey, String op, String filter) throws CatalogDBException { Document query; switch (op) { case "<": query = new Document(queryKey, new Document("$lt", filter)); break; case "<=": query = new Document(queryKey, new Document("$lte", filter)); break; case ">": query = new Document(queryKey, new Document("$gt", filter)); break; case ">=": query = new Document(queryKey, new Document("$gte", filter)); break; case "!=": query = new Document(queryKey, new Document("$ne", filter)); break; case "": case "=": case "==": query = new Document(queryKey, filter); break; case "~": case "=~": query = new Document(queryKey, new Document("$regex", filter)); break; default: throw new CatalogDBException("Unknown numerical query operation " + op); } return query; } @Deprecated static DBObject addNumberOperationQueryFilter(String queryKey, String op, Number filter, DBObject query) throws CatalogDBException { switch (op) { case "<": query.put(queryKey, new BasicDBObject("$lt", filter)); break; case "<=": query.put(queryKey, new BasicDBObject("$lte", filter)); break; case ">": query.put(queryKey, new BasicDBObject("$gt", filter)); break; case ">=": query.put(queryKey, new BasicDBObject("$gte", filter)); break; case "!=": query.put(queryKey, new BasicDBObject("$ne", filter)); break; case "": case "=": case "==": query.put(queryKey, filter); break; default: throw new CatalogDBException("Unknown string query operation " + op); } return query; } static Document addNumberOperationQueryFilter(String queryKey, String op, Number filter) throws CatalogDBException { Document query; switch (op) { case "<": query = new Document(queryKey, new Document("$lt", filter)); break; case "<=": query = new Document(queryKey, new Document("$lte", filter)); break; case ">": query = new Document(queryKey, new Document("$gt", filter)); break; case ">=": query = new Document(queryKey, new Document("$gte", filter)); break; case "!=": query = new Document(queryKey, new Document("$ne", filter)); break; case "": case "=": case "==": query = new Document(queryKey, filter); break; default: throw new CatalogDBException("Unknown string query operation " + op); } return query; } @Deprecated static DBObject addBooleanOperationQueryFilter(String queryKey, String op, Boolean filter, DBObject query) throws CatalogDBException { switch (op) { case "!=": query.put(queryKey, new BasicDBObject("$ne", filter)); break; case "": case "=": case "==": query.put(queryKey, filter); break; default: throw new CatalogDBException("Unknown boolean query operation " + op); } return query; } static Document addBooleanOperationQueryFilter(String queryKey, String op, Boolean filter, Document query) throws CatalogDBException { switch (op) { case "!=": query.put(queryKey, new Document("$ne", filter)); break; case "": case "=": case "==": query.put(queryKey, filter); break; default: throw new CatalogDBException("Unknown boolean query operation " + op); } return query; } static boolean isDuplicateKeyException(MongoWriteException e) { return ErrorCategory.fromErrorCode(e.getCode()) == ErrorCategory.DUPLICATE_KEY; } static CatalogDBException ifDuplicateKeyException(Supplier<? extends CatalogDBException> producer, MongoWriteException e) { if (isDuplicateKeyException(e)) { return producer.get(); } else { throw e; } } }