View Javadoc
1   package de.dlr.shepard.common.search.query;
2   
3   import com.fasterxml.jackson.core.JsonProcessingException;
4   import com.fasterxml.jackson.databind.JsonNode;
5   import com.fasterxml.jackson.databind.ObjectMapper;
6   import de.dlr.shepard.common.exceptions.ShepardParserException;
7   import de.dlr.shepard.common.neo4j.entities.ContainerType;
8   import de.dlr.shepard.common.search.io.SearchScope;
9   import de.dlr.shepard.common.util.Constants;
10  import de.dlr.shepard.common.util.CypherQueryHelper;
11  import de.dlr.shepard.common.util.SortingHelper;
12  import de.dlr.shepard.common.util.TraversalRules;
13  import java.util.ArrayList;
14  import java.util.Iterator;
15  import java.util.List;
16  import java.util.NoSuchElementException;
17  
18  public class Neo4jQueryBuilder {
19  
20    private static final List<String> booleanOperators = List.of(
21      Constants.JSON_AND,
22      Constants.JSON_OR,
23      Constants.JSON_NOT,
24      Constants.JSON_XOR
25    );
26    private static final List<String> opAttributes = List.of(
27      Constants.OP_PROPERTY,
28      Constants.OP_VALUE,
29      Constants.OP_OPERATOR
30    );
31    private static final List<String> notIdProperties = List.of(
32      "createdBy",
33      "updatedBy",
34      "valueIRI",
35      "propertyIRI",
36      "createdAt",
37      "updatedAt",
38      "hasAnnotation",
39      "hasAnnotationIRI"
40    );
41  
42    private static final List<String> IdProperties = List.of(
43      "id",
44      "referencedCollectionId",
45      "referencedDataObjectId",
46      "fileContainerId",
47      "structuredDataContainerId",
48      "timeseriesContainerId",
49      "successorIds",
50      "predecessorIds",
51      "childrenIds",
52      "parentIds"
53    );
54  
55    private static final String INCORRECT_COLON_EXCEPTION_MESSAGE =
56      "the annotation must contain exactly one occurrence of :: to divide the property and the name " +
57      "but the given value is ";
58  
59    private static final String colonMatcher = "[^:]*(:[^:]+)*::([^:]+:)*[^:]*";
60  
61    private static String getNeo4jWithNeo4jIdString(String jsonquery, String variable) {
62      ObjectMapper objectMapper = new ObjectMapper();
63      JsonNode jsonNode = null;
64      try {
65        jsonNode = objectMapper.readValue(jsonquery, JsonNode.class);
66      } catch (JsonProcessingException e) {
67        throw new ShepardParserException("could not parse JSON " + e.getMessage());
68      }
69      return getNeo4jStringWithNeo4jId(jsonNode, variable);
70    }
71  
72    private static String getNeo4jStringWithNeo4jId(JsonNode rootNode, String variable) {
73      String op = "";
74      try {
75        op = rootNode.fieldNames().next();
76      } catch (NoSuchElementException e) {
77        throw new ShepardParserException("error in parsing" + e.getMessage());
78      }
79      if (opAttributes.contains(op)) {
80        return primitiveClauseWithNeo4jId(rootNode, variable);
81      }
82      return complexClauseWithNeo4jId(rootNode, op, variable);
83    }
84  
85    private static String complexClauseWithNeo4jId(JsonNode node, String operator, String variable) {
86      if (!booleanOperators.contains(operator)) throw new ShepardParserException("unknown boolean operator: " + operator);
87      if (operator.equals(Constants.JSON_NOT)) return notClauseWithNeo4jId(node, variable);
88      else return multaryClauseWithNeo4jId(node, operator, variable);
89    }
90  
91    private static String multaryClauseWithNeo4jId(JsonNode node, String operator, String variable) {
92      Iterator<JsonNode> argumentsArray = node.get(operator).elements();
93      String firstArgument = getNeo4jStringWithNeo4jId(argumentsArray.next(), variable);
94      String ret = "(" + firstArgument;
95      while (argumentsArray.hasNext()) {
96        ret = ret + " " + operator + " " + getNeo4jStringWithNeo4jId(argumentsArray.next(), variable);
97      }
98      ret = ret + ")";
99      return ret;
100   }
101 
102   private static String notClauseWithNeo4jId(JsonNode node, String variable) {
103     JsonNode body = node.get(Constants.JSON_NOT);
104     return "(NOT(" + getNeo4jStringWithNeo4jId(body, variable) + "))";
105   }
106 
107   private static String primitiveClauseWithNeo4jId(JsonNode node, String variable) {
108     String property = node.get(Constants.OP_PROPERTY).textValue();
109     property = changeAttributesDelimiter(property);
110     if (notIdProperties.contains(property)) return simpleNotIdPropertyPart(node, variable);
111     if (IdProperties.contains(property)) return simpleIdPropertyPart(node, variable);
112     String ret = "(";
113     ret = ret + "toLower(" + variable + ".`" + property + "`) ";
114     ret = ret + operatorString(node.get(Constants.OP_OPERATOR)) + " ";
115     ret = ret + valuePart(node).toLowerCase();
116     ret = ret + ")";
117     return ret;
118   }
119 
120   /**
121    * This is a fix described in:
122    * https://gitlab.com/dlr-shepard/shepard/-/issues/389
123    * We use new delimiter characters for attributes ('||'), but want to support
124    * the old search functionality using '.' as a delimiter.
125    *
126    * @param property
127    * @return property string, if it contained 'attributes.', it is going to be
128    * replaced by 'attributes||'
129    */
130   private static String changeAttributesDelimiter(String property) {
131     if (property.startsWith("attributes.")) {
132       return property.replaceFirst("attributes.", "attributes||");
133     }
134     return property;
135   }
136 
137   private static String valuePart(JsonNode node) {
138     String ret = "";
139     if (node.get(Constants.OP_OPERATOR).textValue().equals(Constants.JSON_IN)) {
140       ret = ret + "[";
141       Iterator<JsonNode> setArray = node.get(Constants.OP_VALUE).elements();
142       if (setArray.hasNext()) {
143         ret = ret + setArray.next();
144         while (setArray.hasNext()) ret = ret + ", " + setArray.next();
145         ret = ret + "]";
146       } else {
147         ret = ret + "]";
148       }
149     } else ret = ret + node.get(Constants.OP_VALUE);
150     return ret;
151   }
152 
153   private static String simpleNotIdPropertyPart(JsonNode node, String variable) {
154     String property = node.get(Constants.OP_PROPERTY).textValue();
155     // search for creating user
156     if (property.equals("createdBy") || property.equals("updatedBy")) return byPart(node, variable);
157     // search for createdAt/updatedAt
158     if (property.equals("createdAt") || property.equals("updatedAt")) return atPart(node, variable);
159     // for SemanticAnnotationIRIs
160     if (property.equals("valueIRI") || property.equals("propertyIRI")) return iRIPart(node, variable);
161     // for SemanticAnnotations
162     if (property.equals("hasAnnotation")) return hasAnnotationPart(node, variable);
163     // for SemanticAnnotations
164     if (property.equals("hasAnnotationIRI")) return hasAnnotationIRIPart(node, variable);
165     return null;
166   }
167 
168   private static String hasAnnotationIRIPart(JsonNode node, String variable) {
169     //REMARK:
170     //the split function in JAVA behaves sometimes in an inconsistent way:
171     //"::".split("::") = String[0] {  }
172     //"x::y".split("::") = String[2] { "x", "y" }
173     //"::y".split("::") = String[2] { "", "y" }
174     //"x::".split("::") = String[1] { "x" }
175     //to extract the empty string from the annotation pair correctly some additional work is needed
176     String annotation = node.get(Constants.OP_VALUE).textValue();
177     if (!annotation.matches(colonMatcher)) throw new ShepardParserException(
178       INCORRECT_COLON_EXCEPTION_MESSAGE + annotation
179     );
180     String[] propertyValuePair = annotation.split("::");
181     String propertyIRI = null;
182     String valueIRI = null;
183     //case ::
184     if (propertyValuePair.length == 0) {
185       propertyIRI = "";
186       valueIRI = "";
187     }
188     //case x::
189     if (propertyValuePair.length == 1) {
190       propertyIRI = propertyValuePair[0];
191       valueIRI = "";
192     }
193     //case x::y (where x may be the empty string)
194     if (propertyValuePair.length == 2) {
195       propertyIRI = propertyValuePair[0];
196       valueIRI = propertyValuePair[1];
197     }
198     String ret = "(";
199     ret =
200       ret + "EXISTS {MATCH (" + variable + ") - [:has_annotation] -> (sem:SemanticAnnotation) WHERE (sem.propertyIRI ";
201     ret = ret + operatorString(node.get(Constants.OP_OPERATOR)) + " \"" + propertyIRI + "\" AND ";
202     ret = ret + " sem.valueIRI " + operatorString(node.get(Constants.OP_OPERATOR)) + " \"" + valueIRI;
203     ret = ret + "\")})";
204     return ret;
205   }
206 
207   private static String hasAnnotationPart(JsonNode node, String variable) {
208     //REMARK:
209     //the split function in JAVA behaves sometimes in an inconsistent way:
210     //"::".split("::") = String[0] {  }
211     //"x::y".split("::") = String[2] { "x", "y" }
212     //"::y".split("::") = String[2] { "", "y" }
213     //"x::".split("::") = String[1] { "x" }
214     //to extract the empty string from the annotation pair correctly some additional work is needed
215     String annotation = node.get(Constants.OP_VALUE).textValue();
216     if (!annotation.matches(colonMatcher)) throw new ShepardParserException(
217       INCORRECT_COLON_EXCEPTION_MESSAGE + annotation
218     );
219     String[] propertyValuePair = annotation.split("::");
220     String propertyName = null;
221     String valueName = null;
222     //case ::
223     if (propertyValuePair.length == 0) {
224       propertyName = "";
225       valueName = "";
226     }
227     //case x::
228     if (propertyValuePair.length == 1) {
229       propertyName = propertyValuePair[0];
230       valueName = "";
231     }
232     //case x::y (where x may be the empty string)
233     if (propertyValuePair.length == 2) {
234       propertyName = propertyValuePair[0];
235       valueName = propertyValuePair[1];
236     }
237     String ret = "(";
238     ret =
239       ret + "EXISTS {MATCH (" + variable + ") - [:has_annotation] -> (sem:SemanticAnnotation) WHERE (sem.propertyName ";
240     ret = ret + operatorString(node.get(Constants.OP_OPERATOR)) + " \"" + propertyName + "\" AND ";
241     ret = ret + " sem.valueName " + operatorString(node.get(Constants.OP_OPERATOR)) + " \"" + valueName;
242     ret = ret + "\")})";
243     return ret;
244   }
245 
246   private static String simpleIdPropertyPart(JsonNode node, String variable) {
247     String property = node.get(Constants.OP_PROPERTY).textValue();
248     // for simple id
249     if (property.equals("id")) return neo4jIdPart(node, variable);
250     // for CollectionReferences
251     if (property.equals("referencedCollectionId")) return referencedCollectionNeo4jIdPart(node, variable);
252     // for DataObjectReferences
253     if (property.equals("referencedDataObjectId")) return referencedDataObjectNeo4jIdPart(node, variable);
254     // for FileReferences
255     if (property.equals("fileContainerId")) return fileContainerIdPart(node, variable);
256     // for StructuredDataReferences
257     if (property.equals("structuredDataContainerId")) return structuredDataContainerIdPart(node, variable);
258     // for TimeseriesReferences
259     if (property.equals("timeseriesContainerId")) return timeseriesContainerIdPart(node, variable);
260     // for neighborhoodIds
261     if (
262       property.equals("successorIds") ||
263       property.equals("predecessorIds") ||
264       property.equals("childrenIds") ||
265       property.equals("parentIds")
266     ) return neighborhoodIdsPart(node, variable);
267     return null;
268   }
269 
270   private static String byPart(JsonNode node, String variable) {
271     String ret = "(";
272     String by =
273       switch (node.get(Constants.OP_PROPERTY).textValue()) {
274         case "createdBy" -> "created_by";
275         case "updatedBy" -> "updated_by";
276         default -> "";
277       };
278     ret = ret + "EXISTS {MATCH (" + variable + ") - [:" + by + "] -> (u) WHERE toLower(u.username) ";
279     ret = ret + operatorString(node.get(Constants.OP_OPERATOR)) + " ";
280     ret = ret + node.get(Constants.OP_VALUE).toString().toLowerCase() + " ";
281     ret = ret + "})";
282     return ret;
283   }
284 
285   private static String neighborhoodIdsPart(JsonNode node, String variable) {
286     String operatorString = node.get(Constants.OP_OPERATOR).textValue();
287     String neighborhoodProperty = node.get(Constants.OP_PROPERTY).textValue();
288     String neighborhoodNeo4j =
289       switch (neighborhoodProperty) {
290         case "successorIds" -> "-[:has_successor]->";
291         case "predecessorIds" -> "<-[:has_successor]-";
292         case "childrenIds" -> "-[:has_child]->";
293         case "parentIds" -> "<-[:has_child]-";
294         default -> "";
295       };
296     if (operatorString.equals(Constants.JSON_CONTAINS)) return neighborhoodIdsContainsPart(
297       node,
298       variable,
299       neighborhoodNeo4j
300     );
301     if (operatorString.equals(Constants.JSON_IS_CONTAINED_IN)) return neighborhoodIdsIsContainedInPart(
302       node,
303       variable,
304       neighborhoodNeo4j
305     );
306     if (operatorString.equals(Constants.JSON_EQ)) return neighborhoodIdsEqualsPart(node, variable, neighborhoodNeo4j);
307     throw new ShepardParserException("illegal comparison operator " + operatorString);
308   }
309 
310   private static String neighborhoodIdsEqualsPart(JsonNode node, String variable, String neighborhoodNeo4j) {
311     return (
312       "((" +
313       neighborhoodIdsIsContainedInPart(node, variable, neighborhoodNeo4j) +
314       ") AND (" +
315       neighborhoodIdsContainsPart(node, variable, neighborhoodNeo4j) +
316       "))"
317     );
318   }
319 
320   private static String neighborhoodIdsIsContainedInPart(JsonNode node, String variable, String neighborhoodNeo4j) {
321     String ret = "(";
322     ArrayList<Long> successorIds = new ArrayList<Long>();
323     for (JsonNode longNode : node.get(Constants.OP_VALUE)) {
324       successorIds.add(longNode.longValue());
325     }
326     if (successorIds.size() == 0) return "(NOT EXISTS{MATCH (" + variable + ")" + neighborhoodNeo4j + "(neighborObj)})";
327     String arrayString = "[";
328     for (int i = 0; i < successorIds.size() - 1; i++) arrayString = arrayString + successorIds.get(i) + ",";
329     arrayString = arrayString + successorIds.get(successorIds.size() - 1) + "]";
330     ret =
331       ret +
332       "NOT EXISTS{MATCH (" +
333       variable +
334       ")" +
335       neighborhoodNeo4j +
336       "(neighborObj) WHERE (NOT id(neighborObj) IN " +
337       arrayString +
338       ")})";
339     return ret;
340   }
341 
342   private static String neighborhoodIdsContainsPart(JsonNode node, String variable, String neighborhoodNeo4j) {
343     String ret = "(";
344     ArrayList<Long> successorIds = new ArrayList<Long>();
345     for (JsonNode longNode : node.get(Constants.OP_VALUE)) {
346       successorIds.add(longNode.longValue());
347     }
348     if (successorIds.size() == 0) return "(1=1)";
349     for (int i = 0; i < successorIds.size() - 1; i++) {
350       ret =
351         ret +
352         "(EXISTS {MATCH (" +
353         variable +
354         ")" +
355         neighborhoodNeo4j +
356         "(neighborObj) WHERE id(neighborObj)=" +
357         successorIds.get(i) +
358         "}) AND ";
359     }
360     ret =
361       ret +
362       "(EXISTS {MATCH (" +
363       variable +
364       ")" +
365       neighborhoodNeo4j +
366       "(neighborObj) WHERE id(neighborObj)=" +
367       successorIds.get(successorIds.size() - 1) +
368       "}))";
369     return ret;
370   }
371 
372   private static String atPart(JsonNode node, String variable) {
373     String ret = "(";
374     String property = node.get(Constants.OP_PROPERTY).textValue();
375     if (property.equals("id")) ret = ret + "id(" + variable + ") ";
376     else ret = ret + variable + "." + property + " ";
377     ret = ret + operatorString(node.get(Constants.OP_OPERATOR)) + " ";
378     ret = ret + valuePart(node).toLowerCase();
379     ret = ret + ")";
380     return ret;
381   }
382 
383   private static String iRIPart(JsonNode node, String variable) {
384     String ret = "(";
385     String iriType = node.get(Constants.OP_PROPERTY).textValue();
386     ret = ret + "EXISTS {MATCH (" + variable + ") - [] -> (sem:SemanticAnnotation) WHERE (sem." + iriType + " ";
387     ret = ret + operatorString(node.get(Constants.OP_OPERATOR)) + " ";
388     ret = ret + node.get(Constants.OP_VALUE);
389     ret = ret + ")})";
390     return ret;
391   }
392 
393   private static String timeseriesContainerIdPart(JsonNode node, String variable) {
394     return containerIdPart(node, variable, "TimeseriesContainer");
395   }
396 
397   private static String structuredDataContainerIdPart(JsonNode node, String variable) {
398     return containerIdPart(node, variable, "StructuredDataContainer");
399   }
400 
401   private static String fileContainerIdPart(JsonNode node, String variable) {
402     return containerIdPart(node, variable, "FileContainer");
403   }
404 
405   private static String containerIdPart(JsonNode node, String variable, String containerType) {
406     String ret = "(";
407     ret =
408       ret +
409       "EXISTS {MATCH (" +
410       variable +
411       ")-[:" +
412       Constants.IS_IN_CONTAINER +
413       "]->(refCon:" +
414       containerType +
415       ") WHERE id(refCon) ";
416     ret = ret + operatorString(node.get(Constants.OP_OPERATOR)) + " ";
417     ret = ret + node.get(Constants.OP_VALUE) + " ";
418     ret = ret + "})";
419     return ret;
420   }
421 
422   private static String referencedDataObjectNeo4jIdPart(JsonNode node, String variable) {
423     String ret = "(";
424     ret = ret + "EXISTS {MATCH (" + variable + ")-[:" + Constants.POINTS_TO + "]->(refDo:DataObject) WHERE id(refDo) ";
425     ret = ret + operatorString(node.get(Constants.OP_OPERATOR)) + " ";
426     ret = ret + node.get(Constants.OP_VALUE) + " ";
427     ret = ret + "})";
428     return ret;
429   }
430 
431   private static String referencedCollectionNeo4jIdPart(JsonNode node, String variable) {
432     String ret = "(";
433     ret =
434       ret + "EXISTS {MATCH (" + variable + ")-[:" + Constants.POINTS_TO + "]->(refCol:Collection) WHERE id(refCol) ";
435     ret = ret + operatorString(node.get(Constants.OP_OPERATOR)) + " ";
436     ret = ret + node.get(Constants.OP_VALUE) + " ";
437     ret = ret + "})";
438     return ret;
439   }
440 
441   private static String neo4jIdPart(JsonNode node, String variable) {
442     String ret = "(";
443     ret = ret + "id(" + variable + ") ";
444     ret = ret + operatorString(node.get(Constants.OP_OPERATOR)) + " ";
445     ret = ret + valuePart(node);
446     ret = ret + ")";
447     return ret;
448   }
449 
450   private static String operatorString(JsonNode node) {
451     String operator = node.textValue();
452     return switch (operator) {
453       case Constants.JSON_EQ -> "=";
454       case Constants.JSON_CONTAINS -> "contains";
455       case Constants.JSON_GT -> ">";
456       case Constants.JSON_LT -> "<";
457       case Constants.JSON_GE -> ">=";
458       case Constants.JSON_LE -> "<=";
459       case Constants.JSON_IN -> "IN";
460       case Constants.JSON_NE -> "<>";
461       case Constants.JSON_REGMATCH -> "=~";
462       default -> throw new ShepardParserException("unknown comparison operator " + operator);
463     };
464   }
465 
466   private static String collectionDataObjectMatchPartWithoutVersion() {
467     String ret =
468       "MATCH (" +
469       Constants.COLLECTION_IN_QUERY +
470       ":Collection)-[:has_dataobject]->(" +
471       Constants.DATAOBJECT_IN_QUERY +
472       ":DataObject)";
473     return ret;
474   }
475 
476   private static String collectionNeo4jIdWherePart(Long collectionId) {
477     String ret = "(id(" + Constants.COLLECTION_IN_QUERY + ") = " + collectionId + ")";
478     return ret;
479   }
480 
481   private static String notDeletedPart(String variable) {
482     String ret = "(" + variable + ".deleted = FALSE)";
483     return ret;
484   }
485 
486   private static String collectionDataObjectNeo4jIdWherePart(Long collectionId, Long dataObjectId) {
487     String ret =
488       "(id(" +
489       Constants.COLLECTION_IN_QUERY +
490       ") = " +
491       collectionId +
492       " AND id(" +
493       Constants.DATAOBJECT_IN_QUERY +
494       ") = " +
495       dataObjectId +
496       ")";
497     return ret;
498   }
499 
500   private static String collectionDataObjectTraversalNeo4jIdWherePart(Long collectionId, Long dataObjectId) {
501     String ret = "(id(" + Constants.COLLECTION_IN_QUERY + ") = " + collectionId + " AND id(d) = " + dataObjectId + ")";
502     return ret;
503   }
504 
505   private static String basicReferenceMatchPartWithoutVersion() {
506     String ret =
507       "MATCH (" +
508       Constants.COLLECTION_IN_QUERY +
509       ":Collection)-[:has_dataobject]->(" +
510       Constants.DATAOBJECT_IN_QUERY +
511       ":DataObject)-[:has_reference]->(" +
512       Constants.REFERENCE_IN_QUERY +
513       ":BasicReference)";
514     return ret;
515   }
516 
517   public static String collectionSelectionQueryWithNeo4jId(
518     String searchBodyQuery,
519     String userName,
520     SortingHelper sortOrder
521   ) {
522     String ret = "MATCH (" + Constants.COLLECTION_IN_QUERY + ":Collection)";
523     ret = ret + " WHERE ";
524     ret = ret + getNeo4jWithNeo4jIdString(searchBodyQuery, Constants.COLLECTION_IN_QUERY);
525     ret = ret + " AND ";
526     ret = ret + notDeletedPart(Constants.COLLECTION_IN_QUERY);
527     ret = ret + " AND ";
528     ret = ret + readableByPart(userName);
529     if (sortOrder.hasOrderByAttribute()) {
530       ret +=
531         " " +
532         CypherQueryHelper.getOrderByPart(
533           Constants.COLLECTION_IN_QUERY,
534           sortOrder.getOrderByAttribute(),
535           sortOrder.getOrderDesc()
536         );
537     }
538     return ret;
539   }
540 
541   public static String containerSelectionQueryWithNeo4jId(
542     String JSONQuery,
543     ContainerType containerType,
544     SortingHelper sortOrder,
545     String userName
546   ) {
547     String ret = "MATCH (" + containerType.getTypeAlias() + ":" + containerType.getTypeName() + ")";
548     ret = ret + " WHERE ";
549     ret = ret + getNeo4jWithNeo4jIdString(JSONQuery, containerType.getTypeAlias());
550     ret = ret + " AND ";
551     ret = ret + notDeletedPart(containerType.getTypeAlias());
552     ret = ret + " AND ";
553     ret = ret + CypherQueryHelper.getReadableByQuery(containerType.getTypeAlias(), userName);
554     if (sortOrder.hasOrderByAttribute()) {
555       ret +=
556         " " +
557         CypherQueryHelper.getOrderByPart(
558           containerType.getTypeAlias(),
559           sortOrder.getOrderByAttribute(),
560           sortOrder.getOrderDesc()
561         );
562     }
563     return ret;
564   }
565 
566   public static String collectionDataObjectSelectionQueryWithNeo4jId(
567     Long collectionId,
568     String searchBodyQuery,
569     String username
570   ) {
571     String ret = "";
572     ret = ret + collectionDataObjectMatchPartWithoutVersion();
573     ret = ret + " WHERE ";
574     ret = ret + getNeo4jWithNeo4jIdString(searchBodyQuery, Constants.DATAOBJECT_IN_QUERY);
575     ret = ret + " AND ";
576     ret = ret + collectionNeo4jIdWherePart(collectionId);
577     ret = ret + " AND ";
578     ret = ret + notDeletedPart(Constants.DATAOBJECT_IN_QUERY);
579     ret = ret + " AND ";
580     ret = ret + readableByPart(username);
581     return ret;
582   }
583 
584   public static String collectionDataObjectDataObjectSelectionQueryWithNeo4jId(
585     SearchScope scope,
586     TraversalRules traversalRule,
587     String searchBodyQuery,
588     String username
589   ) {
590     String ret = "";
591     ret = ret + collectionDataObjectDataObjectMatchPartWithoutVersion(traversalRule);
592     ret = ret + " WHERE ";
593     ret = ret + getNeo4jWithNeo4jIdString(searchBodyQuery, Constants.DATAOBJECT_IN_QUERY);
594     ret = ret + " AND ";
595     ret = ret + collectionDataObjectTraversalNeo4jIdWherePart(scope.getCollectionId(), scope.getDataObjectId());
596     ret = ret + " AND ";
597     ret = ret + notDeletedPart(Constants.DATAOBJECT_IN_QUERY);
598     ret = ret + " AND ";
599     ret = ret + readableByPart(username);
600     return ret;
601   }
602 
603   public static String collectionDataObjectDataObjectSelectionQueryWithNeo4jId(
604     SearchScope scope,
605     String searchBodyQuery,
606     String username
607   ) {
608     String ret = "";
609     ret = ret + collectionDataObjectMatchPartWithoutVersion();
610     ret = ret + " WHERE ";
611     ret = ret + getNeo4jWithNeo4jIdString(searchBodyQuery, Constants.DATAOBJECT_IN_QUERY);
612     ret = ret + " AND ";
613     ret = ret + collectionDataObjectNeo4jIdWherePart(scope.getCollectionId(), scope.getDataObjectId());
614     ret = ret + " AND ";
615     ret = ret + notDeletedPart(Constants.DATAOBJECT_IN_QUERY);
616     ret = ret + " AND ";
617     ret = ret + readableByPart(username);
618     return ret;
619   }
620 
621   public static String dataObjectSelectionQueryWithNeo4jId(String searchBodyQuery, String username) {
622     String ret = "";
623     ret = ret + collectionDataObjectMatchPartWithoutVersion();
624     ret = ret + " WHERE ";
625     ret = ret + getNeo4jWithNeo4jIdString(searchBodyQuery, Constants.DATAOBJECT_IN_QUERY);
626     ret = ret + " AND ";
627     ret = ret + notDeletedPart(Constants.DATAOBJECT_IN_QUERY);
628     ret = ret + " AND ";
629     ret = ret + readableByPart(username);
630     return ret;
631   }
632 
633   public static String basicReferenceSelectionQueryWithNeo4jId(String searchBodyQuery, String username) {
634     String ret = "";
635     ret = ret + basicReferenceMatchPartWithoutVersion();
636     ret = ret + " WHERE ";
637     ret = ret + getNeo4jWithNeo4jIdString(searchBodyQuery, Constants.REFERENCE_IN_QUERY);
638     ret = ret + " AND ";
639     ret = ret + notDeletedPart(Constants.REFERENCE_IN_QUERY);
640     ret = ret + " AND ";
641     ret = ret + readableByPart(username);
642     return ret;
643   }
644 
645   public static String collectionBasicReferenceSelectionQueryWithNeo4jId(
646     String searchBodyQuery,
647     Long collectionId,
648     String username
649   ) {
650     String ret = "";
651     ret = ret + basicReferenceMatchPartWithoutVersion();
652     ret = ret + " WHERE ";
653     ret = ret + getNeo4jWithNeo4jIdString(searchBodyQuery, Constants.REFERENCE_IN_QUERY);
654     ret = ret + " AND ";
655     ret = ret + collectionNeo4jIdWherePart(collectionId);
656     ret = ret + " AND ";
657     ret = ret + notDeletedPart(Constants.REFERENCE_IN_QUERY);
658     ret = ret + " AND ";
659     ret = ret + readableByPart(username);
660     return ret;
661   }
662 
663   public static String collectionDataObjectReferenceSelectionQueryWithNeo4jId(
664     SearchScope scope,
665     String searchBodyQuery,
666     String username
667   ) {
668     String ret = "";
669     ret = ret + basicReferenceMatchPartWithoutVersion();
670     ret = ret + " WHERE ";
671     ret = ret + getNeo4jWithNeo4jIdString(searchBodyQuery, Constants.REFERENCE_IN_QUERY);
672     ret = ret + " AND ";
673     ret = ret + collectionDataObjectNeo4jIdWherePart(scope.getCollectionId(), scope.getDataObjectId());
674     ret = ret + " AND ";
675     ret = ret + notDeletedPart(Constants.REFERENCE_IN_QUERY);
676     ret = ret + " AND ";
677     ret = ret + readableByPart(username);
678     return ret;
679   }
680 
681   public static String collectionDataObjectBasicReferenceSelectionQueryWithNeo4jId(
682     SearchScope scope,
683     TraversalRules traversalRule,
684     String searchBodyQuery,
685     String username
686   ) {
687     String ret = "";
688     ret = ret + collectionDataObjectBasicReferenceMatchPartWithoutVersion(traversalRule);
689     ret = ret + " WHERE ";
690     ret = ret + getNeo4jWithNeo4jIdString(searchBodyQuery, Constants.REFERENCE_IN_QUERY);
691     ret = ret + " AND ";
692     ret = ret + collectionDataObjectTraversalNeo4jIdWherePart(scope.getCollectionId(), scope.getDataObjectId());
693     ret = ret + " AND ";
694     ret = ret + notDeletedPart(Constants.REFERENCE_IN_QUERY);
695     ret = ret + " AND ";
696     ret = ret + readableByPart(username);
697     return ret;
698   }
699 
700   private static String collectionDataObjectDataObjectMatchPartWithoutVersion(TraversalRules traversalRule) {
701     String ret =
702       switch (traversalRule) {
703         case children -> "MATCH (" +
704         Constants.COLLECTION_IN_QUERY +
705         ":Collection)-[:has_dataobject]->(d:DataObject)-[:has_child*0..]->(" +
706         Constants.DATAOBJECT_IN_QUERY +
707         ":DataObject)";
708         case parents -> "MATCH (" +
709         Constants.COLLECTION_IN_QUERY +
710         ":Collection)-[:has_dataobject]->(d:DataObject)<-[:has_child*0..]-(" +
711         Constants.DATAOBJECT_IN_QUERY +
712         ":DataObject)";
713         case successors -> "MATCH (" +
714         Constants.COLLECTION_IN_QUERY +
715         ":Collection)-[:has_dataobject]->(d:DataObject)-[:has_successor*0..]->(" +
716         Constants.DATAOBJECT_IN_QUERY +
717         ":DataObject)";
718         case predecessors -> "MATCH (" +
719         Constants.COLLECTION_IN_QUERY +
720         ":Collection)-[:has_dataobject]->(d:DataObject)<-[:has_successor*0..]-(" +
721         Constants.DATAOBJECT_IN_QUERY +
722         ":DataObject)";
723         default -> "";
724       };
725     return ret;
726   }
727 
728   private static String collectionDataObjectBasicReferenceMatchPartWithoutVersion(TraversalRules traversalRule) {
729     String ret =
730       switch (traversalRule) {
731         case children -> "MATCH (" +
732         Constants.COLLECTION_IN_QUERY +
733         ":Collection)-[:has_dataobject]->(d:DataObject)-[:has_child*0..]->(" +
734         Constants.DATAOBJECT_IN_QUERY +
735         ":DataObject)-[:has_reference]->(" +
736         Constants.REFERENCE_IN_QUERY +
737         ":BasicReference)";
738         case parents -> "MATCH (" +
739         Constants.COLLECTION_IN_QUERY +
740         ":Collection)-[:has_dataobject]->(d:DataObject)<-[:has_child*0..]-(" +
741         Constants.DATAOBJECT_IN_QUERY +
742         ":DataObject)-[:has_reference]->(" +
743         Constants.REFERENCE_IN_QUERY +
744         ":BasicReference)";
745         case successors -> "MATCH (" +
746         Constants.COLLECTION_IN_QUERY +
747         ":Collection)-[:has_dataobject]->(d:DataObject)-[:has_successor*0..]->(" +
748         Constants.DATAOBJECT_IN_QUERY +
749         ":DataObject)-[:has_reference]->(" +
750         Constants.REFERENCE_IN_QUERY +
751         ":BasicReference)";
752         case predecessors -> "MATCH (" +
753         Constants.COLLECTION_IN_QUERY +
754         ":Collection)-[:has_dataobject]->(d:DataObject)<-[:has_successor*0..]-(" +
755         Constants.DATAOBJECT_IN_QUERY +
756         ":DataObject)-[:has_reference]->(" +
757         Constants.REFERENCE_IN_QUERY +
758         ":BasicReference)";
759         default -> "";
760       };
761     return ret;
762   }
763 
764   private static String readableByPart(String username) {
765     String variable = Constants.COLLECTION_IN_QUERY;
766     return CypherQueryHelper.getReadableByQuery(variable, username);
767   }
768 
769   public static String userSelectionQuery(String query) {
770     String ret = "";
771     ret = ret + userMatchPart();
772     ret = ret + " WHERE ";
773     ret = ret + getNeo4jWithNeo4jIdString(query, Constants.USER_IN_QUERY);
774     return ret;
775   }
776 
777   private static String userMatchPart() {
778     String ret = "";
779     ret = ret + "MATCH (" + Constants.USER_IN_QUERY + ":User)";
780     return ret;
781   }
782 
783   public static String userGroupSelectionQuery(String query) {
784     String ret = "";
785     ret = ret + userGroupMatchPart();
786     ret = ret + " WHERE ";
787     ret = ret + getNeo4jWithNeo4jIdString(query, Constants.USERGROUP_IN_QUERY);
788     return ret;
789   }
790 
791   private static String userGroupMatchPart() {
792     String ret = "";
793     ret = ret + "MATCH (" + Constants.USERGROUP_IN_QUERY + ":UserGroup)";
794     return ret;
795   }
796 }