View Javadoc
1   package de.dlr.shepard.context.references.timeseriesreference.services;
2   
3   import de.dlr.shepard.auth.permission.services.PermissionsService;
4   import de.dlr.shepard.auth.users.entities.User;
5   import de.dlr.shepard.auth.users.services.UserService;
6   import de.dlr.shepard.common.exceptions.InvalidAuthException;
7   import de.dlr.shepard.common.exceptions.InvalidPathException;
8   import de.dlr.shepard.common.exceptions.InvalidRequestException;
9   import de.dlr.shepard.common.util.DateHelper;
10  import de.dlr.shepard.context.collection.entities.DataObject;
11  import de.dlr.shepard.context.collection.services.CollectionService;
12  import de.dlr.shepard.context.collection.services.DataObjectService;
13  import de.dlr.shepard.context.references.IReferenceService;
14  import de.dlr.shepard.context.references.timeseriesreference.daos.ReferencedTimeseriesNodeEntityDAO;
15  import de.dlr.shepard.context.references.timeseriesreference.daos.TimeseriesReferenceDAO;
16  import de.dlr.shepard.context.references.timeseriesreference.io.TimeseriesReferenceIO;
17  import de.dlr.shepard.context.references.timeseriesreference.model.ReferencedTimeseriesNodeEntity;
18  import de.dlr.shepard.context.references.timeseriesreference.model.TimeseriesReference;
19  import de.dlr.shepard.context.version.services.VersionService;
20  import de.dlr.shepard.data.timeseries.io.TimeseriesWithDataPoints;
21  import de.dlr.shepard.data.timeseries.model.Timeseries;
22  import de.dlr.shepard.data.timeseries.model.TimeseriesContainer;
23  import de.dlr.shepard.data.timeseries.model.TimeseriesDataPointsQueryParams;
24  import de.dlr.shepard.data.timeseries.model.enums.AggregateFunction;
25  import de.dlr.shepard.data.timeseries.model.enums.FillOption;
26  import de.dlr.shepard.data.timeseries.services.TimeseriesContainerService;
27  import de.dlr.shepard.data.timeseries.services.TimeseriesCsvService;
28  import de.dlr.shepard.data.timeseries.services.TimeseriesService;
29  import de.dlr.shepard.data.timeseries.utilities.TimeseriesValidator;
30  import io.quarkus.logging.Log;
31  import jakarta.enterprise.context.RequestScoped;
32  import jakarta.inject.Inject;
33  import jakarta.ws.rs.NotFoundException;
34  import java.io.IOException;
35  import java.io.InputStream;
36  import java.util.Collections;
37  import java.util.List;
38  import java.util.Set;
39  import java.util.UUID;
40  
41  @RequestScoped
42  public class TimeseriesReferenceService implements IReferenceService<TimeseriesReference, TimeseriesReferenceIO> {
43  
44    @Inject
45    TimeseriesReferenceDAO timeseriesReferenceDAO;
46  
47    @Inject
48    TimeseriesService timeseriesService;
49  
50    @Inject
51    TimeseriesCsvService timeseriesCsvService;
52  
53    @Inject
54    DataObjectService dataObjectService;
55  
56    @Inject
57    ReferencedTimeseriesNodeEntityDAO timeseriesDAO;
58  
59    @Inject
60    UserService userService;
61  
62    @Inject
63    CollectionService collectionService;
64  
65    @Inject
66    TimeseriesContainerService timeseriesContainerService;
67  
68    @Inject
69    VersionService versionService;
70  
71    @Inject
72    DateHelper dateHelper;
73  
74    @Inject
75    PermissionsService permissionsService;
76  
77    /**
78     * Gets TimeseriesReference list for a given dataobject.
79     *
80     * @param collectionShepardId
81     * @param dataObjectShepardId
82     * @param versionUID the version UUID
83     * @return List<TimeseriesReference>
84     * @throws InvalidPathException If collection or dataobject cannot be found, or no association between dataobject and collection exists
85     * @throws InvalidAuthException If user has no read permissions on collection or dataobject specified by request path
86     */
87    @Override
88    public List<TimeseriesReference> getAllReferencesByDataObjectId(
89      long collectionShepardId,
90      long dataObjectShepardId,
91      UUID versionUID
92    ) {
93      dataObjectService.getDataObject(collectionShepardId, dataObjectShepardId, versionUID);
94  
95      var references = timeseriesReferenceDAO.findByDataObjectShepardId(dataObjectShepardId);
96      return references;
97    }
98  
99    /**
100    * Gets TimeseriesReference by shepard id.
101    *
102    * @param collectionShepardId
103    * @param dataObjectShepardId
104    * @param shepardId
105    * @param versionUID the version UUID
106    * @return TimeseriesReference
107    * @throws InvalidPathException If reference with Id does not exist or is deleted, or if collection or dataObject Id of path is not valid
108    * @throws InvalidAuthException If user has no read permissions on collection or dataobject specified by request path
109    */
110   @Override
111   public TimeseriesReference getReference(
112     long collectionShepardId,
113     long dataObjectShepardId,
114     long shepardId,
115     UUID versionUID
116   ) {
117     dataObjectService.getDataObject(collectionShepardId, dataObjectShepardId, versionUID);
118 
119     TimeseriesReference reference = timeseriesReferenceDAO.findByShepardId(shepardId, versionUID);
120     if (reference == null || reference.isDeleted()) {
121       String errorMsg = String.format("ID ERROR - Timeseries Reference with id %s is null or deleted", shepardId);
122       Log.error(errorMsg);
123       throw new InvalidPathException(errorMsg);
124     }
125 
126     if (reference.getDataObject() == null || !reference.getDataObject().getShepardId().equals(dataObjectShepardId)) {
127       String errorMsg = "ID ERROR - There is no association between dataObject and reference";
128       Log.error(errorMsg);
129       throw new InvalidPathException(errorMsg);
130     }
131 
132     return reference;
133   }
134 
135   /**
136    * Creates a new TimeseriesReference reference
137    *
138    * @param collectionShepardId
139    * @param dataObjectShepardId DataObject id for the reference to be created
140    * @param timeseriesReference Reference object
141    * @return TimeseriesReference
142    * @throws InvalidPathException if collection or dataobject specified by their Ids are null or deleted
143    * @throws InvalidAuthException if user has no permission to edit referencing collection or no read permissions on referenced container
144    * @throws InvalidRequestException if user provides a timeseries reference with a non-accessible container
145    */
146   @Override
147   public TimeseriesReference createReference(
148     long collectionShepardId,
149     long dataObjectShepardId,
150     TimeseriesReferenceIO timeseriesReference
151   ) {
152     DataObject dataObject = dataObjectService.getDataObject(collectionShepardId, dataObjectShepardId);
153     collectionService.assertIsAllowedToEditCollection(collectionShepardId);
154 
155     User user = userService.getCurrentUser();
156 
157     TimeseriesContainer container;
158     try {
159       container = timeseriesContainerService.getContainer(timeseriesReference.getTimeseriesContainerId());
160     } catch (InvalidPathException ex) {
161       Log.error(ex.getMessage());
162       throw new InvalidRequestException(ex.getMessage());
163     }
164 
165     // sanitize timeseries
166     timeseriesReference
167       .getTimeseries()
168       .forEach(timeseries -> TimeseriesValidator.assertTimeseriesPropertiesAreValid(timeseries));
169 
170     var toCreate = new TimeseriesReference();
171     toCreate.setCreatedAt(dateHelper.getDate());
172     toCreate.setCreatedBy(user);
173     toCreate.setDataObject(dataObject);
174     toCreate.setName(timeseriesReference.getName());
175     toCreate.setStart(timeseriesReference.getStart());
176     toCreate.setEnd(timeseriesReference.getEnd());
177     toCreate.setTimeseriesContainer(container);
178 
179     for (var ts : timeseriesReference.getTimeseries()) {
180       var found = timeseriesDAO.find(
181         ts.getMeasurement(),
182         ts.getDevice(),
183         ts.getLocation(),
184         ts.getSymbolicName(),
185         ts.getField()
186       );
187       if (found != null) {
188         toCreate.addTimeseries(found);
189       } else {
190         toCreate.addTimeseries(new ReferencedTimeseriesNodeEntity(ts));
191       }
192     }
193     TimeseriesReference created = timeseriesReferenceDAO.createOrUpdate(toCreate);
194     created.setShepardId(created.getId());
195     created = timeseriesReferenceDAO.createOrUpdate(created);
196     versionService.attachToVersionOfVersionableEntityAndReturnVersion(dataObject.getId(), created.getId());
197     return created;
198   }
199 
200   /**
201    * Deletes the Timeseries reference.
202    *
203    * @param collectionShepardId
204    * @param dataObjectShepardId
205    * @param timeseriesReferenceShepardId
206    * @throws InvalidPathException if collection or dataobject specified by their Ids are null or deleted
207    * @throws InvalidAuthException if user has no permissions to edit the collection, which the reference is assigned to
208    */
209   @Override
210   public void deleteReference(long collectionShepardId, long dataObjectShepardId, long timeseriesReferenceShepardId) {
211     TimeseriesReference timeseriesReference = getReference(
212       collectionShepardId,
213       dataObjectShepardId,
214       timeseriesReferenceShepardId,
215       null
216     );
217     collectionService.assertIsAllowedToEditCollection(collectionShepardId);
218 
219     User user = userService.getCurrentUser();
220     timeseriesReference.setDeleted(true);
221     timeseriesReference.setUpdatedAt(dateHelper.getDate());
222     timeseriesReference.setUpdatedBy(user);
223     timeseriesReferenceDAO.createOrUpdate(timeseriesReference);
224   }
225 
226   public List<TimeseriesWithDataPoints> getReferencedTimeseriesWithDataPointsList(
227     long collectionShepardId,
228     long dataObjectShepardId,
229     long timeseriesShepardId,
230     AggregateFunction function,
231     Long timeSliceNanoseconds,
232     FillOption fillOption,
233     Set<String> devicesFilterSet,
234     Set<String> locationsFilterSet,
235     Set<String> symbolicNameFilterSet
236   ) {
237     TimeseriesReference reference = getReference(collectionShepardId, dataObjectShepardId, timeseriesShepardId, null);
238 
239     if (reference.getTimeseriesContainer() == null || reference.getTimeseriesContainer().isDeleted()) {
240       String errorMsg = String.format(
241         "Referenced Timeseries Container from reference with id %s is null or has been deleted",
242         timeseriesShepardId
243       );
244       Log.error(errorMsg);
245       throw new NotFoundException(errorMsg);
246     }
247 
248     try {
249       // check that referenced container is actually accessible
250       timeseriesContainerService.getContainer(reference.getTimeseriesContainer().getId());
251     } catch (InvalidPathException ex) {
252       throw new NotFoundException(ex.getMessage());
253     }
254 
255     var timeseriesList = reference.getReferencedTimeseriesList().stream().map(ts -> ts.toTimeseries()).toList();
256     var filteredTimeseriesList = timeseriesList
257       .stream()
258       .filter(timeseries -> matchFilter(timeseries, devicesFilterSet, locationsFilterSet, symbolicNameFilterSet))
259       .toList();
260     var containerId = reference.getTimeseriesContainer().getId();
261     TimeseriesDataPointsQueryParams queryParams = new TimeseriesDataPointsQueryParams(
262       reference.getStart(),
263       reference.getEnd(),
264       timeSliceNanoseconds,
265       fillOption,
266       function
267     );
268 
269     return timeseriesService.getManyTimeseriesWithDataPoints(containerId, filteredTimeseriesList, queryParams);
270   }
271 
272   public InputStream exportReferencedTimeseriesByShepardId(
273     long collectionShepardId,
274     long dataObjectShepardId,
275     long timeseriesShepardId,
276     AggregateFunction function,
277     Long timeSliceNanoseconds,
278     FillOption fillOption,
279     Set<String> devicesFilterSet,
280     Set<String> locationsFilterSet,
281     Set<String> symbolicNameFilterSet
282   ) throws IOException {
283     TimeseriesReference reference = getReference(collectionShepardId, dataObjectShepardId, timeseriesShepardId, null);
284 
285     if (reference.getTimeseriesContainer() == null || reference.getTimeseriesContainer().isDeleted()) {
286       String errorMsg = String.format(
287         "The referenced TimeseriesContainer is null or deleted for Reference with id %s",
288         timeseriesShepardId
289       );
290       Log.error(errorMsg);
291       throw new NotFoundException(errorMsg);
292     }
293 
294     try {
295       timeseriesContainerService.getContainer(reference.getTimeseriesContainer().getId());
296     } catch (InvalidPathException ex) {
297       throw new InvalidRequestException(ex.getMessage());
298     }
299 
300     var timeseriesList = reference.getReferencedTimeseriesList().stream().map(ts -> ts.toTimeseries()).toList();
301     var filteredTimeseriesList = timeseriesList
302       .stream()
303       .filter(timeseries -> matchFilter(timeseries, devicesFilterSet, locationsFilterSet, symbolicNameFilterSet))
304       .toList();
305     var containerId = reference.getTimeseriesContainer().getId();
306     TimeseriesDataPointsQueryParams queryParams = new TimeseriesDataPointsQueryParams(
307       reference.getStart(),
308       reference.getEnd(),
309       timeSliceNanoseconds,
310       fillOption,
311       function
312     );
313 
314     return timeseriesCsvService.exportManyTimeseriesWithDataPointsToCsv(
315       containerId,
316       filteredTimeseriesList,
317       queryParams
318     );
319   }
320 
321   public InputStream exportReferencedTimeseriesByShepardId(
322     long collectionShepardId,
323     long dataObjectShepardId,
324     long referenceId
325   ) throws IOException {
326     return exportReferencedTimeseriesByShepardId(
327       collectionShepardId,
328       dataObjectShepardId,
329       referenceId,
330       null,
331       null,
332       null,
333       Collections.emptySet(),
334       Collections.emptySet(),
335       Collections.emptySet()
336     );
337   }
338 
339   private boolean matchFilter(Timeseries timeseries, Set<String> device, Set<String> location, Set<String> symName) {
340     var deviceMatches = true;
341     var locationMatches = true;
342     var symbolicNameMatches = true;
343     if (!device.isEmpty()) {
344       deviceMatches = device.contains(timeseries.getDevice());
345     }
346     if (!location.isEmpty()) {
347       locationMatches = location.contains(timeseries.getLocation());
348     }
349     if (!symName.isEmpty()) {
350       symbolicNameMatches = symName.contains(timeseries.getSymbolicName());
351     }
352     return deviceMatches && locationMatches && symbolicNameMatches;
353   }
354 }