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