View Javadoc
1   package de.dlr.shepard.common.neo4j.daos;
2   
3   import de.dlr.shepard.common.neo4j.NeoConnector;
4   import de.dlr.shepard.common.util.Constants;
5   import de.dlr.shepard.common.util.CypherQueryHelper;
6   import de.dlr.shepard.common.util.CypherQueryHelper.Neighborhood;
7   import de.dlr.shepard.common.util.TraversalRules;
8   import io.quarkus.logging.Log;
9   import java.util.Collection;
10  import java.util.Collections;
11  import java.util.HashMap;
12  import java.util.Map;
13  import java.util.stream.Stream;
14  import java.util.stream.StreamSupport;
15  import org.neo4j.ogm.cypher.Filter;
16  import org.neo4j.ogm.model.Result;
17  import org.neo4j.ogm.session.Session;
18  
19  public abstract class GenericDAO<T> {
20  
21    protected static final int DEPTH_ENTITY = 1;
22  
23    protected Session session = null;
24  
25    protected GenericDAO() {
26      session = NeoConnector.getInstance().getNeo4jSession();
27    }
28  
29    /**
30     * Find all instances of a certain entity T
31     *
32     * @return an Iterable over the found entities
33     */
34    public Collection<T> findAll() {
35      Collection<T> iter = session.loadAll(getEntityType(), DEPTH_ENTITY);
36      return iter;
37    }
38  
39    /**
40     * Find the entity with the given id
41     *
42     * @param id The given id
43     * @return The entity with the given id or null
44     */
45    public T findByNeo4jId(long id) {
46      return session.load(getEntityType(), id, DEPTH_ENTITY);
47    }
48  
49    /**
50     * Find the entity with the given id without any related entities
51     *
52     * @param id The given id
53     * @return The entity with the given id or null
54     */
55    public T findLightByNeo4jId(long id) {
56      return session.load(getEntityType(), id, 0);
57    }
58  
59    /**
60     * Find entities matching the given filter
61     *
62     * @param filter The given filter
63     * @return An iterable with the found entities
64     */
65    public Collection<T> findMatching(Filter filter) {
66      return session.loadAll(getEntityType(), filter, DEPTH_ENTITY);
67    }
68  
69    /**
70     * Delete an entity
71     *
72     * Caution: Regarding the official documentation ids will be reused by neo4j.
73     * @see <a href='https://neo4j.com/docs/ogm-manual/current/reference/#reference:annotating-entities:entity-identifier'>See official documentation for further information on the id issue</a>
74     *
75     * @param id The entity to be deleted
76     * @return Whether the deletion was successful or not
77     */
78    public boolean deleteByNeo4jId(long id) {
79      T entity = session.load(getEntityType(), id);
80      if (entity != null) {
81        session.delete(entity);
82        return true;
83      }
84      return false;
85    }
86  
87    /**
88     * Save an entity and all related entities
89     *
90     * @param entity The entity to be saved
91     * @return the saved entity
92     */
93    public T createOrUpdate(T entity) {
94      session.save(entity, DEPTH_ENTITY);
95      return entity;
96    }
97  
98    public void clearSession() {
99      session.clear();
100   }
101 
102   /**
103    * CAUTION: The query runs against the database and is not checked. You can do
104    * anything you want.
105    *
106    * @param query     The query
107    * @param paramsMap Map of parameters
108    * @return Iterable The result
109    */
110   public Iterable<T> findByQuery(String query, Map<String, Object> paramsMap) {
111     Log.debugf("Run query: %s", query);
112     StringBuilder str = new StringBuilder();
113     for (var entry : paramsMap.entrySet()) {
114       str.append("(" + entry.getKey() + ", " + entry.getValue() + "), ");
115     }
116     Log.debugf("queryParams: %s", str.toString());
117     Iterable<T> iter = session.query(getEntityType(), query, paramsMap);
118     return iter;
119   }
120 
121   public Stream<T> findByQuery(String query) {
122     var results = session.query(getEntityType(), query, Collections.emptyMap());
123     return StreamSupport.stream(results.spliterator(), false);
124   }
125 
126   public boolean runQuery(String query, Map<String, Object> paramsMap) {
127     Log.debugf("Run query: %s", query);
128     Result result = session.query(query, paramsMap);
129     return result.queryStatistics().containsUpdates();
130   }
131 
132   public String getSearchForReachableReferencesByShepardIdQuery(
133     TraversalRules traversalRule,
134     long collectionShepardId,
135     long startShepardId,
136     String userName
137   ) {
138     String ret = "MATCH path = (col:Collection)-[:has_dataobject]->";
139     ret += getTraversalRulesPath(traversalRule);
140     ret += "-[hr:has_reference]->(r:" + getEntityType().getSimpleName() + ")";
141     ret += getWithPart("ns", "ret");
142     ret +=
143       " WHERE d." +
144       Constants.SHEPARD_ID +
145       " = " +
146       startShepardId +
147       " AND col." +
148       Constants.SHEPARD_ID +
149       " = " +
150       collectionShepardId;
151     ret += getReturnPart("ns", "ret", "col", userName);
152     return ret;
153   }
154 
155   public String getSearchForReachableReferencesByNeo4jIdQuery(
156     TraversalRules traversalRule,
157     long collectionShepardId,
158     long startShepardId,
159     String userName
160   ) {
161     String ret = "MATCH path = (col:Collection)-[:has_dataobject]->";
162     ret += getTraversalRulesPath(traversalRule);
163     ret += "-[hr:has_reference]->(r:" + getEntityType().getSimpleName() + ")";
164     ret += getWithPart("ns", "ret");
165     ret += " WHERE id(d) = " + startShepardId + " AND id(col) = " + collectionShepardId;
166     ret += getReturnPart("ns", "ret", "col", userName);
167     return ret;
168   }
169 
170   private String getTraversalRulesPath(TraversalRules traversalRule) {
171     if (traversalRule == null) return "(d:DataObject)";
172     return switch (traversalRule) {
173       case children -> "(d:DataObject)-[:has_child*0..]->(e:DataObject)";
174       case parents -> "(d:DataObject)<-[:has_child*0..]-(e:DataObject)";
175       case successors -> "(d:DataObject)-[:has_successor*0..]->(e:DataObject)";
176       case predecessors -> "(d:DataObject)<-[:has_successor*0..]-(e:DataObject)";
177       default -> "(d:DataObject)";
178     };
179   }
180 
181   public String getSearchForReachableReferencesQuery(long collectionId, String userName) {
182     String ret = "MATCH path = (col:Collection)-[:has_dataobject]->(do:DataObject)";
183     ret += "-[hr:has_reference]->(r:" + getEntityType().getSimpleName() + ")";
184     ret += getWithPart("ns", "ret");
185     ret += " WHERE id(col) = " + collectionId;
186     ret += getReturnPart("ns", "ret", "col", userName);
187     return ret;
188   }
189 
190   public String getSearchForReachableReferencesByShepardIdQuery(long collectionShepardId, String userName) {
191     String ret = "MATCH path = (col:Collection)-[:has_dataobject]->(do:DataObject)";
192     ret += "-[hr:has_reference]->(r:" + getEntityType().getSimpleName() + ")";
193     ret += getWithPart("ns", "ret");
194     ret += " WHERE col." + Constants.SHEPARD_ID + " = " + collectionShepardId;
195     ret += getReturnPart("ns", "ret", "col", userName);
196     return ret;
197   }
198 
199   public String getSearchForReachableReferencesQuery(long collectionId, long startId, String userName) {
200     String ret = "MATCH path = (col:Collection)-[:has_dataobject]->(d:DataObject)";
201     ret += "-[hr:has_reference]->(r:" + getEntityType().getSimpleName() + ")";
202     ret += getWithPart("ns", "ret");
203     ret += " WHERE id(d) = " + startId + " AND id(col) = " + collectionId;
204     ret += getReturnPart("ns", "ret", "col", userName);
205     return ret;
206   }
207 
208   public String getSearchForReachableReferencesByShepardIdQuery(
209     long collectionShepardId,
210     long startShepardId,
211     String userName
212   ) {
213     String ret = "MATCH path = (col:Collection)-[:has_dataobject]->(d:DataObject)";
214     ret += "-[hr:has_reference]->(r:" + getEntityType().getSimpleName() + ")";
215     ret += getWithPart("ns", "ret");
216     ret +=
217       " WHERE d." +
218       Constants.SHEPARD_ID +
219       " = " +
220       startShepardId +
221       " AND col." +
222       Constants.SHEPARD_ID +
223       " = " +
224       collectionShepardId;
225     ret += getReturnPart("ns", "ret", "col", userName);
226     return ret;
227   }
228 
229   private String getWithPart(String nodesVar, String retVar) {
230     return " WITH nodes(path) as " + nodesVar + ", r as " + retVar;
231   }
232 
233   private String getReturnPart(String nodesVar, String retVar, String collectionVar, String username) {
234     String ret = "";
235     ret += " AND NONE(node IN " + nodesVar + " WHERE (node.deleted = TRUE))";
236     ret += " AND " + CypherQueryHelper.getReadableByQuery(collectionVar, username);
237     ret += " " + CypherQueryHelper.getReturnPart(retVar, Neighborhood.EVERYTHING);
238     return ret;
239   }
240 
241   public void deleteRelation(long fromId, long toId, String fromType, String toType, String relationName) {
242     String query =
243       "MATCH (a:%s {shepardId: %s})-[r:%s]->(b:%s {shepardId: %s}) DELETE r;".formatted(
244           fromType,
245           fromId,
246           relationName,
247           toType,
248           toId
249         );
250     session.query(query, new HashMap<String, String>());
251   }
252 
253   public abstract Class<T> getEntityType();
254 }