View Javadoc
1   package de.dlr.shepard.context.collection.services;
2   
3   import de.dlr.shepard.auth.permission.services.PermissionsService;
4   import de.dlr.shepard.auth.security.AuthenticationContext;
5   import de.dlr.shepard.auth.users.entities.User;
6   import de.dlr.shepard.auth.users.services.UserService;
7   import de.dlr.shepard.common.exceptions.InvalidAuthException;
8   import de.dlr.shepard.common.exceptions.InvalidBodyException;
9   import de.dlr.shepard.common.exceptions.InvalidPathException;
10  import de.dlr.shepard.common.exceptions.InvalidRequestException;
11  import de.dlr.shepard.common.util.DateHelper;
12  import de.dlr.shepard.common.util.QueryParamHelper;
13  import de.dlr.shepard.context.collection.daos.DataObjectDAO;
14  import de.dlr.shepard.context.collection.entities.Collection;
15  import de.dlr.shepard.context.collection.entities.DataObject;
16  import de.dlr.shepard.context.collection.io.DataObjectIO;
17  import de.dlr.shepard.context.references.dataobject.daos.DataObjectReferenceDAO;
18  import de.dlr.shepard.context.references.dataobject.entities.DataObjectReference;
19  import de.dlr.shepard.context.version.services.VersionService;
20  import io.quarkus.logging.Log;
21  import jakarta.enterprise.context.RequestScoped;
22  import jakarta.inject.Inject;
23  import java.util.ArrayList;
24  import java.util.Date;
25  import java.util.HashSet;
26  import java.util.List;
27  import java.util.UUID;
28  
29  @RequestScoped
30  public class DataObjectService {
31  
32    @Inject
33    DataObjectDAO dataObjectDAO;
34  
35    @Inject
36    DataObjectReferenceDAO dataObjectReferenceDAO;
37  
38    @Inject
39    UserService userService;
40  
41    @Inject
42    DateHelper dateHelper;
43  
44    @Inject
45    VersionService versionService;
46  
47    @Inject
48    CollectionService collectionService;
49  
50    @Inject
51    PermissionsService permissionsService;
52  
53    @Inject
54    AuthenticationContext authenticationContext;
55  
56    /**
57     * Creates a DataObject
58     *
59     * @param collectionShepardId identifies the Collection
60     * @param dataObject          to be stored
61     * @return the stored DataObject with the auto generated id
62     * @throws InvalidPathException if collection with collectionShepardId does not
63     *                              exist
64     */
65    public DataObject createDataObject(long collectionShepardId, DataObjectIO dataObject) {
66      Collection collection = collectionService.getCollection(collectionShepardId);
67      collectionService.assertIsAllowedToEditCollection(collectionShepardId);
68  
69      User user = userService.getCurrentUser();
70      DataObject parent = findRelatedDataObject(collection.getShepardId(), dataObject.getParentId(), null);
71      List<DataObject> predecessors = findRelatedDataObjects(
72        collection.getShepardId(),
73        dataObject.getPredecessorIds(),
74        null
75      );
76      DataObject toCreate = new DataObject();
77      toCreate.setAttributes(dataObject.getAttributes());
78      toCreate.setDescription(dataObject.getDescription());
79      toCreate.setName(dataObject.getName());
80      toCreate.setCollection(collection);
81      toCreate.setParent(parent);
82      toCreate.setPredecessors(predecessors);
83      toCreate.setCreatedAt(dateHelper.getDate());
84      toCreate.setCreatedBy(user);
85      DataObject created = dataObjectDAO.createOrUpdate(toCreate);
86      created.setShepardId(created.getId());
87      created = dataObjectDAO.createOrUpdate(created);
88      versionService.attachToVersionOfVersionableEntityAndReturnVersion(collectionShepardId, created.getShepardId());
89      return created;
90    }
91  
92    /**
93     * Get DataObject
94     *
95     * @param shepardId identifies the searched dataObject
96     * @return the DataObject with the given id
97     * @throws InvalidPathException if the DataObject cannot be found
98     * @throws InvalidAuthException if user does not have read permissions on the
99     *                              data object's collection
100    */
101   public DataObject getDataObject(long shepardId) {
102     return getDataObject(shepardId, null);
103   }
104 
105   /**
106    * Get DataObject
107    *
108    * @param shepardId  identifies the searched dataObject
109    * @param versionUID the dataobject's version UUID
110    * @return an Optional containing the DataObject with the given id
111    * @throws InvalidPathException if DataObject (with version UUID) cannot be
112    *                              found
113    * @throws InvalidAuthException if user does not have read permissions on the
114    *                              data object's collection
115    */
116   public DataObject getDataObject(long shepardId, UUID versionUID) {
117     DataObject ret;
118     String errorMsg;
119     if (versionUID == null) {
120       ret = dataObjectDAO.findByShepardId(shepardId);
121       errorMsg = String.format("DataObject with id %s is null or deleted", shepardId);
122     } else {
123       ret = dataObjectDAO.findByShepardId(shepardId, versionUID);
124       errorMsg = String.format("DataObject with id %s and versionUID %s is null or deleted", shepardId, versionUID);
125     }
126     if (ret == null || ret.isDeleted()) {
127       Log.error(errorMsg);
128       throw new InvalidPathException("ID ERROR - " + errorMsg);
129     }
130 
131     collectionService.assertIsAllowedToReadCollection(ret.getCollection().getShepardId());
132     cutDeleted(ret);
133 
134     HashSet<Long> incomingReferencesIdList = new HashSet<Long>();
135     for (DataObjectReference reference : ret.getIncoming()) incomingReferencesIdList.add(reference.getId());
136     List<DataObjectReference> completeIncomingReferences = new ArrayList<DataObjectReference>();
137     for (Long id : incomingReferencesIdList) completeIncomingReferences.add(dataObjectReferenceDAO.findByNeo4jId(id));
138 
139     HashSet<Long> childrenIdList = new HashSet<Long>();
140     for (DataObject child : ret.getChildren()) childrenIdList.add(child.getId());
141     List<DataObject> completeChildren = new ArrayList<DataObject>();
142     for (Long id : childrenIdList) completeChildren.add(dataObjectDAO.findByNeo4jId(id));
143 
144     HashSet<Long> predecessorsIdList = new HashSet<Long>();
145     for (DataObject predecessor : ret.getPredecessors()) predecessorsIdList.add(predecessor.getId());
146     List<DataObject> completePredecessors = new ArrayList<DataObject>();
147     for (Long id : predecessorsIdList) completePredecessors.add(dataObjectDAO.findByNeo4jId(id));
148 
149     HashSet<Long> successorsIdList = new HashSet<Long>();
150     for (DataObject successor : ret.getSuccessors()) successorsIdList.add(successor.getId());
151     List<DataObject> completeSuccessors = new ArrayList<DataObject>();
152     for (Long id : successorsIdList) completeSuccessors.add(dataObjectDAO.findByNeo4jId(id));
153 
154     ret.setChildren(completeChildren);
155     ret.setIncoming(completeIncomingReferences);
156     ret.setPredecessors(completePredecessors);
157     ret.setSuccessors(completeSuccessors);
158     if (ret.getParent() != null) ret.setParent(dataObjectDAO.findByNeo4jId(ret.getParent().getId()));
159     return ret;
160   }
161 
162   /**
163    * Get DataObject
164    *
165    * @param collectionShepardId collection's shepardId
166    * @param shepardId           identifies the searched dataObject
167    * @return the DataObject with the given id
168    * @throws InvalidPathException if dataobject or collection cannot be found or
169    *                              the dataobject does not match the collection
170    * @throws InvalidAuthException if user does not have read permissions on the
171    *                              collection
172    */
173   public DataObject getDataObject(long collectionShepardId, long shepardId) {
174     return getDataObject(collectionShepardId, shepardId, null);
175   }
176 
177   /**
178    * Get DataObject
179    *
180    * @param collectionShepardId collection's shepardId
181    * @param shepardId           identifies the searched dataObject
182    * @param versionUID          the DataObject's version UUID
183    * @return the DataObject with the given id
184    * @throws InvalidPathException if DataObject or collection cannot be found or
185    *                              the DataObject does not match the collection
186    * @throws InvalidAuthException if user does not have read permissions on the
187    *                              collection
188    */
189   public DataObject getDataObject(long shepardCollectionId, long shepardId, UUID versionUID) {
190     collectionService.getCollection(shepardCollectionId);
191 
192     DataObject dataObject;
193     try {
194       // This may throw a 403 if the data object is in a different collection for
195       // which the user does not have permissions -> handle that exception
196       // specifically
197       dataObject = getDataObject(shepardId, versionUID);
198     } catch (InvalidAuthException ex) {
199       throw new InvalidPathException("ID ERROR - There is no association between collection and dataObject");
200     }
201 
202     if (!dataObject.getCollection().getShepardId().equals(shepardCollectionId)) {
203       throw new InvalidPathException("ID ERROR - There is no association between collection and dataObject");
204     }
205     return dataObject;
206   }
207 
208   /**
209    * Searches the database for DataObjects.
210    *
211    * @param collectionShepardId  identifies the collection
212    * @param paramsWithShepardIds encapsulates possible parameters
213    * @param versionUID           identifies the version
214    * @return a List of DataObjects
215    * @throws InvalidPathException if collection with collectionShepardId does not
216    *                              exist
217    * @throws InvalidAuthException if user does not have read permissions on the
218    *                              collection
219    */
220   public List<DataObject> getAllDataObjectsByShepardIds(
221     long collectionShepardId,
222     QueryParamHelper paramsWithShepardIds,
223     UUID versionUID
224   ) {
225     collectionService.getCollection(collectionShepardId, versionUID);
226 
227     var unfiltered = dataObjectDAO.findByCollectionByShepardIds(collectionShepardId, paramsWithShepardIds, versionUID);
228     var dataObjects = unfiltered.stream().map(this::cutDeleted).toList();
229     return dataObjects;
230   }
231 
232   /**
233    * Updates a DataObject with new attributes. Hereby only not null attributes
234    * will replace the old attributes.
235    *
236    * @param collectionShepardId ShepardId of the collection the dataobject is
237    *                            assigned to
238    * @param dataObjectShepardId Identifies the dataObject
239    * @param dataObject          DataObject entity for updating.
240    * @return updated DataObject.
241    * @throws InvalidPathException if dataobject cannot be found or collection with
242    *                              collectionShepardId does not exist
243    * @throws InvalidAuthException if user does not have read or write permissions
244    *                              on the collection
245    */
246   public DataObject updateDataObject(long collectionShepardId, long dataObjectShepardId, DataObjectIO dataObject) {
247     DataObject old = getDataObject(collectionShepardId, dataObjectShepardId);
248     collectionService.assertIsAllowedToEditCollection(collectionShepardId);
249 
250     User user = userService.getCurrentUser();
251 
252     if (old.getParent() != null) {
253       dataObjectDAO.deleteHasChildRelation(old.getParent().getShepardId(), old.getShepardId());
254     }
255     if (old.getPredecessors() != null) {
256       old
257         .getPredecessors()
258         .forEach(predecessor -> {
259           dataObjectDAO.deleteHasSuccessorRelation(predecessor.getShepardId(), old.getShepardId());
260         });
261     }
262 
263     DataObject newParent = findRelatedDataObject(
264       old.getCollection().getShepardId(),
265       dataObject.getParentId(),
266       dataObjectShepardId
267     );
268     List<DataObject> newPredecessors = findRelatedDataObjects(
269       old.getCollection().getShepardId(),
270       dataObject.getPredecessorIds(),
271       dataObjectShepardId
272     );
273 
274     old.setShepardId(old.getShepardId());
275     old.setName(dataObject.getName());
276     old.setDescription(dataObject.getDescription());
277     old.setAttributes(dataObject.getAttributes());
278     old.setParent(newParent);
279     old.setPredecessors(newPredecessors);
280     old.setUpdatedAt(dateHelper.getDate());
281     old.setUpdatedBy(user);
282 
283     DataObject updated = dataObjectDAO.createOrUpdate(old);
284     cutDeleted(updated);
285     return updated;
286   }
287 
288   /**
289    * set the deleted flag for the DataObject
290    *
291    * @param collectionShepardId ShepardId of the collection the dataobject is
292    *                            assigned to
293    * @param dataObjectShepardId identifies the DataObject to be deleted
294    * @return a boolean to identify if the DataObject was successfully removed
295    * @throws InvalidPathException if dataobject cannot be found or collection with
296    *                              collectionShepardId does not exist
297    * @throws InvalidAuthException if user does not have read or write permissions
298    *                              on the collection
299    */
300   public void deleteDataObject(long collectionShepardId, long dataObjectShepardId) {
301     getDataObject(collectionShepardId, dataObjectShepardId);
302     collectionService.assertIsAllowedToEditCollection(collectionShepardId);
303 
304     Date date = dateHelper.getDate();
305     User user = userService.getCurrentUser();
306 
307     if (!dataObjectDAO.deleteDataObjectByShepardId(dataObjectShepardId, user, date)) {
308       throw new InvalidRequestException(
309         String.format("Could not delete DataObject with ShepardId %s", dataObjectShepardId)
310       );
311     }
312   }
313 
314   private DataObject cutDeleted(DataObject dataObject) {
315     var incoming = dataObject.getIncoming().stream().filter(i -> !i.isDeleted()).toList();
316     dataObject.setIncoming(incoming);
317     if (dataObject.getParent() != null && dataObject.getParent().isDeleted()) {
318       dataObject.setParent(null);
319     }
320     var children = dataObject.getChildren().stream().filter(s -> !s.isDeleted()).toList();
321     dataObject.setChildren(children);
322     var predecessors = dataObject.getPredecessors().stream().filter(s -> !s.isDeleted()).toList();
323     dataObject.setPredecessors(predecessors);
324     var successors = dataObject.getSuccessors().stream().filter(s -> !s.isDeleted()).toList();
325     dataObject.setSuccessors(successors);
326     var references = dataObject.getReferences().stream().filter(ref -> !ref.isDeleted()).toList();
327     dataObject.setReferences(references);
328     return dataObject;
329   }
330 
331   private List<DataObject> findRelatedDataObjects(
332     long collectionShepardId,
333     long[] referencedShepardIds,
334     Long dataObjectShepardId
335   ) {
336     if (referencedShepardIds == null) return new ArrayList<>();
337 
338     var result = new ArrayList<DataObject>(referencedShepardIds.length);
339     /*
340      * TODO: seems to be inefficient since this loops generates referencedIds.length
341      * calls to Neo4j this could possibly be packed into one query (or in chunks of
342      * queries in case of a large referencedIds array)
343      */
344     for (var shepardId : referencedShepardIds) {
345       result.add(findRelatedDataObject(collectionShepardId, shepardId, dataObjectShepardId));
346     }
347     return result;
348   }
349 
350   private DataObject findRelatedDataObject(
351     long collectionShepardId,
352     Long referencedShepardId,
353     Long dataObjectShepardId
354   ) {
355     if (referencedShepardId == null) return null;
356     else if (referencedShepardId.equals(dataObjectShepardId)) throw new InvalidBodyException(
357       "Self references are not allowed."
358     );
359 
360     var dataObject = dataObjectDAO.findByShepardId(referencedShepardId);
361     if (dataObject == null || dataObject.isDeleted()) throw new InvalidBodyException(
362       String.format("The DataObject with id %d could not be found.", referencedShepardId)
363     );
364 
365     // Prevent cross collection references
366     if (!dataObject.getCollection().getShepardId().equals(collectionShepardId)) throw new InvalidBodyException(
367       "Related data objects must belong to the same collection as the new data object"
368     );
369 
370     return dataObject;
371   }
372 
373   /**
374    * Only needed for fixing session problems in unit tests
375    */
376   public void clearSession() {
377     dataObjectDAO.clearSession();
378   }
379 }