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