View Javadoc
1   package de.dlr.shepard.context.collection.daos;
2   
3   import de.dlr.shepard.auth.users.entities.User;
4   import de.dlr.shepard.common.util.Constants;
5   import de.dlr.shepard.common.util.CypherQueryHelper;
6   import de.dlr.shepard.common.util.QueryParamHelper;
7   import de.dlr.shepard.context.collection.entities.DataObject;
8   import de.dlr.shepard.context.version.daos.VersionableEntityDAO;
9   import jakarta.enterprise.context.RequestScoped;
10  import java.util.ArrayList;
11  import java.util.Collections;
12  import java.util.Date;
13  import java.util.HashMap;
14  import java.util.List;
15  import java.util.Map;
16  import java.util.UUID;
17  import java.util.stream.StreamSupport;
18  import org.neo4j.cypherdsl.core.Cypher;
19  import org.neo4j.cypherdsl.core.Node;
20  
21  @RequestScoped
22  public class DataObjectDAO extends VersionableEntityDAO<DataObject> {
23  
24    @Override
25    public Class<DataObject> getEntityType() {
26      return DataObject.class;
27    }
28  
29    /**
30     * Searches the database for DataObjects.
31     *
32     * @param collectionId identifies the Collection
33     * @param params       encapsulates possible parameters
34     * @return a List of DataObjects
35     */
36    public List<DataObject> findByCollectionByNeo4jIds(long collectionId, QueryParamHelper params) {
37      Map<String, Object> paramsMap = new HashMap<>();
38      paramsMap.put("name", params.getName());
39      if (params.hasPagination()) {
40        paramsMap.put("offset", params.getPagination().getOffset());
41        paramsMap.put("size", params.getPagination().getSize());
42      }
43      String match =
44        "MATCH (c:Collection)-[hdo:has_dataobject]->" +
45        CypherQueryHelper.getObjectPart("d", "DataObject", params.hasName());
46      String where = " WHERE ID(c)=" + collectionId;
47  
48      if (params.hasParentId()) {
49        if (params.getParentId() == -1) {
50          where += " AND NOT EXISTS((d)<-[:has_child]-(:DataObject {deleted: FALSE}))";
51        } else {
52          match += "<-[:has_child]-(parent:DataObject {deleted: FALSE})";
53          where += " AND ID(parent)=" + params.getParentId();
54        }
55      }
56  
57      if (params.hasPredecessorId()) {
58        if (params.getPredecessorId() == -1) {
59          where += " AND NOT EXISTS((d)<-[:has_successor]-(:DataObject {deleted: FALSE}))";
60        } else {
61          match += "<-[:has_successor]-(predecessor:DataObject {deleted: FALSE})";
62          where += " AND ID(predecessor)=" + params.getPredecessorId();
63        }
64      }
65      if (params.hasSuccessorId()) {
66        if (params.getSuccessorId() == -1) {
67          where += " AND NOT EXISTS((d)-[:has_successor]->(:DataObject {deleted: FALSE}))";
68        } else {
69          match += "-[:has_successor]->(successor:DataObject {deleted: FALSE})";
70          where += " AND ID(successor)=" + params.getSuccessorId();
71        }
72      }
73  
74      String query = match + where + " WITH d";
75      if (params.hasOrderByAttribute()) {
76        query += " " + CypherQueryHelper.getOrderByPart("d", params.getOrderByAttribute(), params.getOrderDesc());
77      }
78      if (params.hasPagination()) {
79        query += " " + CypherQueryHelper.getPaginationPart();
80      }
81      query += " " + CypherQueryHelper.getReturnPart("d");
82      var result = new ArrayList<DataObject>();
83      for (var obj : findByQuery(query, paramsMap)) {
84        List<DataObject> parentList = obj.getParent() != null ? List.of(obj.getParent()) : Collections.emptyList();
85        if (
86          matchCollection(obj, collectionId) &&
87          matchName(obj, params.getName()) &&
88          matchRelated(parentList, params.getParentId()) &&
89          matchRelated(obj.getSuccessors(), params.getSuccessorId()) &&
90          matchRelated(obj.getPredecessors(), params.getPredecessorId())
91        ) {
92          result.add(obj);
93        }
94      }
95  
96      return result;
97    }
98  
99    public List<DataObject> findByCollectionByShepardIds(
100     long collectionShepardId,
101     QueryParamHelper paramsWithShepardIds
102   ) {
103     return findByCollectionByShepardIds(collectionShepardId, paramsWithShepardIds, null);
104   }
105 
106   /**
107    * Deletes the has_successor relation between the predecessor and the successor dataobjects in neo4j
108    */
109   public void deleteHasSuccessorRelation(long predecessorShepardId, long successorShepardId) {
110     deleteRelation(
111       predecessorShepardId,
112       successorShepardId,
113       getEntityType().getSimpleName(),
114       getEntityType().getSimpleName(),
115       Constants.HAS_SUCCESSOR
116     );
117   }
118 
119   /**
120    * Deletes the has_child relation between the parent and the child in neo4j
121    */
122   public void deleteHasChildRelation(long parentShepardId, long childShepardId) {
123     deleteRelation(
124       parentShepardId,
125       childShepardId,
126       getEntityType().getSimpleName(),
127       getEntityType().getSimpleName(),
128       Constants.HAS_CHILD
129     );
130   }
131 
132   /**
133    * Deletes all attributes of a DataObject in neo4j
134    * @param dataObject  identifies the DataObject
135    */
136   public void deleteAllAttributes(DataObject dataObject) {
137     if (dataObject.getAttributes() == null || dataObject.getAttributes().isEmpty()) return;
138     Node d = Cypher.node("DataObject");
139     String st = Cypher.match(d)
140       .where(d.internalId().isEqualTo(Cypher.literalOf(dataObject.getId())))
141       .remove(dataObject.getAttributes().keySet().stream().map(key -> d.property("attributes||" + key)).toList())
142       .build()
143       .getCypher();
144     session.query(st, new HashMap<String, String>());
145   }
146 
147   /**
148    * Searches the database for DataObjects.
149    *
150    * @param collectionShepardId  identifies the Collection
151    * @param paramsWithShepardIds encapsulates possible parameters
152    * @return a List of DataObjects
153    */
154   public List<DataObject> findByCollectionByShepardIds(
155     long collectionShepardId,
156     QueryParamHelper paramsWithShepardIds,
157     UUID versionUID
158   ) {
159     Map<String, Object> paramsMap = new HashMap<>();
160     paramsMap.put("name", paramsWithShepardIds.getName());
161     if (paramsWithShepardIds.hasPagination()) {
162       paramsMap.put("offset", paramsWithShepardIds.getPagination().getOffset());
163       paramsMap.put("size", paramsWithShepardIds.getPagination().getSize());
164     }
165     String match =
166       "MATCH (v:Version)<-[:has_version]-(c:Collection)-[hdo:has_dataobject]->" +
167       CypherQueryHelper.getObjectPart("d", "DataObject", paramsWithShepardIds.hasName());
168     String where = " WHERE c." + Constants.SHEPARD_ID + "=" + collectionShepardId + " AND ";
169     //search in HEAD version
170     if (versionUID == null) where = where + CypherQueryHelper.getVersionHeadPart("v");
171     //search in version given by versionUID
172     else where = where + CypherQueryHelper.getVersionPart("v", versionUID);
173     if (paramsWithShepardIds.hasParentId()) {
174       if (paramsWithShepardIds.getParentId() == -1) {
175         where += " AND NOT EXISTS((d)<-[:has_child]-(:DataObject {deleted: FALSE}))";
176       } else {
177         match +=
178           "<-[:has_child]-(parent:DataObject {deleted: FALSE, " +
179           Constants.SHEPARD_ID +
180           ": " +
181           paramsWithShepardIds.getParentId() +
182           "})";
183       }
184     }
185 
186     if (paramsWithShepardIds.hasPredecessorId()) {
187       if (paramsWithShepardIds.getPredecessorId() == -1) {
188         where += " AND NOT EXISTS((d)<-[:has_successor]-(:DataObject {deleted: FALSE}))";
189       } else {
190         match +=
191           "<-[:has_successor]-(predecessor:DataObject {deleted: FALSE, " +
192           Constants.SHEPARD_ID +
193           ": " +
194           paramsWithShepardIds.getPredecessorId() +
195           "})";
196       }
197     }
198     if (paramsWithShepardIds.hasSuccessorId()) {
199       if (paramsWithShepardIds.getSuccessorId() == -1) {
200         where += " AND NOT EXISTS((d)-[:has_successor]->(:DataObject {deleted: FALSE}))";
201       } else {
202         match +=
203           "-[:has_successor]->(successor:DataObject {deleted: FALSE, " +
204           Constants.SHEPARD_ID +
205           ": " +
206           paramsWithShepardIds.getSuccessorId() +
207           "})";
208       }
209     }
210 
211     String query = match + where + " WITH d";
212     if (paramsWithShepardIds.hasOrderByAttribute()) {
213       query +=
214         " " +
215         CypherQueryHelper.getOrderByPart(
216           "d",
217           paramsWithShepardIds.getOrderByAttribute(),
218           paramsWithShepardIds.getOrderDesc()
219         );
220     }
221     if (paramsWithShepardIds.hasPagination()) {
222       query += " " + CypherQueryHelper.getPaginationPart();
223     }
224     query += " " + CypherQueryHelper.getReturnPart("d");
225     var result = new ArrayList<DataObject>();
226     for (var obj : findByQuery(query, paramsMap)) {
227       List<DataObject> parentList = obj.getParent() != null ? List.of(obj.getParent()) : Collections.emptyList();
228       if (
229         matchCollectionByShepardId(obj, collectionShepardId) &&
230         matchName(obj, paramsWithShepardIds.getName()) &&
231         matchRelatedByShepardId(parentList, paramsWithShepardIds.getParentId()) &&
232         matchRelatedByShepardId(obj.getSuccessors(), paramsWithShepardIds.getSuccessorId()) &&
233         matchRelatedByShepardId(obj.getPredecessors(), paramsWithShepardIds.getPredecessorId())
234       ) {
235         result.add(obj);
236       }
237     }
238 
239     return result;
240   }
241 
242   /**
243    * Delete dataObject and all related references
244    *
245    * @param id        identifies the dataObject
246    * @param updatedBy current date
247    * @param updatedAt current user
248    * @return whether the deletion was successful or not
249    */
250   public boolean deleteDataObjectByNeo4jId(long id, User updatedBy, Date updatedAt) {
251     var dataObject = findByNeo4jId(id);
252     dataObject.setUpdatedBy(updatedBy);
253     dataObject.setUpdatedAt(updatedAt);
254     dataObject.setDeleted(true);
255     createOrUpdate(dataObject);
256     String query = String.format(
257       "MATCH (d:DataObject) WHERE ID(d) = %d OPTIONAL MATCH (d)-[:has_reference]->(r:BasicReference) " +
258       "FOREACH (n in [d,r] | SET n.deleted = true)",
259       id
260     );
261     var result = runQuery(query, Collections.emptyMap());
262     return result;
263   }
264 
265   /**
266    * Delete dataObject and all related references
267    *
268    * @param shepardId identifies the dataObject
269    * @param updatedBy current date
270    * @param updatedAt current user
271    * @return whether the deletion was successful or not
272    */
273   public boolean deleteDataObjectByShepardId(long shepardId, User updatedBy, Date updatedAt) {
274     DataObject dataObject = findByShepardId(shepardId);
275     dataObject.setUpdatedBy(updatedBy);
276     dataObject.setUpdatedAt(updatedAt);
277     dataObject.setDeleted(true);
278     createOrUpdate(dataObject);
279     String query = String.format(
280       "MATCH (d:DataObject) WHERE ID(d) = %d OPTIONAL MATCH (d)-[:has_reference]->(r:BasicReference) " +
281       "FOREACH (n in [d,r] | SET n.deleted = true)",
282       dataObject.getId()
283     );
284     var result = runQuery(query, Collections.emptyMap());
285     return result;
286   }
287 
288   private boolean matchName(DataObject obj, String name) {
289     return name == null || name.equalsIgnoreCase(obj.getName());
290   }
291 
292   private boolean matchRelated(List<DataObject> related, Long id) {
293     if (id == null) {
294       return true;
295     } else if (id == -1) {
296       // return true if there is no related object or all objects are deleted
297       return related.stream().allMatch(DataObject::isDeleted);
298     } else {
299       // return true if at least one related object that is not deleted matches the ID
300       return related.stream().anyMatch(d -> !d.isDeleted() && d.getId().equals(id));
301     }
302   }
303 
304   private boolean matchRelatedByShepardId(List<DataObject> related, Long shepardId) {
305     if (shepardId == null) {
306       return true;
307     } else if (shepardId == -1) {
308       // return true if there is no related object or all objects are deleted
309       return related.stream().allMatch(DataObject::isDeleted);
310     } else {
311       // return true if at least one related object that is not deleted matches the ID
312       return related.stream().anyMatch(d -> !d.isDeleted() && d.getShepardId().equals(shepardId));
313     }
314   }
315 
316   private boolean matchCollection(DataObject obj, long collectionId) {
317     return obj.getCollection() != null && obj.getCollection().getId().equals(collectionId);
318   }
319 
320   private boolean matchCollectionByShepardId(DataObject obj, long collectionShepardId) {
321     return obj.getCollection() != null && obj.getCollection().getShepardId().equals(collectionShepardId);
322   }
323 
324   public List<DataObject> getDataObjectsByQuery(String query) {
325     var queryResult = findByQuery(query, Collections.emptyMap());
326     List<DataObject> ret = StreamSupport.stream(queryResult.spliterator(), false).toList();
327     return ret;
328   }
329 }