View Javadoc
1   package net.sumaris.server.http.graphql.data;
2   
3   /*-
4    * #%L
5    * SUMARiS:: Server
6    * %%
7    * Copyright (C) 2018 SUMARiS Consortium
8    * %%
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU General Public License as
11   * published by the Free Software Foundation, either version 3 of the
12   * License, or (at your option) any later version.
13   * 
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   * 
19   * You should have received a copy of the GNU General Public
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/gpl-3.0.html>.
22   * #L%
23   */
24  
25  import com.google.common.base.Preconditions;
26  import com.google.common.collect.Maps;
27  import io.leangen.graphql.annotations.*;
28  import net.sumaris.core.dao.referential.metier.MetierRepository;
29  import net.sumaris.core.dao.technical.SortDirection;
30  import net.sumaris.core.dao.technical.model.IEntity;
31  import net.sumaris.core.model.data.*;
32  import net.sumaris.core.service.data.*;
33  import net.sumaris.core.service.referential.PmfmService;
34  import net.sumaris.core.vo.administration.user.DepartmentVO;
35  import net.sumaris.core.vo.administration.user.PersonVO;
36  import net.sumaris.core.vo.data.*;
37  import net.sumaris.core.vo.filter.*;
38  import net.sumaris.core.vo.referential.MetierVO;
39  import net.sumaris.core.vo.referential.PmfmVO;
40  import net.sumaris.server.http.security.IsSupervisor;
41  import net.sumaris.server.http.security.IsUser;
42  import net.sumaris.server.service.administration.ImageService;
43  import net.sumaris.server.service.technical.ChangesPublisherService;
44  import org.apache.commons.collections4.CollectionUtils;
45  import org.reactivestreams.Publisher;
46  import org.springframework.beans.factory.annotation.Autowired;
47  import org.springframework.stereotype.Service;
48  import org.springframework.transaction.annotation.Transactional;
49  
50  import java.io.File;
51  import java.util.List;
52  import java.util.Map;
53  import java.util.Set;
54  
55  @Service
56  @Transactional
57  public class DataGraphQLService {
58  
59      @Autowired
60      private VesselService vesselService;
61  
62      @Autowired
63      private TripService tripService;
64  
65      @Autowired
66      private ObservedLocationService observedLocationService;
67  
68      @Autowired
69      private OperationService operationService;
70  
71      @Autowired
72      private LandingService landingService;
73  
74      @Autowired
75      private SaleService saleService;
76  
77      @Autowired
78      private VesselPositionService vesselPositionService;
79  
80      @Autowired
81      private SampleService sampleService;
82  
83      @Autowired
84      private BatchService batchService;
85  
86      @Autowired
87      private MeasurementService measurementService;
88  
89      @Autowired
90      private PmfmService pmfmService;
91  
92      @Autowired
93      protected PhysicalGearService physicalGearService;
94  
95      @Autowired
96      private ImageService imageService;
97  
98      @Autowired
99      private ChangesPublisherService changesPublisherService;
100 
101     @Autowired
102     private MetierRepository metierRepository;
103 
104     /* -- Vessel -- */
105 
106     @GraphQLQuery(name = "vesselSnapshots", description = "Search in vessel snapshots")
107     @Transactional(readOnly = true)
108     @IsUser
109     public List<VesselSnapshotVO> findVesselSnapshotsByFilter(@GraphQLArgument(name = "filter") VesselFilterVO filter,
110                                                               @GraphQLArgument(name = "offset", defaultValue = "0") Integer offset,
111                                                               @GraphQLArgument(name = "size", defaultValue = "1000") Integer size,
112                                                               @GraphQLArgument(name = "sortBy", defaultValue = VesselSnapshotVO.Fields.EXTERIOR_MARKING) String sort,
113                                                               @GraphQLArgument(name = "sortDirection", defaultValue = "asc") String direction
114     ) {
115         return vesselService.findSnapshotByFilter(filter, offset, size, sort,
116                 direction != null ? SortDirection.valueOf(direction.toUpperCase()) : null);
117     }
118 
119     @GraphQLQuery(name = "vessels", description = "Search in vessels")
120     @Transactional(readOnly = true)
121     @IsUser
122     public List<VesselVO> findVesselByFilter(@GraphQLArgument(name = "filter") VesselFilterVO filter,
123                                                               @GraphQLArgument(name = "offset", defaultValue = "0") Integer offset,
124                                                               @GraphQLArgument(name = "size", defaultValue = "1000") Integer size,
125                                                               @GraphQLArgument(name = "sortBy") String sort,
126                                                               @GraphQLArgument(name = "sortDirection", defaultValue = "asc") String direction
127     ) {
128         return vesselService.findVesselsByFilter(filter, offset, size, sort,
129                 direction != null ? SortDirection.valueOf(direction.toUpperCase()) : null);
130     }
131 
132     @GraphQLQuery(name = "vesselsCount", description = "Get total vessels count")
133     @Transactional(readOnly = true)
134     @IsUser
135     public long getVesselsCount(@GraphQLArgument(name = "filter") VesselFilterVO filter) {
136         return vesselService.countVesselsByFilter(filter);
137     }
138 
139     @GraphQLQuery(name = "vessel", description = "Get a vessel")
140     @Transactional(readOnly = true)
141     @IsUser
142     public VesselVO getVesselById(@GraphQLArgument(name = "vesselId") int vesselId) {
143         return vesselService.getVesselById(vesselId);
144     }
145 
146     @GraphQLQuery(name = "vesselFeaturesHistory", description = "Get vessel features history")
147     @Transactional(readOnly = true)
148     @IsUser
149     public List<VesselFeaturesVO> getVesselFeaturesHistory(@GraphQLArgument(name = "vesselId") Integer vesselId,
150                                                            @GraphQLArgument(name = "offset", defaultValue = "0") Integer offset,
151                                                            @GraphQLArgument(name = "size", defaultValue = "1000") Integer size,
152                                                            @GraphQLArgument(name = "sortBy", defaultValue = VesselFeaturesVO.Fields.START_DATE) String sort,
153                                                            @GraphQLArgument(name = "sortDirection", defaultValue = "asc") String direction) {
154         return vesselService.getFeaturesByVesselId(vesselId, offset, size, sort, direction != null ? SortDirection.valueOf(direction.toUpperCase()) : null);
155     }
156 
157     @GraphQLQuery(name = "vesselRegistrationHistory", description = "Get vessel registration history")
158     @Transactional(readOnly = true)
159     @IsUser
160     public List<VesselRegistrationVO> getVesselRegistrationHistory(@GraphQLArgument(name = "vesselId") Integer vesselId,
161                                                      @GraphQLArgument(name = "offset", defaultValue = "0") Integer offset,
162                                                      @GraphQLArgument(name = "size", defaultValue = "1000") Integer size,
163                                                      @GraphQLArgument(name = "sortBy", defaultValue = VesselRegistrationVO.Fields.START_DATE) String sort,
164                                                      @GraphQLArgument(name = "sortDirection", defaultValue = "asc") String direction) {
165         return vesselService.getRegistrationsByVesselId(vesselId, offset, size, sort, direction != null ? SortDirection.valueOf(direction.toUpperCase()) : null);
166     }
167 
168     @GraphQLMutation(name = "saveVessel", description = "Create or update a vessel")
169     @IsUser
170     public VesselVO saveVessel(@GraphQLArgument(name = "vessel") VesselVO vessel) {
171         return vesselService.save(vessel);
172     }
173 
174     @GraphQLMutation(name = "saveVessels", description = "Create or update many vessels")
175     @IsUser
176     public List<VesselVO> saveVessels(@GraphQLArgument(name = "vessels") List<VesselVO> vessels) {
177         return vesselService.save(vessels);
178     }
179 
180     @GraphQLMutation(name = "deleteVessel", description = "Delete a vessel (by vessel features id)")
181     @IsUser
182     public void deleteVessel(@GraphQLArgument(name = "id") int id) {
183         vesselService.delete(id);
184     }
185 
186     @GraphQLMutation(name = "deleteVessels", description = "Delete many vessels (by vessel features ids)")
187     @IsUser
188     public void deleteVessels(@GraphQLArgument(name = "ids") List<Integer> ids) {
189         vesselService.delete(ids);
190     }
191 
192 
193     /* -- Trip -- */
194 
195     @GraphQLQuery(name = "trips", description = "Search in trips")
196     @Transactional(readOnly = true)
197     @IsUser
198     public List<TripVO> findTripsByFilter(@GraphQLArgument(name = "filter") TripFilterVO filter,
199                                           @GraphQLArgument(name = "offset", defaultValue = "0") Integer offset,
200                                           @GraphQLArgument(name = "size", defaultValue = "1000") Integer size,
201                                           @GraphQLArgument(name = "sortBy", defaultValue = TripVO.Fields.DEPARTURE_DATE_TIME) String sort,
202                                           @GraphQLArgument(name = "sortDirection", defaultValue = "asc") String direction,
203                                           @GraphQLEnvironment() Set<String> fields
204                                   ) {
205 
206         final List<TripVO> result = tripService.findByFilter(filter, offset, size, sort,
207                 direction != null ? SortDirection.valueOf(direction.toUpperCase()) : null,
208                 getFetchOptions(fields));
209 
210         // Add additional properties if needed
211         fillTrips(result, fields);
212 
213         return result;
214     }
215 
216     @GraphQLQuery(name = "tripsCount", description = "Get total trips count")
217     @Transactional(readOnly = true)
218     @IsUser
219     public long getTripsCount(@GraphQLArgument(name = "filter") TripFilterVO filter) {
220         return tripService.countByFilter(filter);
221     }
222 
223     @GraphQLQuery(name = "trip", description = "Get a trip, by id")
224     @Transactional(readOnly = true)
225     @IsUser
226     public TripVO getTripById(@GraphQLArgument(name = "id") int id,
227                               @GraphQLEnvironment() Set<String> fields) {
228         final TripVO result = tripService.get(id);
229 
230         // Add additional properties if needed
231         fillTripFields(result, fields);
232 
233         return result;
234     }
235 
236     @GraphQLMutation(name = "saveTrip", description = "Create or update a trip")
237     @IsUser
238     public TripVO saveTrip(@GraphQLArgument(name = "trip") TripVO trip, @GraphQLEnvironment() Set<String> fields) {
239         final TripVO result = tripService.save(trip, false);
240 
241         // Add additional properties if needed
242         fillTripFields(result, fields);
243 
244         return result;
245     }
246 
247     @GraphQLMutation(name = "saveTrips", description = "Create or update many trips")
248     @IsUser
249     public List<TripVO> saveTrips(@GraphQLArgument(name = "trips") List<TripVO> trips, @GraphQLEnvironment() Set<String> fields) {
250         final List<TripVO> result = tripService.save(trips, false);
251 
252         // Add additional properties if needed
253         fillTrips(result, fields);
254 
255         return result;
256     }
257 
258     @GraphQLMutation(name = "deleteTrip", description = "Delete a trip")
259     @IsUser
260     public void deleteTrip(@GraphQLArgument(name = "id") int id) {
261         tripService.delete(id);
262     }
263 
264     @GraphQLMutation(name = "deleteTrips", description = "Delete many trips")
265     @IsUser
266     public void deleteTrips(@GraphQLArgument(name = "ids") List<Integer> ids) {
267         tripService.delete(ids);
268     }
269 
270     @GraphQLSubscription(name = "updateTrip", description = "Subscribe to changes on a trip")
271     @IsUser
272     public Publisher<TripVO> updateTrip(@GraphQLArgument(name = "id") final int id,
273                                         @GraphQLArgument(name = "interval", defaultValue = "30", description = "Minimum interval to get changes, in seconds.") final Integer minIntervalInSecond) {
274 
275         Preconditions.checkArgument(id >= 0, "Invalid id");
276         return changesPublisherService.getPublisher(Trip.class, TripVO.class, id, minIntervalInSecond, true);
277     }
278 
279     @GraphQLMutation(name = "controlTrip", description = "Control a trip")
280     @IsUser
281     public TripVO controlTrip(@GraphQLArgument(name = "trip") TripVO trip, @GraphQLEnvironment() Set<String> fields) {
282         final TripVO result = tripService.control(trip);
283 
284         // Add additional properties if needed
285         fillTripFields(result, fields);
286 
287         return result;
288     }
289 
290     @GraphQLMutation(name = "validateTrip", description = "Validate a trip")
291     @IsSupervisor
292     public TripVO validateTrip(@GraphQLArgument(name = "trip") TripVO trip, @GraphQLEnvironment() Set<String> fields) {
293         final TripVO result = tripService.validate(trip);
294 
295         // Add additional properties if needed
296         fillTripFields(result, fields);
297 
298         return result;
299     }
300 
301     @GraphQLMutation(name = "unvalidateTrip", description = "Unvalidate a trip")
302     @IsSupervisor
303     public TripVO unvalidateTrip(@GraphQLArgument(name = "trip") TripVO trip, @GraphQLEnvironment() Set<String> fields) {
304         final TripVO result = tripService.unvalidate(trip);
305 
306         // Add additional properties if needed
307         fillTripFields(result, fields);
308 
309         return result;
310     }
311 
312     @GraphQLMutation(name = "qualifyTrip", description = "Qualify a trip")
313     @IsSupervisor
314     public TripVO qualifyTrip(@GraphQLArgument(name = "trip") TripVO trip, @GraphQLEnvironment() Set<String> fields) {
315         final TripVO result = tripService.qualify(trip);
316 
317         // Add additional properties if needed
318         fillTripFields(result, fields);
319 
320         return result;
321     }
322 
323     /* -- Gears -- */
324 
325     @GraphQLQuery(name = "gears", description = "Get operation's gears")
326     public List<PhysicalGearVO> getGearsByTrip(@GraphQLContext TripVO trip) {
327         return physicalGearService.getPhysicalGearByTripId(trip.getId());
328     }
329 
330     /* -- Observed location -- */
331 
332     @GraphQLQuery(name = "observedLocations", description = "Search in observed locations")
333     @Transactional(readOnly = true)
334     @IsUser
335     public List<ObservedLocationVO> findObservedLocationsByFilter(@GraphQLArgument(name = "filter") ObservedLocationFilterVO filter,
336                                                                 @GraphQLArgument(name = "offset", defaultValue = "0") Integer offset,
337                                                                 @GraphQLArgument(name = "size", defaultValue = "1000") Integer size,
338                                                                 @GraphQLArgument(name = "sortBy", defaultValue = ObservedLocationVO.Fields.START_DATE_TIME) String sort,
339                                                                 @GraphQLArgument(name = "sortDirection", defaultValue = "asc") String direction,
340                                                                 @GraphQLEnvironment() Set<String> fields
341     ) {
342         final List<ObservedLocationVO> result = observedLocationService.findByFilter(filter, offset, size, sort,
343                 direction != null ? SortDirection.valueOf(direction.toUpperCase()) : null,
344                 getFetchOptions(fields));
345 
346         // Add additional properties if needed
347         fillObservedLocationsFields(result, fields);
348 
349         return result;
350     }
351 
352     @GraphQLQuery(name = "observedLocationsCount", description = "Get total number of observed locations")
353     @Transactional(readOnly = true)
354     @IsUser
355     public long getObservedLocationsCount(@GraphQLArgument(name = "filter") ObservedLocationFilterVO filter) {
356         return observedLocationService.countByFilter(filter);
357     }
358 
359     @GraphQLQuery(name = "observedLocation", description = "Get an observed location, by id")
360     @Transactional(readOnly = true)
361     @IsUser
362     public ObservedLocationVO getObservedLocationById(@GraphQLArgument(name = "id") int id,
363                               @GraphQLEnvironment() Set<String> fields) {
364         final ObservedLocationVO result = observedLocationService.get(id);
365 
366         // Add additional properties if needed
367         fillObservedLocationFields(result, fields);
368 
369         return result;
370     }
371 
372     @GraphQLMutation(name = "saveObservedLocation", description = "Create or update an observed location")
373     @IsUser
374     public ObservedLocationVO saveObservedLocation(@GraphQLArgument(name = "observedLocation") ObservedLocationVO observedLocation, @GraphQLEnvironment() Set<String> fields) {
375         final ObservedLocationVO result = observedLocationService.save(observedLocation, false);
376 
377         // Fill expected fields
378         fillObservedLocationFields(result, fields);
379 
380         return result;
381     }
382 
383     @GraphQLMutation(name = "saveObservedLocations", description = "Create or update many observed locations")
384     @IsUser
385     public List<ObservedLocationVO> saveObservedLocations(@GraphQLArgument(name = "observedLocations") List<ObservedLocationVO> observedLocations, @GraphQLEnvironment() Set<String> fields) {
386         final List<ObservedLocationVO> result = observedLocationService.save(observedLocations, false);
387 
388         // Fill expected fields
389         fillObservedLocationsFields(result, fields);
390 
391         return result;
392     }
393 
394     @GraphQLMutation(name = "deleteObservedLocation", description = "Delete an observed location")
395     @IsUser
396     public void deleteObservedLocation(@GraphQLArgument(name = "id") int id) {
397         observedLocationService.delete(id);
398     }
399 
400     @GraphQLMutation(name = "deleteObservedLocations", description = "Delete many observed locations")
401     @IsUser
402     public void deleteObservedLocations(@GraphQLArgument(name = "ids") List<Integer> ids) {
403         observedLocationService.delete(ids);
404     }
405 
406     @GraphQLSubscription(name = "updateObservedLocation", description = "Subscribe to changes on an observed location")
407     @IsUser
408     public Publisher<ObservedLocationVO> updateObservedLocation(@GraphQLArgument(name = "id") final int id,
409                                         @GraphQLArgument(name = "interval", defaultValue = "30", description = "Minimum interval to get changes, in seconds.") final Integer minIntervalInSecond) {
410 
411         Preconditions.checkArgument(id >= 0, "Invalid id");
412         return changesPublisherService.getPublisher(ObservedLocation.class, ObservedLocationVO.class, id, minIntervalInSecond, true);
413     }
414 
415     @GraphQLMutation(name = "controlObservedLocation", description = "Control an observed location")
416     @IsUser
417     public ObservedLocationVO controlObservedLocation(@GraphQLArgument(name = "observedLocation") ObservedLocationVO observedLocation, @GraphQLEnvironment() Set<String> fields) {
418         final ObservedLocationVO result = observedLocationService.control(observedLocation);
419 
420         // Add additional properties if needed
421         fillObservedLocationFields(result, fields);
422 
423         return result;
424     }
425 
426     @GraphQLMutation(name = "validateObservedLocation", description = "Validate an observed location")
427     @IsSupervisor
428     public ObservedLocationVO validateObservedLocation(@GraphQLArgument(name = "observedLocation") ObservedLocationVO observedLocation, @GraphQLEnvironment() Set<String> fields) {
429         final ObservedLocationVO result = observedLocationService.validate(observedLocation);
430 
431         // Add additional properties if needed
432         fillObservedLocationFields(result, fields);
433 
434         return result;
435     }
436 
437     @GraphQLMutation(name = "unvalidateObservedLocation", description = "Unvalidate an observed location")
438     @IsSupervisor
439     public ObservedLocationVO unvalidateObservedLocation(@GraphQLArgument(name = "observedLocation") ObservedLocationVO observedLocation, @GraphQLEnvironment() Set<String> fields) {
440         final ObservedLocationVO result = observedLocationService.unvalidate(observedLocation);
441 
442         // Add additional properties if needed
443         fillObservedLocationFields(result, fields);
444 
445         return result;
446     }
447 
448 
449     /* -- Sales -- */
450 
451     @GraphQLQuery(name = "sales", description = "Get trip's sales")
452     public List<SaleVO> getSalesByTrip(@GraphQLContext TripVO trip) {
453         return saleService.getAllByTripId(trip.getId());
454     }
455 
456     @GraphQLQuery(name = "sale", description = "Get trip's unique sale")
457     public SaleVO getUniqueSaleByTrip(@GraphQLContext TripVO trip) {
458         List<SaleVO> sales = saleService.getAllByTripId(trip.getId());
459         return CollectionUtils.isEmpty(sales) ? null : CollectionUtils.extractSingleton(sales);
460     }
461 
462     /* -- Operations -- */
463 
464     @GraphQLQuery(name = "operations", description = "Get trip's operations")
465     @Transactional(readOnly = true)
466     @IsUser
467     public List<OperationVO> getOperationsByTripId(@GraphQLArgument(name = "filter") OperationFilterVO filter,
468                                                    @GraphQLArgument(name = "offset", defaultValue = "0") Integer offset,
469                                                    @GraphQLArgument(name = "size", defaultValue = "1000") Integer size,
470                                                    @GraphQLArgument(name = "sortBy", defaultValue = OperationVO.Fields.START_DATE_TIME) String sort,
471                                                    @GraphQLArgument(name = "sortDirection", defaultValue = "asc") String direction) {
472         Preconditions.checkNotNull(filter, "Missing tripFilter or tripFilter.tripId");
473         Preconditions.checkNotNull(filter.getTripId(), "Missing tripFilter or tripFilter.tripId");
474         return operationService.getAllByTripId(filter.getTripId(), offset, size, sort, direction != null ? SortDirection.valueOf(direction.toUpperCase()) : null);
475     }
476 
477     @GraphQLQuery(name = "operations", description = "Get trip's operations")
478     public List<OperationVO> getOperationsByTrip(@GraphQLContext TripVO trip) {
479         return operationService.getAllByTripId(trip.getId(), 0, 100, OperationVO.Fields.START_DATE_TIME, SortDirection.ASC);
480     }
481 
482     @GraphQLQuery(name = "operation", description = "Get an operation")
483     @Transactional(readOnly = true)
484     @IsUser
485     public OperationVO getOperation(@GraphQLArgument(name = "id") int id) {
486         return operationService.get(id);
487     }
488 
489     @GraphQLMutation(name = "saveOperations", description = "Save operations")
490     @IsUser
491     public List<OperationVO> saveOperations(@GraphQLArgument(name = "operations") List<OperationVO> operations) {
492         return operationService.save(operations);
493     }
494 
495     @GraphQLMutation(name = "saveOperation", description = "Create or update an operation")
496     @IsUser
497     public OperationVO saveOperation(@GraphQLArgument(name = "operation") OperationVO operation) {
498         return operationService.save(operation);
499     }
500 
501     @GraphQLMutation(name = "deleteOperation", description = "Delete an operation")
502     @IsUser
503     public void deleteOperation(@GraphQLArgument(name = "id") int id) {
504         operationService.delete(id);
505     }
506 
507     @GraphQLMutation(name = "deleteOperations", description = "Delete many operations")
508     @IsUser
509     public void deleteOperations(@GraphQLArgument(name = "ids") List<Integer> ids) {
510         operationService.delete(ids);
511     }
512 
513     @GraphQLSubscription(name = "updateOperation", description = "Subscribe to changes on an operation")
514     @IsUser
515     public Publisher<OperationVO> updateOperation(@GraphQLArgument(name = "id") final int id,
516                                         @GraphQLArgument(name = "interval", defaultValue = "30", description = "Minimum interval to get changes, in seconds.") final Integer minIntervalInSecond) {
517 
518         Preconditions.checkArgument(id >= 0, "Invalid id");
519         return changesPublisherService.getPublisher(Operation.class, OperationVO.class, id, minIntervalInSecond, true);
520     }
521 
522     /* -- Vessel position -- */
523 
524     @GraphQLQuery(name = "positions", description = "Get operation's position")
525     public List<VesselPositionVO> getPositionsByOperation(@GraphQLContext OperationVO operation) {
526         // Avoid a reloading (e.g. when saving)
527         if (CollectionUtils.isNotEmpty(operation.getPositions())) {
528             return operation.getPositions();
529         }
530         return vesselPositionService.getAllByOperationId(operation.getId(), 0, 100, VesselPositionVO.Fields.DATE_TIME, SortDirection.ASC);
531     }
532 
533     /* -- Vessel features -- */
534 
535     @GraphQLQuery(name = "vesselSnapshot", description = "Get trip vessel snapshot")
536     public VesselSnapshotVO getVesselFeaturesByTrip(@GraphQLContext TripVO trip) {
537         return vesselService.getSnapshotByIdAndDate(trip.getVesselSnapshot().getId(), trip.getDepartureDateTime());
538     }
539 
540     /* -- Sample -- */
541 
542     @GraphQLQuery(name = "samples", description = "Get operation's samples")
543     public List<SampleVO> getSamplesByOperation(@GraphQLContext OperationVO operation) {
544         // Avoid a reloading (e.g. when saving)
545         if (CollectionUtils.isNotEmpty(operation.getSamples())) {
546             return operation.getSamples();
547         }
548 
549         return sampleService.getAllByOperationId(operation.getId());
550     }
551 
552 
553     @GraphQLQuery(name = "samples", description = "Get landing's samples")
554     public List<SampleVO> getSamplesByLanding(@GraphQLContext LandingVO landing) {
555         // Avoid a reloading (e.g. when saving)
556         if (CollectionUtils.isNotEmpty(landing.getSamples())) {
557             return landing.getSamples();
558         }
559 
560         return sampleService.getAllByLandingId(landing.getId());
561     }
562 
563     /* -- Batch -- */
564 
565     @GraphQLQuery(name = "batches", description = "Get operation's batches")
566     public List<BatchVO> getBatchesByOperation(@GraphQLContext OperationVO operation) {
567         // Avoid a reloading (e.g. when saving): reuse existing VO
568         if (operation.getBatches() != null) {
569             return operation.getBatches();
570         }
571 
572         // Reload, if not exist in VO
573         return batchService.getAllByOperationId(operation.getId());
574     }
575 
576     /* -- Landings -- */
577 
578     @GraphQLQuery(name = "landings", description = "Search in landings")
579     @Transactional(readOnly = true)
580     //@IsUser
581     public List<LandingVO> findAllLandings(@GraphQLArgument(name = "filter") LandingFilterVO filter,
582                                            @GraphQLArgument(name = "offset", defaultValue = "0") Integer offset,
583                                            @GraphQLArgument(name = "size", defaultValue = "1000") Integer size,
584                                            @GraphQLArgument(name = "sortBy", defaultValue = LandingVO.Fields.DATE_TIME) String sort,
585                                            @GraphQLArgument(name = "sortDirection", defaultValue = "asc") String direction,
586                                            @GraphQLEnvironment() Set<String> fields
587     ) {
588         final List<LandingVO> result = landingService.findAll(filter, offset, size, sort,
589                 direction != null ? SortDirection.valueOf(direction.toUpperCase()) : null,
590                 getFetchOptions(fields));
591 
592         // Add additional properties if needed
593         fillLandingsFields(result, fields);
594 
595         return result;
596     }
597 
598     @GraphQLQuery(name = "landingsCount", description = "Get total number of landings")
599     @Transactional(readOnly = true)
600     @IsUser
601     public long countLandings(@GraphQLArgument(name = "filter") LandingFilterVO filter) {
602         return landingService.countByFilter(filter);
603     }
604 
605     @GraphQLQuery(name = "landing", description = "Get an observed location, by id")
606     @Transactional(readOnly = true)
607     @IsUser
608     public LandingVO getLanding(@GraphQLArgument(name = "id") int id,
609                                 @GraphQLEnvironment() Set<String> fields) {
610         final LandingVO result = landingService.get(id);
611 
612         // Add additional properties if needed
613         fillLandingFields(result, fields);
614 
615         return result;
616     }
617 
618     @GraphQLMutation(name = "saveLanding", description = "Create or update an landing")
619     @IsUser
620     public LandingVO saveLanding(@GraphQLArgument(name = "landing") LandingVO landing,
621                                  @GraphQLEnvironment() Set<String> fields) {
622         final LandingVO result = landingService.save(landing);
623 
624         // Fill expected fields
625         fillLandingFields(result, fields);
626 
627         return result;
628     }
629 
630     @GraphQLMutation(name = "saveLandings", description = "Create or update many landings")
631     @IsUser
632     public List<LandingVO> saveLandings(@GraphQLArgument(name = "landings") List<LandingVO> landings, @GraphQLEnvironment() Set<String> fields) {
633         final List<LandingVO> result = landingService.save(landings);
634 
635         // Fill expected fields
636         fillLandingsFields(result, fields);
637 
638         return result;
639     }
640 
641     @GraphQLMutation(name = "deleteLanding", description = "Delete an observed location")
642     @IsUser
643     public void deleteLanding(@GraphQLArgument(name = "id") int id) {
644         landingService.delete(id);
645     }
646 
647     @GraphQLMutation(name = "deleteLandings", description = "Delete many observed locations")
648     @IsUser
649     public void deleteLandings(@GraphQLArgument(name = "ids") List<Integer> ids) {
650         landingService.delete(ids);
651     }
652 
653     @GraphQLSubscription(name = "updateLanding", description = "Subscribe to changes on an landing")
654     @IsUser
655     public Publisher<LandingVO> updateLanding(@GraphQLArgument(name = "id") final int id,
656                                                                 @GraphQLArgument(name = "interval", defaultValue = "30", description = "Minimum interval to get changes, in seconds.") final Integer minIntervalInSecond) {
657 
658         Preconditions.checkArgument(id >= 0, "Invalid id");
659         return changesPublisherService.getPublisher(Landing.class, LandingVO.class, id, minIntervalInSecond, true);
660     }
661 
662     /* -- Measurements -- */
663 
664     // Trip
665     @GraphQLQuery(name = "measurements", description = "Get trip's measurements")
666     public List<MeasurementVO> getTripVesselUseMeasurements(@GraphQLContext TripVO trip) {
667         return measurementService.getTripVesselUseMeasurements(trip.getId());
668     }
669 
670     @GraphQLQuery(name = "measurementValues", description = "Get trip's measurements")
671     public Map<Integer, String> getTripVesselUseMeasurementsMap(@GraphQLContext TripVO trip) {
672         if (trip.getMeasurementValues() != null) {
673             return trip.getMeasurementValues();
674         }
675         return measurementService.getTripVesselUseMeasurementsMap(trip.getId());
676     }
677 
678     // Operation
679     @GraphQLQuery(name = "measurements", description = "Get operation's measurements")
680     public List<MeasurementVO> getOperationVesselUseMeasurements(@GraphQLContext OperationVO operation) {
681         return measurementService.getOperationVesselUseMeasurements(operation.getId());
682     }
683 
684     @GraphQLQuery(name = "measurementValues", description = "Get operation's measurements")
685     public Map<Integer, String> getOperationVesselUseMeasurementsMap(@GraphQLContext OperationVO operation) {
686         if (operation.getMeasurementValues() != null) {
687             return operation.getMeasurementValues();
688         }
689         return measurementService.getOperationVesselUseMeasurementsMap(operation.getId());
690     }
691 
692     @GraphQLQuery(name = "gearMeasurements", description = "Get operation's gear measurements")
693     public List<MeasurementVO> getOperationGearUseMeasurements(@GraphQLContext OperationVO operation) {
694         return measurementService.getOperationGearUseMeasurements(operation.getId());
695     }
696 
697     @GraphQLQuery(name = "gearMeasurementValues", description = "Get operation's gear measurements")
698     public Map<Integer, String> getOperationGearUseMeasurementsMap(@GraphQLContext OperationVO operation) {
699         if (operation.getGearMeasurementValues() != null) {
700             return operation.getGearMeasurementValues();
701         }
702         return measurementService.getOperationGearUseMeasurementsMap(operation.getId());
703     }
704 
705 
706     // Physical gear
707     @GraphQLQuery(name = "measurements", description = "Get physical gear measurements")
708     public List<MeasurementVO> getPhysicalGearMeasurements(@GraphQLContext PhysicalGearVO physicalGear) {
709         return measurementService.getPhysicalGearMeasurements(physicalGear.getId());
710     }
711 
712     @GraphQLQuery(name = "measurementValues", description = "Get physical gear measurements")
713     public Map<Integer, String> getPhysicalGearMeasurementsMap(@GraphQLContext PhysicalGearVO physicalGear) {
714         if (physicalGear.getMeasurementValues() != null) {
715             return physicalGear.getMeasurementValues();
716         }
717         return measurementService.getPhysicalGearMeasurementsMap(physicalGear.getId());
718     }
719 
720     // Metier
721     @GraphQLQuery(name = "metier", description = "Get operation's metier")
722     public MetierVO getOperationMetier(@GraphQLContext OperationVO operation) {
723         // Already fetch: use it
724         if (operation.getMetier() == null || operation.getMetier().getTaxonGroup() != null) {
725             return operation.getMetier();
726         }
727         Integer metierId = operation.getMetier().getId();
728         if (metierId == null) return null;
729 
730         return metierRepository.getById(metierId);
731     }
732 
733     // Sample
734     @GraphQLQuery(name = "measurements", description = "Get sample measurements")
735     public List<MeasurementVO> getSampleMeasurements(@GraphQLContext SampleVO sample) {
736         if (sample.getMeasurements() != null) {
737             return sample.getMeasurements();
738         }
739         return measurementService.getSampleMeasurements(sample.getId());
740     }
741 
742     @GraphQLQuery(name = "measurementValues", description = "Get measurement values (as a key/value map, using pmfmId as key)")
743     public Map<Integer, String> getSampleMeasurementValues(@GraphQLContext SampleVO sample) {
744         if (sample.getMeasurementValues() != null) {
745             return sample.getMeasurementValues();
746         }
747         return measurementService.getSampleMeasurementsMap(sample.getId());
748     }
749 
750     // Batch
751     @GraphQLQuery(name = "measurementValues", description = "Get measurement values (as a key/value map, using pmfmId as key)")
752     public Map<Integer, String> getBatchMeasurementValues(@GraphQLContext BatchVO batch) {
753         if (batch.getMeasurementValues() != null) {
754             return batch.getMeasurementValues();
755         }
756         Map<Integer, String> map = Maps.newHashMap();
757         map.putAll(measurementService.getBatchSortingMeasurementsMap(batch.getId()));
758         map.putAll(measurementService.getBatchQuantificationMeasurementsMap(batch.getId()));
759         return map;
760     }
761 
762     // Observed location
763     @GraphQLQuery(name = "measurements", description = "Get measurement values")
764     public List<MeasurementVO> getObservedLocationMeasurements(@GraphQLContext ObservedLocationVO observedLocation) {
765         if (observedLocation.getMeasurements() != null) {
766             return observedLocation.getMeasurements();
767         }
768         return measurementService.getObservedLocationMeasurements(observedLocation.getId());
769     }
770 
771     @GraphQLQuery(name = "measurementValues", description = "Get measurement values (as a key/value map, using pmfmId as key)")
772     public Map<Integer, String> getObservedLocationMeasurementsMap(@GraphQLContext ObservedLocationVO observedLocation) {
773         if (observedLocation.getMeasurementValues() != null) {
774             return observedLocation.getMeasurementValues();
775         }
776         return measurementService.getObservedLocationMeasurementsMap(observedLocation.getId());
777     }
778 
779     @GraphQLQuery(name = "measurementValues", description = "Get measurement values (as a key/value map, using pmfmId as key)")
780     public Map<Integer, String> getLandingMeasurementsMap(@GraphQLContext LandingVO landing) {
781         if (landing.getMeasurementValues() != null) {
782             return landing.getMeasurementValues();
783         }
784         return measurementService.getLandingMeasurementsMap(landing.getId());
785     }
786 
787     // Measurement pmfm
788     @GraphQLQuery(name = "pmfm", description = "Get measurement's pmfm")
789     public PmfmVO getMeasurementPmfm(@GraphQLContext MeasurementVO measurement) {
790         return pmfmService.get(measurement.getPmfmId());
791     }
792 
793     // Vessel
794     @GraphQLQuery(name = "measurements", description = "Get vessel's physical measurements")
795     public List<MeasurementVO> getVesselFeaturesMeasurements(@GraphQLContext VesselSnapshotVO vesselSnapshot) {
796         return measurementService.getVesselFeaturesMeasurements(vesselSnapshot.getId());
797     }
798 
799     @GraphQLQuery(name = "measurementValues", description = "Get vessel's physical measurements")
800     public Map<Integer, String> getVesselFeaturesMeasurementsMap(@GraphQLContext VesselSnapshotVO vesselSnapshot) {
801         if (vesselSnapshot.getMeasurementValues() == null) {
802             return measurementService.getVesselFeaturesMeasurementsMap(vesselSnapshot.getId());
803         }
804         return vesselSnapshot.getMeasurementValues();
805     }
806 
807 
808     /* -- protected methods -- */
809 
810     protected void fillTripFields(TripVO trip, Set<String> fields) {
811         // Add image if need
812         fillImages(trip, fields);
813 
814         // Add vessel if need
815         fillVesselSnapshot(trip, fields);
816     }
817 
818     protected void fillTrips(List<TripVO> trips, Set<String> fields) {
819         // Add image if need
820         fillImages(trips, fields);
821 
822         // Add vessel if need
823         fillVesselSnapshot(trips, fields);
824     }
825 
826     protected void fillObservedLocationFields(ObservedLocationVO observedLocation, Set<String> fields) {
827         // Add image if need
828         fillImages(observedLocation, fields);
829     }
830 
831     protected void fillObservedLocationsFields(List<ObservedLocationVO> observedLocations, Set<String> fields) {
832         // Add image if need
833         fillImages(observedLocations, fields);
834     }
835 
836     protected void fillLandingFields(LandingVO landing, Set<String> fields) {
837         // Add image if need
838         fillImages(landing, fields);
839 
840         // Add vessel if need
841         fillVesselSnapshot(landing, fields);
842     }
843 
844     protected void fillLandingsFields(List<LandingVO> landings, Set<String> fields) {
845         // Add image if need
846         fillImages(landings, fields);
847 
848         // Add vessel if need
849         fillVesselSnapshot(landings, fields);
850     }
851 
852     protected boolean hasImageField(Set<String> fields) {
853         return fields.contains(TripVO.Fields.RECORDER_DEPARTMENT + File.separator + DepartmentVO.Fields.LOGO) ||
854                 fields.contains(TripVO.Fields.RECORDER_PERSON + File.separator + PersonVO.Fields.AVATAR);
855     }
856 
857     protected boolean hasVesselFeaturesField(Set<String> fields) {
858         return fields.contains(TripVO.Fields.VESSEL_SNAPSHOT + File.separator + VesselSnapshotVO.Fields.EXTERIOR_MARKING)
859                 || fields.contains(TripVO.Fields.VESSEL_SNAPSHOT + File.separator + VesselSnapshotVO.Fields.NAME);
860     }
861 
862     protected <T extends IRootDataVO<?>> List<T> fillImages(final List<T> results) {
863         results.forEach(this::fillImages);
864         return results;
865     }
866 
867     protected <T extends IRootDataVO<?>> T fillImages(T result) {
868         if (result != null) {
869 
870             // Fill avatar on recorder department (if not null)
871             imageService.fillLogo(result.getRecorderDepartment());
872 
873             // Fill avatar on recorder persons (if not null)
874             imageService.fillAvatar(result.getRecorderPerson());
875         }
876 
877         return result;
878     }
879 
880     protected <T extends IRootDataVO<?>> List<T> fillImages(final List<T> results, Set<String> fields) {
881         if (hasImageField(fields)) results.forEach(this::fillImages);
882         return results;
883     }
884 
885     protected <T extends IRootDataVO<?>> T fillImages(T result, Set<String> fields) {
886         if (hasImageField(fields) && result != null) {
887 
888             // Fill avatar on recorder department (if not null)
889             imageService.fillLogo(result.getRecorderDepartment());
890 
891             // Fill avatar on recorder persons (if not null)
892             imageService.fillAvatar(result.getRecorderPerson());
893         }
894 
895         return result;
896     }
897 
898     protected <T extends IWithVesselSnapshotEntity<?, VesselSnapshotVO>> void fillVesselSnapshot(T bean, Set<String> fields) {
899         // Add vessel if need
900         if (hasVesselFeaturesField(fields)) {
901             if (bean.getVesselSnapshot() != null && bean.getVesselSnapshot().getId() != null) {
902                 bean.setVesselSnapshot(vesselService.getSnapshotByIdAndDate(bean.getVesselSnapshot().getId(), bean.getVesselDateTime()));
903             }
904         }
905     }
906 
907     protected <T extends IWithVesselSnapshotEntity<?, VesselSnapshotVO>> void fillVesselSnapshot(List<T> beans, Set<String> fields) {
908         // Add vessel if need
909         if (hasVesselFeaturesField(fields)) {
910             beans.forEach(bean -> {
911                 if (bean.getVesselSnapshot() != null && bean.getVesselSnapshot().getId() != null) {
912                     bean.setVesselSnapshot(vesselService.getSnapshotByIdAndDate(bean.getVesselSnapshot().getId(), bean.getVesselDateTime()));
913                 }
914             });
915         }
916     }
917 
918     protected DataFetchOptions getFetchOptions(Set<String> fields) {
919         return DataFetchOptions.builder()
920                 .withObservers(fields.contains(IWithObserversEntity.Fields.OBSERVERS + "/" + IEntity.Fields.ID))
921                 .withRecorderDepartment(fields.contains(IWithRecorderDepartmentEntity.Fields.RECORDER_DEPARTMENT + "/" + IEntity.Fields.ID))
922                 .withRecorderPerson(fields.contains(IWithRecorderPersonEntity.Fields.RECORDER_PERSON + "/" + IEntity.Fields.ID))
923                 .build();
924     }
925 }