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