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