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.TimeseriesReferenceDAO;
15  import de.dlr.shepard.context.references.timeseriesreference.daos.TimeseriesTupleDAO;
16  import de.dlr.shepard.context.references.timeseriesreference.io.TimeseriesReferenceIO;
17  import de.dlr.shepard.context.references.timeseriesreference.model.TimeseriesReference;
18  import de.dlr.shepard.context.version.services.VersionService;
19  import de.dlr.shepard.data.timeseries.io.TimeseriesWithDataPoints;
20  import de.dlr.shepard.data.timeseries.model.TimeseriesContainer;
21  import de.dlr.shepard.data.timeseries.model.TimeseriesDataPointsQueryParams;
22  import de.dlr.shepard.data.timeseries.model.TimeseriesTuple;
23  import de.dlr.shepard.data.timeseries.model.enums.AggregateFunction;
24  import de.dlr.shepard.data.timeseries.model.enums.CsvFormat;
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    TimeseriesTupleDAO 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 timeseriesReferenceShepardId
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 timeseriesReferenceShepardId,
115     UUID versionUID
116   ) {
117     dataObjectService.getDataObject(collectionShepardId, dataObjectShepardId, versionUID);
118 
119     TimeseriesReference reference = timeseriesReferenceDAO.findByShepardId(timeseriesReferenceShepardId, versionUID);
120     if (reference == null || reference.isDeleted()) {
121       String errorMsg =
122         "ID ERROR - Timeseries Reference with id %s is null or deleted".formatted(timeseriesReferenceShepardId);
123       Log.error(errorMsg);
124       throw new InvalidPathException(errorMsg);
125     }
126 
127     if (reference.getDataObject() == null || !reference.getDataObject().getShepardId().equals(dataObjectShepardId)) {
128       String errorMsg = "ID ERROR - There is no association between dataObject and reference";
129       Log.error(errorMsg);
130       throw new InvalidPathException(errorMsg);
131     }
132 
133     return reference;
134   }
135 
136   /**
137    * Creates a new TimeseriesReference reference
138    *
139    * @param collectionShepardId
140    * @param dataObjectShepardId DataObject id for the reference to be created
141    * @param timeseriesReference Reference object
142    * @return TimeseriesReference
143    * @throws InvalidPathException if collection or dataobject specified by their Ids are null or deleted
144    * @throws InvalidAuthException if user has no permission to edit referencing collection or no read permissions on referenced container
145    * @throws InvalidRequestException if user provides a timeseries reference with a non-accessible container
146    */
147   @Override
148   public TimeseriesReference createReference(
149     long collectionShepardId,
150     long dataObjectShepardId,
151     TimeseriesReferenceIO timeseriesReference
152   ) {
153     DataObject dataObject = dataObjectService.getDataObject(collectionShepardId, dataObjectShepardId);
154     collectionService.assertIsAllowedToEditCollection(collectionShepardId);
155 
156     User user = userService.getCurrentUser();
157 
158     TimeseriesContainer container;
159     try {
160       container = timeseriesContainerService.getContainer(timeseriesReference.getTimeseriesContainerId());
161     } catch (InvalidPathException ex) {
162       Log.error(ex.getMessage());
163       throw new InvalidRequestException(ex.getMessage());
164     }
165 
166     // sanitize timeseries
167     timeseriesReference
168       .getTimeseries()
169       .forEach(timeseries -> TimeseriesValidator.assertTimeseriesPropertiesAreValid(timeseries));
170 
171     var toCreate = new TimeseriesReference();
172     toCreate.setCreatedAt(dateHelper.getDate());
173     toCreate.setCreatedBy(user);
174     toCreate.setDataObject(dataObject);
175     toCreate.setName(timeseriesReference.getName());
176     toCreate.setStart(timeseriesReference.getStart());
177     toCreate.setEnd(timeseriesReference.getEnd());
178     toCreate.setTimeseriesContainer(container);
179 
180     synchronized (TimeseriesReferenceService.class) {
181       for (var ts : timeseriesReference.getTimeseries()) {
182         var found = timeseriesDAO.find(ts);
183         toCreate.addTimeseries(found.orElse(ts));
184       }
185       TimeseriesReference created = timeseriesReferenceDAO.createOrUpdate(toCreate);
186       created.setShepardId(created.getId());
187       created = timeseriesReferenceDAO.createOrUpdate(created);
188       versionService.attachToVersionOfVersionableEntityAndReturnVersion(dataObject.getId(), created.getId());
189       return created;
190     }
191   }
192 
193   /**
194    * Deletes the Timeseries reference.
195    *
196    * @param collectionShepardId
197    * @param dataObjectShepardId
198    * @param timeseriesReferenceShepardId
199    * @throws InvalidPathException if collection or dataobject specified by their Ids are null or deleted
200    * @throws InvalidAuthException if user has no permissions to edit the collection, which the reference is assigned to
201    */
202   @Override
203   public void deleteReference(long collectionShepardId, long dataObjectShepardId, long timeseriesReferenceShepardId) {
204     TimeseriesReference timeseriesReference = getReference(
205       collectionShepardId,
206       dataObjectShepardId,
207       timeseriesReferenceShepardId,
208       null
209     );
210     collectionService.assertIsAllowedToEditCollection(collectionShepardId);
211 
212     User user = userService.getCurrentUser();
213     timeseriesReference.setDeleted(true);
214     timeseriesReference.setUpdatedAt(dateHelper.getDate());
215     timeseriesReference.setUpdatedBy(user);
216     timeseriesReferenceDAO.createOrUpdate(timeseriesReference);
217   }
218 
219   public List<TimeseriesWithDataPoints> getReferencedTimeseriesWithDataPointsList(
220     long collectionShepardId,
221     long dataObjectShepardId,
222     long timeseriesShepardId,
223     AggregateFunction function,
224     Long timeSliceNanoseconds,
225     FillOption fillOption,
226     Set<String> devicesFilterSet,
227     Set<String> locationsFilterSet,
228     Set<String> symbolicNameFilterSet,
229     Set<String> measurementFilterSet,
230     Set<String> fieldFilterSet
231   ) {
232     TimeseriesReference reference = getReference(collectionShepardId, dataObjectShepardId, timeseriesShepardId, null);
233 
234     if (reference.getTimeseriesContainer() == null || reference.getTimeseriesContainer().isDeleted()) {
235       String errorMsg =
236         "Referenced Timeseries Container from reference with id %s is null or has been deleted".formatted(
237             timeseriesShepardId
238           );
239       Log.error(errorMsg);
240       throw new NotFoundException(errorMsg);
241     }
242 
243     try {
244       // check that referenced container is actually accessible
245       timeseriesContainerService.getContainer(reference.getTimeseriesContainer().getId());
246     } catch (InvalidPathException ex) {
247       throw new NotFoundException(ex.getMessage());
248     }
249 
250     var timeseriesList = reference.getReferencedTimeseriesList().stream().toList();
251     var filteredTimeseriesList = timeseriesList
252       .stream()
253       .filter(timeseries ->
254         matchFilter(
255           timeseries,
256           devicesFilterSet,
257           locationsFilterSet,
258           symbolicNameFilterSet,
259           measurementFilterSet,
260           fieldFilterSet
261         )
262       )
263       .toList();
264     var containerId = reference.getTimeseriesContainer().getId();
265     TimeseriesDataPointsQueryParams queryParams = new TimeseriesDataPointsQueryParams(
266       reference.getStart(),
267       reference.getEnd(),
268       timeSliceNanoseconds,
269       fillOption,
270       function
271     );
272 
273     return timeseriesService.getManyTimeseriesWithDataPoints(containerId, filteredTimeseriesList, queryParams);
274   }
275 
276   public InputStream exportReferencedTimeseriesByShepardId(
277     long collectionShepardId,
278     long dataObjectShepardId,
279     long timeseriesReferenceShepardId,
280     AggregateFunction function,
281     Long timeSliceNanoseconds,
282     FillOption fillOption,
283     Set<String> devicesFilterSet,
284     Set<String> locationsFilterSet,
285     Set<String> symbolicNameFilterSet,
286     Set<String> measurementFilterSet,
287     Set<String> fieldFilterSet,
288     CsvFormat csvFormat
289   ) throws IOException {
290     TimeseriesReference reference = getReference(
291       collectionShepardId,
292       dataObjectShepardId,
293       timeseriesReferenceShepardId,
294       null
295     );
296 
297     if (reference.getTimeseriesContainer() == null || reference.getTimeseriesContainer().isDeleted()) {
298       String errorMsg =
299         "The referenced TimeseriesContainer is null or deleted for Reference with id %s".formatted(
300             timeseriesReferenceShepardId
301           );
302       Log.error(errorMsg);
303       throw new NotFoundException(errorMsg);
304     }
305 
306     try {
307       timeseriesContainerService.getContainer(reference.getTimeseriesContainer().getId());
308     } catch (InvalidPathException ex) {
309       throw new InvalidRequestException(ex.getMessage());
310     }
311 
312     var filteredTimeseriesList = reference
313       .getReferencedTimeseriesList()
314       .stream()
315       .filter(timeseries ->
316         matchFilter(
317           timeseries,
318           devicesFilterSet,
319           locationsFilterSet,
320           symbolicNameFilterSet,
321           measurementFilterSet,
322           fieldFilterSet
323         )
324       )
325       .toList();
326     var containerId = reference.getTimeseriesContainer().getId();
327     TimeseriesDataPointsQueryParams queryParams = new TimeseriesDataPointsQueryParams(
328       reference.getStart(),
329       reference.getEnd(),
330       timeSliceNanoseconds,
331       fillOption,
332       function
333     );
334 
335     return timeseriesCsvService.exportManyTimeseriesWithDataPointsToCsv(
336       containerId,
337       filteredTimeseriesList,
338       queryParams,
339       csvFormat
340     );
341   }
342 
343   public InputStream exportReferencedTimeseriesByShepardId(
344     long collectionShepardId,
345     long dataObjectShepardId,
346     long referenceId,
347     CsvFormat csvFormat
348   ) throws IOException {
349     return exportReferencedTimeseriesByShepardId(
350       collectionShepardId,
351       dataObjectShepardId,
352       referenceId,
353       null,
354       null,
355       null,
356       Collections.emptySet(),
357       Collections.emptySet(),
358       Collections.emptySet(),
359       Collections.emptySet(),
360       Collections.emptySet(),
361       csvFormat
362     );
363   }
364 
365   private boolean matchFilter(
366     TimeseriesTuple timeseries,
367     Set<String> device,
368     Set<String> location,
369     Set<String> symName,
370     Set<String> measurement,
371     Set<String> field
372   ) {
373     var deviceMatches = true;
374     var locationMatches = true;
375     var symbolicNameMatches = true;
376     var measurementMatches = true;
377     var fieldMatches = true;
378     if (!device.isEmpty()) {
379       deviceMatches = device.contains(timeseries.getDevice());
380     }
381     if (!location.isEmpty()) {
382       locationMatches = location.contains(timeseries.getLocation());
383     }
384     if (!symName.isEmpty()) {
385       symbolicNameMatches = symName.contains(timeseries.getSymbolicName());
386     }
387     if (!measurement.isEmpty()) {
388       measurementMatches = measurement.contains(timeseries.getMeasurement());
389     }
390     if (!field.isEmpty()) {
391       fieldMatches = field.contains(timeseries.getField());
392     }
393     return deviceMatches && locationMatches && symbolicNameMatches && measurementMatches && fieldMatches;
394   }
395 }