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.CsvFormat;
26  import de.dlr.shepard.data.timeseries.model.enums.FillOption;
27  import de.dlr.shepard.data.timeseries.services.TimeseriesContainerService;
28  import de.dlr.shepard.data.timeseries.services.TimeseriesCsvService;
29  import de.dlr.shepard.data.timeseries.services.TimeseriesService;
30  import de.dlr.shepard.data.timeseries.utilities.TimeseriesValidator;
31  import io.quarkus.logging.Log;
32  import jakarta.enterprise.context.RequestScoped;
33  import jakarta.inject.Inject;
34  import jakarta.ws.rs.NotFoundException;
35  import java.io.IOException;
36  import java.io.InputStream;
37  import java.util.Collections;
38  import java.util.List;
39  import java.util.Set;
40  import java.util.UUID;
41  
42  @RequestScoped
43  public class TimeseriesReferenceService implements IReferenceService<TimeseriesReference, TimeseriesReferenceIO> {
44  
45    @Inject
46    TimeseriesReferenceDAO timeseriesReferenceDAO;
47  
48    @Inject
49    TimeseriesService timeseriesService;
50  
51    @Inject
52    TimeseriesCsvService timeseriesCsvService;
53  
54    @Inject
55    DataObjectService dataObjectService;
56  
57    @Inject
58    ReferencedTimeseriesNodeEntityDAO timeseriesDAO;
59  
60    @Inject
61    UserService userService;
62  
63    @Inject
64    CollectionService collectionService;
65  
66    @Inject
67    TimeseriesContainerService timeseriesContainerService;
68  
69    @Inject
70    VersionService versionService;
71  
72    @Inject
73    DateHelper dateHelper;
74  
75    @Inject
76    PermissionsService permissionsService;
77  
78    /**
79     * Gets TimeseriesReference list for a given dataobject.
80     *
81     * @param collectionShepardId
82     * @param dataObjectShepardId
83     * @param versionUID the version UUID
84     * @return List<TimeseriesReference>
85     * @throws InvalidPathException If collection or dataobject cannot be found, or no association between dataobject and collection exists
86     * @throws InvalidAuthException If user has no read permissions on collection or dataobject specified by request path
87     */
88    @Override
89    public List<TimeseriesReference> getAllReferencesByDataObjectId(
90      long collectionShepardId,
91      long dataObjectShepardId,
92      UUID versionUID
93    ) {
94      dataObjectService.getDataObject(collectionShepardId, dataObjectShepardId, versionUID);
95  
96      var references = timeseriesReferenceDAO.findByDataObjectShepardId(dataObjectShepardId);
97      return references;
98    }
99  
100   /**
101    * Gets TimeseriesReference by shepard id.
102    *
103    * @param collectionShepardId
104    * @param dataObjectShepardId
105    * @param shepardId
106    * @param versionUID the version UUID
107    * @return TimeseriesReference
108    * @throws InvalidPathException If reference with Id does not exist or is deleted, or if collection or dataObject Id of path is not valid
109    * @throws InvalidAuthException If user has no read permissions on collection or dataobject specified by request path
110    */
111   @Override
112   public TimeseriesReference getReference(
113     long collectionShepardId,
114     long dataObjectShepardId,
115     long shepardId,
116     UUID versionUID
117   ) {
118     dataObjectService.getDataObject(collectionShepardId, dataObjectShepardId, versionUID);
119 
120     TimeseriesReference reference = timeseriesReferenceDAO.findByShepardId(shepardId, versionUID);
121     if (reference == null || reference.isDeleted()) {
122       String errorMsg = "ID ERROR - Timeseries Reference with id %s is null or deleted".formatted(shepardId);
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     for (var ts : timeseriesReference.getTimeseries()) {
181       var found = timeseriesDAO.find(
182         ts.getMeasurement(),
183         ts.getDevice(),
184         ts.getLocation(),
185         ts.getSymbolicName(),
186         ts.getField()
187       );
188       if (found != null) {
189         toCreate.addTimeseries(found);
190       } else {
191         toCreate.addTimeseries(new ReferencedTimeseriesNodeEntity(ts));
192       }
193     }
194     TimeseriesReference created = timeseriesReferenceDAO.createOrUpdate(toCreate);
195     created.setShepardId(created.getId());
196     created = timeseriesReferenceDAO.createOrUpdate(created);
197     versionService.attachToVersionOfVersionableEntityAndReturnVersion(dataObject.getId(), created.getId());
198     return created;
199   }
200 
201   /**
202    * Deletes the Timeseries reference.
203    *
204    * @param collectionShepardId
205    * @param dataObjectShepardId
206    * @param timeseriesReferenceShepardId
207    * @throws InvalidPathException if collection or dataobject specified by their Ids are null or deleted
208    * @throws InvalidAuthException if user has no permissions to edit the collection, which the reference is assigned to
209    */
210   @Override
211   public void deleteReference(long collectionShepardId, long dataObjectShepardId, long timeseriesReferenceShepardId) {
212     TimeseriesReference timeseriesReference = getReference(
213       collectionShepardId,
214       dataObjectShepardId,
215       timeseriesReferenceShepardId,
216       null
217     );
218     collectionService.assertIsAllowedToEditCollection(collectionShepardId);
219 
220     User user = userService.getCurrentUser();
221     timeseriesReference.setDeleted(true);
222     timeseriesReference.setUpdatedAt(dateHelper.getDate());
223     timeseriesReference.setUpdatedBy(user);
224     timeseriesReferenceDAO.createOrUpdate(timeseriesReference);
225   }
226 
227   public List<TimeseriesWithDataPoints> getReferencedTimeseriesWithDataPointsList(
228     long collectionShepardId,
229     long dataObjectShepardId,
230     long timeseriesShepardId,
231     AggregateFunction function,
232     Long timeSliceNanoseconds,
233     FillOption fillOption,
234     Set<String> devicesFilterSet,
235     Set<String> locationsFilterSet,
236     Set<String> symbolicNameFilterSet,
237     Set<String> measurementFilterSet,
238     Set<String> fieldFilterSet
239   ) {
240     TimeseriesReference reference = getReference(collectionShepardId, dataObjectShepardId, timeseriesShepardId, null);
241 
242     if (reference.getTimeseriesContainer() == null || reference.getTimeseriesContainer().isDeleted()) {
243       String errorMsg =
244         "Referenced Timeseries Container from reference with id %s is null or has been deleted".formatted(
245             timeseriesShepardId
246           );
247       Log.error(errorMsg);
248       throw new NotFoundException(errorMsg);
249     }
250 
251     try {
252       // check that referenced container is actually accessible
253       timeseriesContainerService.getContainer(reference.getTimeseriesContainer().getId());
254     } catch (InvalidPathException ex) {
255       throw new NotFoundException(ex.getMessage());
256     }
257 
258     var timeseriesList = reference.getReferencedTimeseriesList().stream().map(ts -> ts.toTimeseries()).toList();
259     var filteredTimeseriesList = timeseriesList
260       .stream()
261       .filter(timeseries ->
262         matchFilter(
263           timeseries,
264           devicesFilterSet,
265           locationsFilterSet,
266           symbolicNameFilterSet,
267           measurementFilterSet,
268           fieldFilterSet
269         )
270       )
271       .toList();
272     var containerId = reference.getTimeseriesContainer().getId();
273     TimeseriesDataPointsQueryParams queryParams = new TimeseriesDataPointsQueryParams(
274       reference.getStart(),
275       reference.getEnd(),
276       timeSliceNanoseconds,
277       fillOption,
278       function
279     );
280 
281     return timeseriesService.getManyTimeseriesWithDataPoints(containerId, filteredTimeseriesList, queryParams);
282   }
283 
284   public InputStream exportReferencedTimeseriesByShepardId(
285     long collectionShepardId,
286     long dataObjectShepardId,
287     long timeseriesShepardId,
288     AggregateFunction function,
289     Long timeSliceNanoseconds,
290     FillOption fillOption,
291     Set<String> devicesFilterSet,
292     Set<String> locationsFilterSet,
293     Set<String> symbolicNameFilterSet,
294     Set<String> measurementFilterSet,
295     Set<String> fieldFilterSet,
296     CsvFormat csvFormat
297   ) throws IOException {
298     TimeseriesReference reference = getReference(collectionShepardId, dataObjectShepardId, timeseriesShepardId, null);
299 
300     if (reference.getTimeseriesContainer() == null || reference.getTimeseriesContainer().isDeleted()) {
301       String errorMsg =
302         "The referenced TimeseriesContainer is null or deleted for Reference with id %s".formatted(timeseriesShepardId);
303       Log.error(errorMsg);
304       throw new NotFoundException(errorMsg);
305     }
306 
307     try {
308       timeseriesContainerService.getContainer(reference.getTimeseriesContainer().getId());
309     } catch (InvalidPathException ex) {
310       throw new InvalidRequestException(ex.getMessage());
311     }
312 
313     var timeseriesList = reference.getReferencedTimeseriesList().stream().map(ts -> ts.toTimeseries()).toList();
314     var filteredTimeseriesList = timeseriesList
315       .stream()
316       .filter(timeseries ->
317         matchFilter(
318           timeseries,
319           devicesFilterSet,
320           locationsFilterSet,
321           symbolicNameFilterSet,
322           measurementFilterSet,
323           fieldFilterSet
324         )
325       )
326       .toList();
327     var containerId = reference.getTimeseriesContainer().getId();
328     TimeseriesDataPointsQueryParams queryParams = new TimeseriesDataPointsQueryParams(
329       reference.getStart(),
330       reference.getEnd(),
331       timeSliceNanoseconds,
332       fillOption,
333       function
334     );
335 
336     return timeseriesCsvService.exportManyTimeseriesWithDataPointsToCsv(
337       containerId,
338       filteredTimeseriesList,
339       queryParams,
340       csvFormat
341     );
342   }
343 
344   public InputStream exportReferencedTimeseriesByShepardId(
345     long collectionShepardId,
346     long dataObjectShepardId,
347     long referenceId,
348     CsvFormat csvFormat
349   ) throws IOException {
350     return exportReferencedTimeseriesByShepardId(
351       collectionShepardId,
352       dataObjectShepardId,
353       referenceId,
354       null,
355       null,
356       null,
357       Collections.emptySet(),
358       Collections.emptySet(),
359       Collections.emptySet(),
360       Collections.emptySet(),
361       Collections.emptySet(),
362       csvFormat
363     );
364   }
365 
366   private boolean matchFilter(
367     Timeseries timeseries,
368     Set<String> device,
369     Set<String> location,
370     Set<String> symName,
371     Set<String> measurement,
372     Set<String> field
373   ) {
374     var deviceMatches = true;
375     var locationMatches = true;
376     var symbolicNameMatches = true;
377     var measurementMatches = true;
378     var fieldMatches = true;
379     if (!device.isEmpty()) {
380       deviceMatches = device.contains(timeseries.getDevice());
381     }
382     if (!location.isEmpty()) {
383       locationMatches = location.contains(timeseries.getLocation());
384     }
385     if (!symName.isEmpty()) {
386       symbolicNameMatches = symName.contains(timeseries.getSymbolicName());
387     }
388     if (!measurement.isEmpty()) {
389       measurementMatches = measurement.contains(timeseries.getMeasurement());
390     }
391     if (!field.isEmpty()) {
392       fieldMatches = field.contains(timeseries.getField());
393     }
394     return deviceMatches && locationMatches && symbolicNameMatches && measurementMatches && fieldMatches;
395   }
396 }