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