View Javadoc
1   package de.dlr.shepard.common.util;
2   
3   import de.dlr.shepard.common.configuration.feature.toggles.VersioningFeatureToggle;
4   import de.dlr.shepard.common.neo4j.endpoints.OrderByAttribute;
5   import de.dlr.shepard.common.search.endpoints.BasicContainerAttributes;
6   import java.util.List;
7   import java.util.UUID;
8   import java.util.stream.Collectors;
9   
10  public class CypherQueryHelper {
11  
12    public enum Neighborhood {
13      EVERYTHING,
14      OUTGOING,
15      ESSENTIAL,
16    }
17  
18    private CypherQueryHelper() {}
19  
20    public static String getObjectPartWithVersion(String variable, String type, boolean hasName, String versionVariable) {
21      String ret = getObjectPart(variable, type, hasName);
22      ret = ret + "-[:has_version]->(" + versionVariable + ":Version)";
23      return ret;
24    }
25  
26    public static String getObjectPart(String variable, String type, boolean hasName) {
27      if (hasName) return getObjectPartWithName(variable, type);
28      else return getObjectPartWithoutName(variable, type);
29    }
30  
31    private static String getObjectPartWithName(String variable, String type) {
32      var namePart = "{ name : $name, deleted: FALSE }";
33      var result = "(%s:%s %s)".formatted(variable, type, namePart);
34      return result;
35    }
36  
37    private static String getObjectPartWithoutName(String variable, String type) {
38      var namePart = "{ deleted: FALSE }";
39      var result = "(%s:%s %s)".formatted(variable, type, namePart);
40      return result;
41    }
42  
43    public static String getPaginationPart() {
44      return "SKIP $offset LIMIT $size";
45    }
46  
47    public static String getPaginationPart(PaginationHelper paginationParams) {
48      return "SKIP %d LIMIT %d".formatted(paginationParams.getOffset(), paginationParams.getSize());
49    }
50  
51    public static String getReturnPart(String entity) {
52      return getReturnPart(entity, Neighborhood.EVERYTHING, 1);
53    }
54  
55    public static String getReturnPart(String entity, int depth) {
56      return getReturnPart(entity, Neighborhood.EVERYTHING, depth);
57    }
58  
59    public static String getReturnPart(String entity, Neighborhood neighborhood) {
60      return getReturnPart(entity, neighborhood, 1);
61    }
62  
63    public static String getReturnPart(String entity, Neighborhood neighborhood, PaginationHelper pagination) {
64      return getReturnPart(entity, neighborhood, 1, pagination);
65    }
66  
67    public static String getReturnCountPart(String entity, Neighborhood neighborhood) {
68      return (getNeighborhoodPart(entity, neighborhood, 1) + " RETURN " + "COUNT(%s)".formatted(entity));
69    }
70  
71    public static String getReturnPart(String entity, Neighborhood neighborhood, int depth) {
72      return (
73        getNeighborhoodPart(entity, neighborhood, depth) +
74        " RETURN " +
75        "%s, nodes(path), relationships(path)".formatted(entity)
76      );
77    }
78  
79    public static String getReturnPart(String entity, Neighborhood neighborhood, int depth, PaginationHelper pagination) {
80      return (
81        getNeighborhoodPart(entity, neighborhood, depth) +
82        (pagination != null ? " " + CypherQueryHelper.getPaginationPart(pagination) : "") +
83        " RETURN " +
84        "%s, nodes(path), relationships(path)".formatted(entity)
85      );
86    }
87  
88    private static String getNeighborhoodPart(String entity, Neighborhood neighborhood, int depth) {
89      // Clamp the depth between 1 and 3 nodes
90      depth = Math.max(1, Math.min(3, depth));
91      String match =
92        switch (neighborhood) {
93          case EVERYTHING -> "path=(%s)-[*0..%d]-(n) WHERE n.deleted = FALSE OR n.deleted IS NULL";
94          case OUTGOING -> "path=(%s)-[*0..%d]->(n) WHERE n.deleted = FALSE OR n.deleted IS NULL";
95          case ESSENTIAL -> "path=(%s)-[*0..%d]->(n) WHERE n:Permission OR n:User";
96        };
97      return "MATCH " + match.formatted(entity, depth);
98    }
99  
100   public static String getReturnPartLight(String entity) {
101     return "RETURN " + entity;
102   }
103 
104   public static String getOrderByPart(String variable, OrderByAttribute orderByAttribute, Boolean orderDesc) {
105     String ret;
106     boolean isString = orderByAttribute.isString();
107     if (!isString) ret = "ORDER BY " + variable + "." + orderByAttribute;
108     else if (
109       orderByAttribute instanceof BasicContainerAttributes attributes && attributes == BasicContainerAttributes.type
110     ) ret = "ORDER BY LABELS(" + variable + ")";
111     else ret = "ORDER BY toLower(" + variable + "." + orderByAttribute + ")";
112     if (orderByAttribute.toString() == "id") ret = "ORDER BY id(" + variable + ")";
113     if (orderDesc != null && orderDesc) ret = ret + " DESC";
114     return ret;
115   }
116 
117   public static String getShepardIdPart(String variable, long shepardId) {
118     return variable + "." + Constants.SHEPARD_ID + " = " + shepardId;
119   }
120 
121   public static String getShepardIdsPart(String variable, List<Long> shepardIds) {
122     String commaSeparatedIds = shepardIds.stream().map(String::valueOf).collect(Collectors.joining(","));
123     return variable + "." + Constants.SHEPARD_ID + " in [" + commaSeparatedIds + "]";
124   }
125 
126   public static String getReadableByQuery(String variable, String username) {
127     String ret =
128       """
129       (NOT exists((%s)-[:has_permissions]->(:Permissions)) \
130       OR exists((%s)-[:has_permissions]->(:Permissions)-[:readable_by|owned_by]->(:User { username: "%s" })) \
131       OR exists((%s)-[:has_permissions]->(:Permissions {permissionType: "Public"})) \
132       OR exists((%s)-[:has_permissions]->(:Permissions {permissionType: "PublicReadable"})) \
133       OR exists((%s)-[:has_permissions]->(:Permissions)-[:readable_by_group]->(:UserGroup)<-[:is_in_group]-(:User { username: "%s"})))""".formatted(
134           variable,
135           variable,
136           username,
137           variable,
138           variable,
139           variable,
140           username
141         );
142     return ret;
143   }
144 
145   public static String getVersionHeadPart(String variable) {
146     if (VersioningFeatureToggle.isEnabled()) {
147       return "(" + variable + ".isHEADVersion = true)";
148     }
149     return "(1=1)";
150   }
151 
152   public static String getVersionPart(String variable, UUID versionUID) {
153     return "(" + variable + ".uid = '" + versionUID + "')";
154   }
155 }