View Javadoc
1   package net.sumaris.core.dao.data;
2   
3   /*-
4    * #%L
5    * SUMARiS:: Core
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.ArrayListMultimap;
27  import com.google.common.collect.Lists;
28  import com.google.common.collect.Maps;
29  import com.google.common.collect.Multimap;
30  import net.sumaris.core.dao.referential.PmfmDao;
31  import net.sumaris.core.dao.referential.ReferentialDao;
32  import net.sumaris.core.dao.technical.model.IEntity;
33  import net.sumaris.core.exception.ErrorCodes;
34  import net.sumaris.core.exception.SumarisTechnicalException;
35  import net.sumaris.core.model.administration.user.Department;
36  import net.sumaris.core.model.data.*;
37  import net.sumaris.core.model.referential.QualityFlag;
38  import net.sumaris.core.model.referential.pmfm.Pmfm;
39  import net.sumaris.core.model.referential.pmfm.QualitativeValue;
40  import net.sumaris.core.util.Beans;
41  import net.sumaris.core.vo.administration.user.DepartmentVO;
42  import net.sumaris.core.vo.data.MeasurementVO;
43  import net.sumaris.core.vo.referential.ParameterValueType;
44  import net.sumaris.core.vo.referential.PmfmVO;
45  import net.sumaris.core.vo.referential.ReferentialVO;
46  import org.apache.commons.collections4.CollectionUtils;
47  import org.apache.commons.collections4.MapUtils;
48  import org.apache.commons.lang3.StringUtils;
49  import org.nuiton.i18n.I18n;
50  import org.slf4j.Logger;
51  import org.slf4j.LoggerFactory;
52  import org.springframework.beans.BeanUtils;
53  import org.springframework.beans.factory.annotation.Autowired;
54  import org.springframework.stereotype.Repository;
55  
56  import javax.persistence.EntityManager;
57  import javax.persistence.TypedQuery;
58  import javax.persistence.criteria.CriteriaBuilder;
59  import javax.persistence.criteria.CriteriaQuery;
60  import javax.persistence.criteria.ParameterExpression;
61  import javax.persistence.criteria.Root;
62  import java.beans.PropertyDescriptor;
63  import java.io.Serializable;
64  import java.sql.Timestamp;
65  import java.util.Collection;
66  import java.util.List;
67  import java.util.Map;
68  import java.util.Objects;
69  import java.util.stream.Collectors;
70  
71  @Repository("measurementDao")
72  public class MeasurementDaoImpl extends BaseDataDaoImpl implements MeasurementDao {
73  
74      /** Logger. */
75      private static final Logger log =
76              LoggerFactory.getLogger(MeasurementDaoImpl.class);
77  
78      static {
79          I18n.n("sumaris.persistence.table.vesselUseMeasurement");
80          I18n.n("sumaris.persistence.table.gearUseMeasurement");
81          I18n.n("sumaris.persistence.table.physicalGearMeasurement");
82          I18n.n("sumaris.persistence.table.sampleMeasurement");
83          I18n.n("sumaris.persistence.table.batchSortingMeasurement");
84          I18n.n("sumaris.persistence.table.batchQuantificationMeasurement");
85          I18n.n("sumaris.persistence.table.observedLocationMeasurement");
86      }
87  
88      protected static Multimap<Class<? extends IMeasurementEntity>, PropertyDescriptor> initParentPropertiesMap() {
89          Multimap<Class<? extends IMeasurementEntity>, PropertyDescriptor> result = ArrayListMultimap.create();
90  
91          // Trip
92          result.put(VesselUseMeasurement.class, BeanUtils.getPropertyDescriptor(VesselUseMeasurement.class, VesselUseMeasurement.Fields.TRIP));
93  
94          // Physical Gear
95          result.put(PhysicalGearMeasurement.class, BeanUtils.getPropertyDescriptor(PhysicalGearMeasurement.class, PhysicalGearMeasurement.Fields.PHYSICAL_GEAR));
96  
97          // Operation
98          result.put(VesselUseMeasurement.class, BeanUtils.getPropertyDescriptor(VesselUseMeasurement.class, VesselUseMeasurement.Fields.OPERATION));
99          result.put(GearUseMeasurement.class, BeanUtils.getPropertyDescriptor(GearUseMeasurement.class, GearUseMeasurement.Fields.OPERATION));
100 
101         // Observed location
102         result.put(ObservedLocationMeasurement.class, BeanUtils.getPropertyDescriptor(ObservedLocationMeasurement.class, ObservedLocationMeasurement.Fields.OBSERVED_LOCATION));
103 
104         // Sample
105         result.put(SampleMeasurement.class, BeanUtils.getPropertyDescriptor(SampleMeasurement.class, SampleMeasurement.Fields.SAMPLE));
106 
107         // Batch
108         result.put(BatchSortingMeasurement.class, BeanUtils.getPropertyDescriptor(BatchSortingMeasurement.class, BatchSortingMeasurement.Fields.BATCH));
109         result.put(BatchQuantificationMeasurement.class, BeanUtils.getPropertyDescriptor(BatchQuantificationMeasurement.class, BatchSortingMeasurement.Fields.BATCH));
110 
111         // Sale
112         result.put(SaleMeasurement.class, BeanUtils.getPropertyDescriptor(SaleMeasurement.class, SaleMeasurement.Fields.SALE));
113 
114         // Landing
115         result.put(LandingMeasurement.class, BeanUtils.getPropertyDescriptor(LandingMeasurement.class, LandingMeasurement.Fields.LANDING));
116 
117         return result;
118     }
119 
120     private Multimap<Class<? extends IMeasurementEntity>, PropertyDescriptor> parentPropertiesMap = initParentPropertiesMap();
121 
122     @Autowired
123     private ReferentialDao referentialDao;
124 
125     @Autowired
126     private PmfmDao pmfmDao;
127 
128 
129     @Override
130     public List<MeasurementVO> getTripVesselUseMeasurements(int tripId) {
131         return getMeasurementsByParentId(VesselUseMeasurement.class,
132                 VesselUseMeasurement.Fields.TRIP,
133                 tripId,
134                 VesselUseMeasurement.Fields.RANK_ORDER
135         );
136     }
137 
138     @Override
139     @SuppressWarnings("unchecked")
140     public Map<Integer, String> getTripVesselUseMeasurementsMap(int tripId) {
141         return getMeasurementsMapByParentId(VesselUseMeasurement.class,
142                 VesselUseMeasurement.Fields.TRIP,
143                 tripId,
144                 VesselUseMeasurement.Fields.ID
145         );
146     }
147 
148     @Override
149     public List<MeasurementVO> getPhysicalGearMeasurements(int physicalGearId) {
150         return getMeasurementsByParentId(PhysicalGearMeasurement.class,
151                 PhysicalGearMeasurement.Fields.PHYSICAL_GEAR,
152                 physicalGearId,
153                 PhysicalGearMeasurement.Fields.RANK_ORDER
154         );
155     }
156 
157     @Override
158     public Map<Integer, String> getPhysicalGearMeasurementsMap(int physicalGearId) {
159         return getMeasurementsMapByParentId(PhysicalGearMeasurement.class,
160                 PhysicalGearMeasurement.Fields.PHYSICAL_GEAR,
161                 physicalGearId,
162                 PhysicalGearMeasurement.Fields.ID
163         );
164     }
165 
166     @Override
167     @SuppressWarnings("unchecked")
168     public List<MeasurementVO> getOperationVesselUseMeasurements(int operationId) {
169         return getMeasurementsByParentId(VesselUseMeasurement.class,
170                 VesselUseMeasurement.Fields.OPERATION,
171                 operationId,
172                 VesselUseMeasurement.Fields.RANK_ORDER
173         );
174     }
175 
176     @Override
177     @SuppressWarnings("unchecked")
178     public Map<Integer, String> getOperationVesselUseMeasurementsMap(int operationId) {
179         return getMeasurementsMapByParentId(VesselUseMeasurement.class,
180                 VesselUseMeasurement.Fields.OPERATION,
181                 operationId,
182                 VesselUseMeasurement.Fields.ID
183         );
184     }
185 
186     @Override
187     public Map<Integer, String> getOperationGearUseMeasurementsMap(int operationId) {
188         return getMeasurementsMapByParentId(GearUseMeasurement.class,
189                 GearUseMeasurement.Fields.OPERATION,
190                 operationId,
191                 GearUseMeasurement.Fields.ID
192         );
193     }
194 
195     @Override
196     @SuppressWarnings("unchecked")
197     public List<MeasurementVO> getOperationGearUseMeasurements(int operationId) {
198         return getMeasurementsByParentId(GearUseMeasurement.class,
199                 GearUseMeasurement.Fields.OPERATION,
200                 operationId,
201                 GearUseMeasurement.Fields.RANK_ORDER
202         );
203     }
204 
205     @Override
206     @SuppressWarnings("unchecked")
207     public List<MeasurementVO> getSampleMeasurements(int sampleId) {
208         return getMeasurementsByParentId(SampleMeasurement.class,
209                 SampleMeasurement.Fields.SAMPLE,
210                 sampleId,
211                 SampleMeasurement.Fields.RANK_ORDER
212         );
213     }
214 
215     @Override
216     public List<MeasurementVO> getObservedLocationMeasurements(int observedLocationId) {
217         return getMeasurementsByParentId(ObservedLocationMeasurement.class,
218                 ObservedLocationMeasurement.Fields.OBSERVED_LOCATION,
219                 observedLocationId,
220                 ObservedLocationMeasurement.Fields.RANK_ORDER
221         );
222     }
223 
224     @Override
225     @SuppressWarnings("unchecked")
226     public Map<Integer, String> getSampleMeasurementsMap(int sampleId) {
227         return getMeasurementsMapByParentId(SampleMeasurement.class,
228                 SampleMeasurement.Fields.SAMPLE,
229                 sampleId,
230                 SampleMeasurement.Fields.RANK_ORDER
231         );
232     }
233 
234     @Override
235     @SuppressWarnings("unchecked")
236     public Map<Integer, String> getBatchSortingMeasurementsMap(int batchId) {
237         return getMeasurementsMapByParentId(BatchSortingMeasurement.class,
238                 BatchSortingMeasurement.Fields.BATCH,
239                 batchId,
240                 BatchSortingMeasurement.Fields.RANK_ORDER
241         );
242     }
243 
244     @Override
245     @SuppressWarnings("unchecked")
246     public Map<Integer, String> getBatchQuantificationMeasurementsMap(int batchId) {
247         return getMeasurementsMapByParentId(BatchQuantificationMeasurement.class,
248                 BatchQuantificationMeasurement.Fields.BATCH,
249                 batchId,
250                 BatchQuantificationMeasurement.Fields.ID
251         );
252     }
253 
254     @Override
255     @SuppressWarnings("unchecked")
256     public Map<Integer, String> getObservedLocationMeasurementsMap(int observedLocationId) {
257         return getMeasurementsMapByParentId(ObservedLocationMeasurement.class,
258                 ObservedLocationMeasurement.Fields.OBSERVED_LOCATION,
259                 observedLocationId,
260                 ObservedLocationMeasurement.Fields.ID
261         );
262     }
263 
264 
265     @Override
266     @SuppressWarnings("unchecked")
267     public Map<Integer, String> getLandingMeasurementsMap(int landingId) {
268         return getMeasurementsMapByParentId(LandingMeasurement.class,
269                 LandingMeasurement.Fields.LANDING,
270                 landingId,
271                 LandingMeasurement.Fields.ID
272         );
273     }
274 
275     @Override
276     @SuppressWarnings("unchecked")
277     public List<MeasurementVO> getLandingMeasurements(int landingId) {
278         return getMeasurementsByParentId(LandingMeasurement.class,
279                 LandingMeasurement.Fields.LANDING,
280                 landingId,
281                 LandingMeasurement.Fields.ID
282         );
283     }
284 
285     @Override
286     public <T extends IMeasurementEntity>  MeasurementVO toMeasurementVO(T source) {
287         if (source == null) return null;
288 
289         MeasurementVOmentVO.html#MeasurementVO">MeasurementVO target = new MeasurementVO();
290 
291         Beans.copyProperties(source, target);
292 
293         // Pmfm Id
294         if (source.getPmfm() != null) {
295             target.setPmfmId(source.getPmfm().getId());
296         }
297 
298         // Qualitative value
299         if (source.getQualitativeValue() != null){
300             ReferentialVO qv = referentialDao.toReferentialVO(source.getQualitativeValue());
301             target.setQualitativeValue(qv);
302         }
303 
304         // Quality flag
305         target.setQualityFlagId(source.getQualityFlag().getId());
306 
307         // Recorder department
308         DepartmentVO recorderDepartment = referentialDao.toTypedVO(source.getRecorderDepartment(), DepartmentVO.class).orElse(null);
309         target.setRecorderDepartment(recorderDepartment);
310 
311         // Entity Name
312         target.setEntityName(getEntityName(source));
313 
314         return target;
315     }
316 
317     @Override
318     public List<MeasurementVO> saveTripVesselUseMeasurements(final int tripId, List<MeasurementVO> sources) {
319         Trip parent = get(Trip.class, tripId);
320         return saveMeasurements(VesselUseMeasurement.class, sources, parent.getMeasurements(), parent);
321     }
322 
323     @Override
324     public Map<Integer, String> saveTripMeasurementsMap(int tripId, Map<Integer, String> sources) {
325         Trip parent = get(Trip.class, tripId);
326         return saveMeasurementsMap(VesselUseMeasurement.class, sources, parent.getMeasurements(), parent);
327     }
328 
329     @Override
330     public List<MeasurementVO> savePhysicalGearMeasurements(final int physicalGearId, List<MeasurementVO> sources) {
331         PhysicalGear parent = get(PhysicalGear.class, physicalGearId);
332         return saveMeasurements(PhysicalGearMeasurement.class, sources, parent.getMeasurements(), parent);
333     }
334 
335     @Override
336     public Map<Integer, String> savePhysicalGearMeasurementsMap(int physicalGearId, Map<Integer, String> sources) {
337         PhysicalGear parent = get(PhysicalGear.class, physicalGearId);
338         return saveMeasurementsMap(PhysicalGearMeasurement.class, sources, parent.getMeasurements(), parent);
339     }
340 
341     @Override
342     public List<MeasurementVO> saveOperationGearUseMeasurements(final int operationId, List<MeasurementVO> sources) {
343         Operation parent = get(Operation.class, operationId);
344         return saveMeasurements(GearUseMeasurement.class, sources, parent.getGearUseMeasurements(), parent);
345     }
346 
347     @Override
348     public List<MeasurementVO> saveOperationVesselUseMeasurements(final int operationId, List<MeasurementVO> sources) {
349         Operation parent = get(Operation.class, operationId);
350         return saveMeasurements(VesselUseMeasurement.class, sources, parent.getVesselUseMeasurements(), parent);
351     }
352 
353     @Override
354     public Map<Integer, String> saveOperationGearUseMeasurementsMap(int operationId, Map<Integer, String> sources) {
355         Operation parent = get(Operation.class, operationId);
356         return saveMeasurementsMap(GearUseMeasurement.class, sources, parent.getGearUseMeasurements(), parent);
357     }
358 
359     @Override
360     public Map<Integer, String> saveOperationVesselUseMeasurementsMap(int operationId, Map<Integer, String> sources) {
361         Operation parent = get(Operation.class, operationId);
362         return saveMeasurementsMap(VesselUseMeasurement.class, sources, parent.getVesselUseMeasurements(), parent);
363     }
364 
365     @Override
366     public List<MeasurementVO> saveObservedLocationMeasurements(final int observedLocationId, List<MeasurementVO> sources) {
367         ObservedLocation parent = get(ObservedLocation.class, observedLocationId);
368         return saveMeasurements(ObservedLocationMeasurement.class, sources, parent.getMeasurements(), parent);
369     }
370 
371     @Override
372     public Map<Integer, String> saveObservedLocationMeasurementsMap(final int observedLocationId, Map<Integer, String> sources) {
373         ObservedLocation parent = get(ObservedLocation.class, observedLocationId);
374         return saveMeasurementsMap(ObservedLocationMeasurement.class, sources, parent.getMeasurements(), parent);
375     }
376 
377     @Override
378     public List<MeasurementVO> saveSaleMeasurements(final int saleId, List<MeasurementVO> sources) {
379         Sale parent = get(Sale.class, saleId);
380         return saveMeasurements(SaleMeasurement.class, sources, parent.getMeasurements(), parent);
381     }
382 
383     @Override
384     public Map<Integer, String> saveSaleMeasurementsMap(final int saleId, Map<Integer, String> sources) {
385         Sale parent = get(Sale.class, saleId);
386         return saveMeasurementsMap(SaleMeasurement.class, sources, parent.getMeasurements(), parent);
387     }
388 
389     @Override
390     public List<MeasurementVO> saveLandingMeasurements(final int landingId, List<MeasurementVO> sources) {
391         Landing parent = get(Landing.class, landingId);
392         return saveMeasurements(LandingMeasurement.class, sources, parent.getMeasurements(), parent);
393     }
394 
395     @Override
396     public Map<Integer, String> saveLandingMeasurementsMap(final int landingId, Map<Integer, String> sources) {
397         Landing parent = get(Landing.class, landingId);
398         return saveMeasurementsMap(LandingMeasurement.class, sources, parent.getMeasurements(), parent);
399     }
400 
401     @Override
402     public List<MeasurementVO> saveSampleMeasurements(final int sampleId, List<MeasurementVO> sources) {
403         Sample parent = get(Sample.class, sampleId);
404         return saveMeasurements(SampleMeasurement.class, sources, parent.getMeasurements(), parent);
405     }
406 
407     @Override
408     public Map<Integer, String> saveSampleMeasurementsMap(final int sampleId, Map<Integer, String> sources) {
409         Sample parent = get(Sample.class, sampleId);
410         return saveMeasurementsMap(SampleMeasurement.class, sources, parent.getMeasurements(), parent);
411     }
412 
413     @Override
414     public List<MeasurementVO> saveBatchSortingMeasurements(int batchId, List<MeasurementVO> sources) {
415         Batch parent = get(Batch.class, batchId);
416         return saveMeasurements(BatchSortingMeasurement.class, sources, parent.getSortingMeasurements(), parent);
417     }
418 
419     @Override
420     public List<MeasurementVO> saveBatchQuantificationMeasurements(int batchId, List<MeasurementVO> sources) {
421         Batch parent = get(Batch.class, batchId);
422         return saveMeasurements(BatchSortingMeasurement.class, sources, parent.getSortingMeasurements(), parent);
423     }
424 
425     @Override
426     public Map<Integer, String> saveBatchSortingMeasurementsMap(int batchId, Map<Integer, String> sources) {
427         Batch parent = get(Batch.class, batchId);
428         Preconditions.checkNotNull(parent, "Could not found batch with id=" + batchId);
429         return saveMeasurementsMap(BatchSortingMeasurement.class, sources, parent.getSortingMeasurements(), parent);
430     }
431 
432     @Override
433     public Map<Integer, String> saveBatchQuantificationMeasurementsMap(int batchId, Map<Integer, String> sources) {
434         Batch parent = get(Batch.class, batchId);
435         return saveMeasurementsMap(BatchQuantificationMeasurement.class, sources, parent.getQuantificationMeasurements(), parent);
436     }
437 
438     @Override
439     public List<MeasurementVO> saveVesselPhysicalMeasurements(int vesselFeaturesId, List<MeasurementVO> sources) {
440         VesselFeatures parent = get(VesselFeatures.class, vesselFeaturesId);
441         return saveMeasurements(VesselPhysicalMeasurement.class, sources, parent.getMeasurements(), parent);
442     }
443 
444     @Override
445     public Map<Integer, String> saveVesselPhysicalMeasurementsMap(int vesselFeaturesId, Map<Integer, String> sources) {
446         VesselFeatures parent = get(VesselFeatures.class, vesselFeaturesId);
447         return saveMeasurementsMap(VesselPhysicalMeasurement.class, sources, parent.getMeasurements(), parent);
448     }
449 
450     @Override
451     public List<MeasurementVO> getVesselFeaturesMeasurements(int vesselFeaturesId) {
452         return getMeasurementsByParentId(VesselPhysicalMeasurement.class,
453                 VesselPhysicalMeasurement.Fields.VESSEL_FEATURES,
454                 vesselFeaturesId,
455                 VesselPhysicalMeasurement.Fields.ID
456         );
457     }
458 
459     @Override
460     public Map<Integer, String> getVesselFeaturesMeasurementsMap(int vesselFeaturesId) {
461         return getMeasurementsMapByParentId(VesselPhysicalMeasurement.class,
462                 VesselPhysicalMeasurement.Fields.VESSEL_FEATURES,
463                 vesselFeaturesId,
464                 VesselPhysicalMeasurement.Fields.ID
465         );
466     }
467 
468     @Override
469     public <T extends IMeasurementEntity> List<MeasurementVO> saveMeasurements(
470             final Class<? extends IMeasurementEntity> entityClass,
471             List<MeasurementVO> sources,
472             List<T> target,
473             final IDataEntity<?> parent) {
474 
475         final EntityManager em = getEntityManager();
476 
477         // Remember existing measurements, to be able to remove unused measurements
478         // note: Need Beans.getList() to avoid NullPointerException if target=null
479         final Map<Integer, T> sourceToRemove = Beans.splitById(Beans.getList(target));
480 
481         int rankOrder = 1;
482         List<MeasurementVO> result = Lists.newArrayList();
483         for (MeasurementVO source: sources) {
484             if (isNotEmpty(source)) {
485                 IMeasurementEntity entity = null;
486 
487                 // Get existing meas and remove it from list to remove
488                 if (source.getId() != null) {
489                     entity = sourceToRemove.remove(source.getId());
490                 }
491                 boolean isNew = (entity == null);
492                 if (isNew) {
493                     try {
494                         entity = entityClass.newInstance();
495                     }
496                     catch(IllegalAccessException | InstantiationException e) {
497                         throw new SumarisTechnicalException(e);
498                     }
499                 }
500 
501                 // VO -> Entity
502                 measurementVOToEntity(source, entity, true);
503 
504                 // Update rankOrder
505                 if (entity instanceof ISortedMeasurementEntity) {
506                     ((ISortedMeasurementEntity)entity).setRankOrder(rankOrder);
507                     source.setRankOrder(rankOrder);
508                     rankOrder++;
509                 }
510 
511                 // Set parent
512                 setParent(entity, parent.getClass(), parent.getId(), false);
513 
514                 // Update update_dt
515                 Timestamp newUpdateDate = getDatabaseCurrentTimestamp();
516                 entity.setUpdateDate(newUpdateDate);
517 
518                 // Save entityName
519                 if (isNew) {
520                     em.persist(entity);
521                     source.setId(entity.getId());
522                 } else {
523                     em.merge(entity);
524                 }
525 
526                 source.setUpdateDate(newUpdateDate);
527                 source.setEntityName(getEntityName(entity));
528 
529                 result.add(source);
530             }
531         }
532 
533         // Remove unused tableNames
534         if (MapUtils.isNotEmpty(sourceToRemove)) {
535             sourceToRemove.values().forEach(em::remove);
536         }
537 
538         return result;
539     }
540 
541     /* -- protected methods -- */
542 
543     protected <T extends IMeasurementEntity> String getEntityName(T source) {
544         String classname = source.getClass().getSimpleName();
545         int index = classname.indexOf("$HibernateProxy");
546         if (index > 0) {
547             return classname.substring(0, index);
548         }
549         return classname;
550     }
551 
552     protected <T extends IDataEntity> Class<T> getEntityClass(T source) {
553         String classname = source.getClass().getName();
554         int index = classname.indexOf("$HibernateProxy");
555         if (index > 0) {
556             try {
557                 return (Class<T>) Class.forName(classname.substring(0, index));
558             }
559             catch(ClassNotFoundException t) {
560                 throw new SumarisTechnicalException(t);
561             }
562         }
563         return (Class<T>)source.getClass();
564     }
565 
566 
567     protected <T extends IMeasurementEntity> Map<Integer, String> saveMeasurementsMap(
568             final Class<? extends T> entityClass,
569             Map<Integer, String> sources,
570             List<T> target,
571             final IDataEntity<?> parent) {
572 
573         final EntityManager session = getEntityManager();
574 
575         // TODO add option to preserve existing measurements
576 
577         // Remember existing measurements, to be able to remove unused measurements
578         // note: Need Beans.getList() to avoid NullPointerException if target=null
579         final Map<Integer, T> sourceToRemove = Beans.splitByProperty(Beans.getList(target), IMeasurementEntity.Fields.PMFM + "." + IMeasurementEntity.Fields.ID);
580 
581         int rankOrder = 1;
582         for (Map.Entry<Integer, String> source: sources.entrySet()) {
583             Integer pmfmId = source.getKey();
584             String value = source.getValue();
585 
586             if (StringUtils.isNotBlank(value)) {
587                 // Get existing meas and remove it from list to remove
588                 IMeasurementEntity entity = sourceToRemove.remove(pmfmId);
589 
590                 // Exists ?
591                 boolean isNew = (entity == null);
592                 if (isNew) {
593                     try {
594                         entity = entityClass.newInstance();
595                     } catch (IllegalAccessException | InstantiationException e) {
596                         throw new SumarisTechnicalException(e);
597                     }
598                 }
599 
600                 // Make sure to set pmfm
601                 if (entity.getPmfm() == null) {
602                     entity.setPmfm(load(Pmfm.class, pmfmId));
603                 }
604 
605                 // Rank order
606                 if (entity instanceof ISortedMeasurementEntity) {
607                     ((ISortedMeasurementEntity) entity).setRankOrder(rankOrder++);
608                 }
609 
610                 // Is reference ?
611                 if (entity instanceof BatchQuantificationMeasurement) {
612                     ((BatchQuantificationMeasurement) entity).setIsReferenceQuantification(rankOrder == 1);
613                 }
614 
615                 // Fill default properties
616                 fillDefaultProperties(parent, entity);
617 
618                 // Set value to entity
619                 valueToEntity(value, pmfmId, entity);
620 
621                 // Link to parent
622                 setParent(entity, getEntityClass(parent), parent.getId(), false);
623 
624                 // Update update_dt
625                 Timestamp newUpdateDate = getDatabaseCurrentTimestamp();
626                 entity.setUpdateDate(newUpdateDate);
627 
628                 // Save entity
629                 if (isNew) {
630                     session.persist(entity);
631                 } else {
632                     session.merge(entity);
633                 }
634             }
635         }
636 
637         // Remove unused measurements
638         if (MapUtils.isNotEmpty(sourceToRemove)) {
639             boolean preserveHistoricalMeasurements = config.isPreserveHistoricalMeasurements();
640             sourceToRemove.values().stream()
641                 // if the measurement is part of the sources or if the historical measurements have not to be preserved
642                 .filter(entity -> sources.containsKey(entity.getPmfm().getId()) || !preserveHistoricalMeasurements)
643                 .forEach(entity -> getEntityManager().remove(entity));
644         }
645 
646         return sources;
647     }
648 
649     protected <T extends IMeasurementEntity> List<MeasurementVO> getMeasurementsByParentId(Class<T> entityClass,
650                                                                                         String parentPropertyName,
651                                                                                         int parentId,
652                                                                                         String sortByPropertyName) {
653         TypedQuery<T> query = getMeasurementsByParentIdQuery(entityClass, parentPropertyName, parentId, sortByPropertyName);
654         return toMeasurementVOs(query.getResultList());
655     }
656 
657     protected <T extends IMeasurementEntity> Map<Integer, String> getMeasurementsMapByParentId(Class<T> entityClass,
658                                                                                            String parentPropertyName,
659                                                                                            int parentId,
660                                                                                            String sortByPropertyName) {
661         TypedQuery<T> query = getMeasurementsByParentIdQuery(entityClass, parentPropertyName, parentId, sortByPropertyName);
662         return toMeasurementsMap(query.getResultList());
663     }
664 
665 
666 
667     protected <T extends IMeasurementEntity> TypedQuery<T> getMeasurementsByParentIdQuery(Class<T> entityClass,
668                                                                                            String parentPropertyName,
669                                                                                            int parentId,
670                                                                                            String sortByPropertyName) {
671         Preconditions.checkNotNull(sortByPropertyName);
672 
673         CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
674         CriteriaQuery<T> query = builder.createQuery(entityClass);
675         Root<T> root = query.from(entityClass);
676 
677         ParameterExpression<Integer> idParam = builder.parameter(Integer.class);
678 
679         query.select(root)
680                 .where(builder.equal(root.get(parentPropertyName).get(IEntity.Fields.ID), idParam))
681                 // Order byldev
682                 .orderBy(builder.asc(root.get(sortByPropertyName)));
683 
684         return getEntityManager().createQuery(query)
685                 .setParameter(idParam, parentId);
686     }
687 
688     protected <T extends IMeasurementEntity> List<MeasurementVO> toMeasurementVOs(List<T> source) {
689         return source.stream()
690                 .map(this::toMeasurementVO)
691                 .filter(Objects::nonNull)
692                 .collect(Collectors.toList());
693     }
694 
695     protected <T extends IMeasurementEntity> Map<Integer, String> toMeasurementsMap(List<T> source) {
696         final Map<Integer, String> result = Maps.newIdentityHashMap();
697         source.stream()
698                 .filter(m -> m.getPmfm() != null && m.getPmfm().getId() != null)
699                 .forEach(m -> {
700 
701                     if (m.getPmfm() != null && m.getPmfm().getId() != null) {
702                         Object value = this.entityToValue(m);
703                         if (value != null) {
704                             result.put(m.getPmfm().getId(), value.toString());
705                         }
706                     }
707                 });
708                 //.collect(Collectors.toMap(m -> m.getPmfm().getId(), this::entityToValue))
709         return result;
710     }
711 
712 
713     protected void measurementVOToEntity(MeasurementVO source,
714                                          IMeasurementEntity target,
715                                          boolean copyIfNull) {
716 
717         Beans.copyProperties(source, target);
718 
719         // Pmfm
720         target.setPmfm(load(Pmfm.class, source.getPmfmId()));
721 
722         // Qualitative value
723         if (copyIfNull || source.getQualitativeValue() != null) {
724             if (source.getQualitativeValue() == null || source.getQualitativeValue().getId() == null) {
725                 target.setQualitativeValue(null);
726             }
727             else {
728                 target.setQualitativeValue(load(QualitativeValue.class, source.getQualitativeValue().getId()));
729             }
730         }
731 
732         // Recorder department
733         if (copyIfNull || source.getRecorderDepartment() != null) {
734             if (source.getRecorderDepartment() == null || source.getRecorderDepartment().getId() == null) {
735                 target.setRecorderDepartment(null);
736             }
737             else {
738                 target.setRecorderDepartment(load(Department.class, source.getRecorderDepartment().getId()));
739             }
740         }
741 
742         // Quality flag
743         if (copyIfNull || source.getQualityFlagId() != null) {
744             if (source.getQualityFlagId() == null) {
745                 target.setQualityFlag(load(QualityFlag.class, config.getDefaultQualityFlagId()));
746             }
747             else {
748                 target.setQualityFlag(load(QualityFlag.class, source.getQualityFlagId()));
749             }
750         }
751 
752     }
753 
754     protected void valueToEntity(String value, int pmfmId, IMeasurementEntity target) {
755 
756         if (value == null) {
757             throw new SumarisTechnicalException(ErrorCodes.BAD_REQUEST, "Unable to set value NULL value on a measurement");
758         }
759 
760         PmfmVO pmfm = pmfmDao.get(pmfmId);
761         if (pmfm == null) {
762             throw new SumarisTechnicalException(ErrorCodes.BAD_REQUEST, "Unable to find pmfm with id=" + pmfmId);
763         }
764 
765         ParameterValueType type = ParameterValueType.fromPmfm(pmfm);
766         if (type == null) {
767             throw new SumarisTechnicalException(ErrorCodes.BAD_REQUEST, "Unable to find the type of the pmfm with id=" + pmfmId);
768         }
769 
770         switch (type) {
771             case BOOLEAN:
772                 target.setNumericalValue(Boolean.parseBoolean(value) || "1".equals(value) ? 1d : 0d);
773                 break;
774             case QUALITATIVE_VALUE:
775                 // If get a object structure (e.g. ReferentialVO), try to get the id
776                 target.setQualitativeValue(load(QualitativeValue.class, Integer.parseInt(value)));
777                 break;
778             case STRING:
779                 target.setAlphanumericalValue(value);
780                 break;
781             case DATE:
782                 target.setAlphanumericalValue(value);
783                 break;
784             case INTEGER:
785             case DOUBLE:
786                 target.setNumericalValue(Double.parseDouble(value));
787                 break;
788             default:
789                 // Unknown type
790                 throw new SumarisTechnicalException( String.format("Unable to set measurement value {%s} for the type {%s}", value, type.name().toLowerCase()));
791         }
792     }
793 
794     protected Object entityToValue(IMeasurementEntity source) {
795 
796         Preconditions.checkNotNull(source);
797         Preconditions.checkNotNull(source.getPmfm());
798         Preconditions.checkNotNull(source.getPmfm().getId());
799 
800         PmfmVO pmfm = pmfmDao.get(source.getPmfm().getId());
801 
802         Preconditions.checkNotNull(pmfm, "Unable to find Pmfm with id=" + source.getPmfm().getId());
803 
804         ParameterValueType type = ParameterValueType.fromPmfm(pmfm);
805         switch (type) {
806             case BOOLEAN:
807                 return (source.getNumericalValue() != null && source.getNumericalValue().doubleValue() == 1d ? Boolean.TRUE : Boolean.FALSE);
808             case QUALITATIVE_VALUE:
809                 // If get a object structure (e.g. ReferentialVO), try to get the id
810                 return ((source.getQualitativeValue() != null && source.getQualitativeValue().getId() != null) ? source.getQualitativeValue().getId() : null);
811             case STRING:
812             case DATE:
813                 return source.getAlphanumericalValue();
814             case INTEGER:
815                 return ((source.getNumericalValue() != null) ? new Integer(source.getNumericalValue().intValue()) : null);
816             case DOUBLE:
817                 return source.getNumericalValue();
818             default:
819                 // Unknown type
820                 throw new SumarisTechnicalException( String.format("Unable to read measurement's value for the type {%s}. Measurement id=%s", type.name().toLowerCase(), source.getId()));
821         }
822     }
823 
824     protected void fillDefaultProperties(IDataEntity<?> parent, IMeasurementEntity target) {
825 
826         // Recorder department
827         if (target.getRecorderDepartment() == null) {
828             if (parent.getRecorderDepartment() == null || parent.getRecorderDepartment().getId() == null) {
829                 target.setRecorderDepartment(null);
830             }
831             else {
832                 target.setRecorderDepartment(parent.getRecorderDepartment());
833             }
834         }
835 
836         // Quality flag
837         if (target.getQualityFlag() == null) {
838             target.setQualityFlag(load(QualityFlag.class, config.getDefaultQualityFlagId()));
839         }
840     }
841 
842     protected void setParent(IMeasurementEntity target, final Class<?> parentClass, Serializable parentId, boolean copyIfNull) {
843 
844         // If null: skip
845         if (parentClass == null || (!copyIfNull && parentId == null)) return;
846 
847         // First, try to set using a corresponding parent property
848         Collection<PropertyDescriptor> parentDescriptors = parentPropertiesMap.get(target.getClass());
849         if (CollectionUtils.isNotEmpty(parentDescriptors)) {
850 
851             // Find th right parent property (use the first compatible parent)
852             PropertyDescriptor parentProperty = parentDescriptors.stream()
853                     .filter(property -> property.getPropertyType().isAssignableFrom(parentClass))
854                     .findFirst().orElse(null);
855 
856             // If a parent property has been found, use it
857             if (parentProperty != null) {
858                 try {
859                     if (parentId == null) {
860                         parentProperty.getWriteMethod().invoke(target, new Object[]{null});
861                     } else {
862                         Object parentEntity = load(parentClass, parentId);
863                         parentProperty.getWriteMethod().invoke(target, parentEntity);
864                     }
865                     return;
866                 } catch (Exception e) {
867                     throw new SumarisTechnicalException(e);
868                 }
869             }
870         }
871 
872         // No parent property in the global map: continue as a special case
873 
874         // If vessel use measurement
875         if (target instanceof VesselUseMeasurement) {
876 
877             // Trip
878             if (parentClass.isAssignableFrom(Trip.class)) {
879                 if (parentId == null) {
880                     ((VesselUseMeasurement) target).setTrip(null);
881                 } else {
882                     ((VesselUseMeasurement) target).setTrip(load(Trip.class, parentId));
883                 }
884             }
885 
886             // Operation
887             else if (parentClass.isAssignableFrom(Operation.class)) {
888                 if (parentId == null) {
889                     ((VesselUseMeasurement) target).setOperation(null);
890                 } else {
891                     ((VesselUseMeasurement) target).setOperation(load(Operation.class, parentId));
892                 }
893             }
894         }
895 
896         // If gear use measurement
897         else if (target instanceof GearUseMeasurement) {
898             // Operation
899             if (parentClass.isAssignableFrom(Operation.class)) {
900                 if (parentId == null) {
901                     ((GearUseMeasurement) target).setOperation(null);
902                 } else {
903                     ((GearUseMeasurement) target).setOperation(load(Operation.class, parentId));
904                 }
905             }
906         }
907 
908         // If physical gear measurement
909         else if (target instanceof PhysicalGearMeasurement) {
910             // Physical gear
911             if (parentId == null) {
912                 ((PhysicalGearMeasurement) target).setPhysicalGear(null);
913             } else {
914                 ((PhysicalGearMeasurement) target).setPhysicalGear(load(PhysicalGear.class, parentId));
915             }
916         }
917 
918         // Batch quantification measurement
919         else if (target instanceof BatchQuantificationMeasurement) {
920             if (parentId == null) {
921                 ((BatchQuantificationMeasurement) target).setBatch(null);
922             } else {
923                 ((BatchQuantificationMeasurement) target).setBatch(load(Batch.class, parentId));
924             }
925         }
926 
927         // Batch sorting measurement
928         else if (target instanceof BatchSortingMeasurement) {
929             if (parentId == null) {
930                 ((BatchSortingMeasurement) target).setBatch(null);
931             } else {
932                 ((BatchSortingMeasurement) target).setBatch(load(Batch.class, parentId));
933             }
934         }
935 
936         // Unknown measurement class
937         else {
938             throw new IllegalArgumentException(String.format("Class {%s} not manage yet in this method", target.getClass().getSimpleName()));
939         }
940 
941     }
942 
943     protected boolean isEmpty(MeasurementVO source) {
944         return StringUtils.isBlank(source.getAlphanumericalValue()) && source.getNumericalValue() == null
945                 && (source.getQualitativeValue() == null || source.getQualitativeValue().getId() == null);
946     }
947     protected boolean isNotEmpty(MeasurementVO source) {
948         return !isEmpty(source);
949     }
950 
951 }