View Javadoc
1   package fr.ifremer.dali.service.control;
2   
3   /*
4    * #%L
5    * Dali :: Core
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2014 - 2015 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU Affero General Public License as published by
13   * the Free Software Foundation, either version 3 of the License, or
14   * (at your option) any later version.
15   * 
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   * 
21   * You should have received a copy of the GNU Affero General Public License
22   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23   * #L%
24   */
25  
26  import com.google.common.collect.Lists;
27  import com.vividsolutions.jts.geom.Coordinate;
28  import com.vividsolutions.jts.geom.Geometry;
29  import com.vividsolutions.jts.geom.GeometryFactory;
30  import com.vividsolutions.jts.geom.Point;
31  import com.vividsolutions.jts.operation.distance.DistanceOp;
32  import fr.ifremer.dali.config.DaliConfiguration;
33  import fr.ifremer.dali.dao.data.measurement.DaliMeasurementDao;
34  import fr.ifremer.dali.dao.data.samplingoperation.DaliSamplingOperationDao;
35  import fr.ifremer.dali.dao.data.survey.DaliSurveyDao;
36  import fr.ifremer.dali.dao.system.rule.DaliRuleDao;
37  import fr.ifremer.dali.dao.technical.Geometries;
38  import fr.ifremer.dali.dto.DaliBeanFactory;
39  import fr.ifremer.dali.dto.DaliBeans;
40  import fr.ifremer.dali.dto.ErrorDTO;
41  import fr.ifremer.dali.dto.configuration.control.ControlRuleDTO;
42  import fr.ifremer.dali.dto.configuration.control.RulePmfmDTO;
43  import fr.ifremer.dali.dto.data.Coordinate1DAware;
44  import fr.ifremer.dali.dto.data.Coordinate2DAware;
45  import fr.ifremer.dali.dto.data.LocationCoordinateAware;
46  import fr.ifremer.dali.dto.data.PositioningPrecisionAware;
47  import fr.ifremer.dali.dto.data.measurement.MeasurementAware;
48  import fr.ifremer.dali.dto.data.measurement.MeasurementDTO;
49  import fr.ifremer.dali.dto.data.sampling.SamplingOperationDTO;
50  import fr.ifremer.dali.dto.data.survey.SurveyDTO;
51  import fr.ifremer.dali.dto.enums.*;
52  import fr.ifremer.dali.dto.referential.pmfm.PmfmDTO;
53  import fr.ifremer.dali.service.DaliTechnicalException;
54  import fr.ifremer.dali.service.observation.ObservationService;
55  import fr.ifremer.quadrige3.core.ProgressionCoreModel;
56  import fr.ifremer.quadrige3.core.dao.technical.Assert;
57  import fr.ifremer.quadrige3.core.dao.technical.Dates;
58  import fr.ifremer.quadrige3.ui.core.dto.CodeOnly;
59  import fr.ifremer.quadrige3.ui.core.dto.referential.BaseReferentialDTO;
60  import org.apache.commons.collections4.CollectionUtils;
61  import org.apache.commons.lang3.ArrayUtils;
62  import org.apache.commons.lang3.StringUtils;
63  import org.apache.commons.logging.Log;
64  import org.apache.commons.logging.LogFactory;
65  import org.springframework.stereotype.Service;
66  
67  import javax.annotation.Resource;
68  import java.math.BigDecimal;
69  import java.time.LocalDate;
70  import java.util.*;
71  import java.util.stream.Collectors;
72  
73  import static org.nuiton.i18n.I18n.t;
74  
75  /**
76   * Implementation rules control service.
77   */
78  @Service("daliControlRuleService")
79  public class ControlRuleServiceImpl implements ControlRuleService {
80  
81      private static final Log LOG = LogFactory.getLog(ControlRuleServiceImpl.class);
82  
83      @Resource(name = "daliRuleDao")
84      private DaliRuleDao ruleDao;
85      @Resource(name = "daliSurveyDao")
86      private DaliSurveyDao surveyDao;
87      @Resource(name = "daliSamplingOperationDao")
88      private DaliSamplingOperationDao samplingOperationDao;
89      @Resource(name = "daliMeasurementDao")
90      private DaliMeasurementDao measurementDao;
91      @Resource(name = "daliSurveyService")
92      private ObservationService observationService;
93      @Resource
94      protected DaliConfiguration configuration;
95  
96      // active control rules temporary loaded in a weak map
97      private Map<SurveyDTO, List<ControlRuleDTO>> rulesBySurveyMap = new WeakHashMap<>();
98  
99      /**
100      * {@inheritDoc}
101      */
102     @Override
103     public ControlRuleMessagesBean controlSurveys(Collection<? extends SurveyDTO> surveys,
104                                                   boolean updateControlDateWhenSucceed,
105                                                   boolean resetControlDateWhenFailed, ProgressionCoreModel progressionModel) {
106 
107         Date controlDate = new Date(System.currentTimeMillis());
108         ControlRuleMessagesBean messages = new ControlRuleMessagesBean(controlDate);
109 
110         List<Integer> validControlledElements = Lists.newArrayList();
111         List<Integer> invalidControlledElements = Lists.newArrayList();
112 
113         List<SurveyDTO> surveysToControl = surveys.stream().filter(survey -> !DaliBeans.isSurveyValidated(survey)).collect(Collectors.toList());
114         progressionModel.setTotal(surveysToControl.size());
115 
116         // Control all observations
117         for (final SurveyDTO surveyToControl : surveysToControl) {
118 
119             progressionModel.increments(t("dali.service.common.progression",
120                     t("dali.service.control"), progressionModel.getCurrent() + 1, progressionModel.getTotal()));
121 
122             // The process must control all data but this method is called from HomeUI, the surveys are not fully loaded
123             // So load them in separated beans and copy control errors afterwards
124             SurveyDTO loadedSurveyToControl = observationService.getSurveyWithoutPmfmFiltering(surveyToControl.getId());
125 
126             // process control skipping survey measurements (Mantis #47285)
127             // But only if no control date update is asked (Mantis #52518)
128             boolean succeed = executeControlOnSurvey(loadedSurveyToControl,
129                 messages,
130                 updateControlDateWhenSucceed,
131                 resetControlDateWhenFailed,
132                 !updateControlDateWhenSucceed && !resetControlDateWhenFailed);
133 
134             if (succeed && updateControlDateWhenSucceed) {
135                 validControlledElements.add(surveyToControl.getId());
136                 // update new controlDate in DTO too
137                 surveyToControl.setControlDate(controlDate);
138             } else if (!succeed && resetControlDateWhenFailed) {
139                 invalidControlledElements.add(surveyToControl.getId());
140                 // update new controlDate (null) in DTO too
141                 surveyToControl.setControlDate(null);
142             }
143 
144             // set errors from controlled survey and sampling operations
145             surveyToControl.setErrors(loadedSurveyToControl.getErrors());
146             Map<Integer, SamplingOperationDTO> samplingOperationsMap = DaliBeans.mapById(surveyToControl.getSamplingOperations());
147             for (SamplingOperationDTO samplingOperationToControl : loadedSurveyToControl.getSamplingOperations()) {
148                 SamplingOperationDTO samplingOperation = samplingOperationsMap.get(samplingOperationToControl.getId());
149                 if (samplingOperation != null) {
150                     samplingOperation.setErrors(samplingOperationToControl.getErrors());
151                 }
152             }
153         }
154 
155         // update in DB control date of controlled surveys
156         updateSurveysControlDate(validControlledElements, invalidControlledElements, controlDate);
157 
158         // clear temporary rules maps
159         rulesBySurveyMap.clear();
160 
161         return messages;
162     }
163 
164     /**
165      * {@inheritDoc}
166      */
167     @Override
168     public ControlRuleMessagesBean controlSurvey(SurveyDTO survey,
169                                                  boolean updateControlDateWhenSucceed,
170                                                  boolean resetControlDateWhenFailed) {
171 
172         // don't control if already valid
173         if (survey == null || DaliBeans.isSurveyValidated(survey)) {
174             return null;
175         }
176 
177         Date controlDate = new Date(System.currentTimeMillis());
178         ControlRuleMessagesBean messages = new ControlRuleMessagesBean(controlDate);
179 
180         List<Integer> validControlledElements = Lists.newArrayListWithCapacity(1);
181         List<Integer> invalidControlledElements = Lists.newArrayListWithCapacity(1);
182 
183         // execute control
184         boolean success = executeControlOnSurvey(survey, messages, updateControlDateWhenSucceed, resetControlDateWhenFailed, false);
185 
186         if (success && updateControlDateWhenSucceed) {
187             validControlledElements.add(survey.getId());
188             // update new controlDate in DTO too
189             survey.setControlDate(controlDate);
190         } else if (!success && resetControlDateWhenFailed) {
191             invalidControlledElements.add(survey.getId());
192             // update new controlDate (null) in DTO too
193             survey.setControlDate(null);
194         }
195 
196         // update in DB control date of controlled surveys
197         updateSurveysControlDate(validControlledElements, invalidControlledElements, controlDate);
198 
199         // clear temporary rules maps
200         rulesBySurveyMap.clear();
201 
202         return messages;
203     }
204 
205     @Override
206     public boolean controlUniqueObject(ControlRuleDTO rule, Object objectToControl) {
207 
208         // dummy beans
209         ControlRuleMessagesBean messages = new ControlRuleMessagesBean(null);
210         ErrorDTO error = DaliBeanFactory.newErrorDTO();
211 
212         if (objectToControl instanceof BigDecimal) {
213             validBigDecimal((BigDecimal) objectToControl, rule, messages, error, null);
214         } else if (objectToControl instanceof Double) {
215             validDouble((Double) objectToControl, rule, messages, error);
216         } else if (objectToControl instanceof Integer) {
217             validInteger((Integer) objectToControl, rule, messages, error);
218         } else if (objectToControl instanceof Date) {
219             validDate((Date) objectToControl, rule, messages, error);
220         } else if (objectToControl instanceof String) {
221             validString((String) objectToControl, rule, messages, error);
222         } else if (objectToControl instanceof Collection) {
223             validCollection((Collection) objectToControl, rule, messages, error);
224         } else {
225             validObject(objectToControl, rule, messages, error);
226         }
227 
228         // object if valid if no error or warning
229         return !error.isError() && !error.isWarning();
230     }
231 
232     /**
233      * Search all rules for programme and element control.
234      *
235      * @param survey          Survey identifier
236      * @param elementControls Element control.
237      * @return All rules
238      */
239     private List<ControlRuleDTO> getRules(SurveyDTO survey, ControlElementValues... elementControls) {
240         return DaliBeans.filterCollection(getRulesForSurvey(survey),
241                 controlRule -> ArrayUtils.contains(elementControls, ControlElementValues.getByCode(controlRule.getControlElement().getCode())));
242     }
243 
244     /**
245      * All control rules for a survey
246      *
247      * @param survey the survey
248      * @return Rules control list
249      */
250     private List<ControlRuleDTO> getRulesForSurvey(SurveyDTO survey) {
251         Assert.notNull(survey);
252         Assert.notNull(survey.getDate());
253         Assert.notNull(survey.getProgram());
254         Assert.notNull(survey.getDepartment());
255 
256         return rulesBySurveyMap.computeIfAbsent(survey,
257                 rules -> ruleDao.findActiveRules(
258                         Dates.convertToDate(survey.getDate(), configuration.getDbTimezone()),
259                         survey.getProgram().getCode(),
260                         survey.getDepartment().getId()));
261     }
262 
263     private boolean executeControlOnSurvey(SurveyDTO survey, ControlRuleMessagesBean messages,
264                                            boolean updateControlDateWhenSucceed,
265                                            boolean resetControlDateWhenFailed,
266                                            boolean skipSurveyMeasurements) {
267 
268         boolean isSurveyValid = true;
269         survey.getErrors().clear();
270 
271         // All observation rules
272         final List<ControlRuleDTO> rules = getRules(survey, ControlElementValues.SURVEY);
273         for (final ControlRuleDTO rule : rules) {
274 
275             // Enum value
276             final ControlFeatureSurveyValues enumValue = ControlFeatureSurveyValues.getByCode(rule.getControlFeature().getCode());
277             if (enumValue == null) {
278                 throw new DaliTechnicalException(String.format("ControlFeatureSurveyValues with code=%s has not been found", rule.getControlFeature().getCode()));
279             }
280             final ErrorDTO error = newControlError(ControlElementValues.SURVEY);
281 
282             // Test all features
283             switch (enumValue) {
284                 case CAMPAIGN:
285                     validObject(survey.getCampaign(), rule, messages, error, SurveyDTO.PROPERTY_CAMPAIGN);
286                     break;
287                 case LOCATION:
288                     validObject(survey.getLocation(), rule, messages, error, SurveyDTO.PROPERTY_LOCATION);
289                     break;
290                 case PROGRAM:
291                     validObject(survey.getProgram(), rule, messages, error, SurveyDTO.PROPERTY_PROGRAM);
292                     break;
293                 case VALIDATION_COMMENT:
294                     validString(survey.getValidationComment(), rule, messages, error, SurveyDTO.PROPERTY_VALIDATION_COMMENT);
295                     break;
296                 case QUALIFICATION_COMMENT:
297                     validString(survey.getQualificationComment(), rule, messages, error, SurveyDTO.PROPERTY_QUALIFICATION_COMMENT);
298                     break;
299                 case DATE:
300                     validLocalDate(survey.getDate(), rule, messages, error, SurveyDTO.PROPERTY_DATE);
301                     break;
302                 case CONTROL_DATE:
303                     validDate(survey.getControlDate(), rule, messages, error, SurveyDTO.PROPERTY_CONTROL_DATE);
304                     break;
305                 case UPDATE_DATE:
306                     validDate(survey.getUpdateDate(), rule, messages, error, SurveyDTO.PROPERTY_UPDATE_DATE);
307                     break;
308                 case VALIDATION_DATE:
309                     validDate(survey.getValidationDate(), rule, messages, error, SurveyDTO.PROPERTY_VALIDATION_DATE);
310                     break;
311                 case QUALIFICATION_DATE:
312                     validDate(survey.getQualificationDate(), rule, messages, error, SurveyDTO.PROPERTY_QUALIFICATION_DATE);
313                     break;
314                 case TIME:
315                     validInteger(survey.getTime(), rule, messages, error, SurveyDTO.PROPERTY_TIME);
316                     break;
317                 case COMMENT:
318                     validString(survey.getComment(), rule, messages, error, SurveyDTO.PROPERTY_COMMENT);
319                     break;
320                 case LATITUDE_REAL_SURVEY:
321                     validDouble(survey.getCoordinate() == null ? null : survey.getCoordinate().getMinLatitude(), rule, messages, error, Coordinate1DAware.PROPERTY_LATITUDE);
322                     break;
323                 case LONGITUDE_REAL_SURVEY:
324                     validDouble(survey.getCoordinate() == null ? null : survey.getCoordinate().getMinLongitude(), rule, messages, error, Coordinate1DAware.PROPERTY_LONGITUDE);
325                     break;
326                 case NAME:
327                     validString(survey.getName(), rule, messages, error, SurveyDTO.PROPERTY_NAME);
328                     break;
329                 case DEPARTMENT:
330                     validObject(survey.getDepartment(), rule, messages, error, SurveyDTO.PROPERTY_DEPARTMENT);
331                     break;
332                 case POSITIONING:
333                     validObject(survey.getPositioning(), rule, messages, error, SurveyDTO.PROPERTY_POSITIONING);
334                     break;
335                 case POSITIONING_PRECISION:
336                     if (survey.getPositioning() != null) {
337                         validString(survey.getPositioning().getPrecision(), rule, messages, error, PositioningPrecisionAware.PROPERTY_POSITIONING_PRECISION);
338                     }
339                     break;
340                 case BOTTOM_DEPTH:
341                     validDouble(survey.getBottomDepth(), rule, messages, error, SurveyDTO.PROPERTY_BOTTOM_DEPTH);
342                     break;
343                 case LATITUDE_MAX_LOCATION:
344                     validDouble(survey.getLocation().getCoordinate() == null ? null : survey.getLocation().getCoordinate().getMaxLatitude(), rule, messages, error, LocationCoordinateAware.PROPERTY_LOCATION_MAX_LATITUDE);
345                     break;
346                 case LATITUDE_MIN_LOCATION:
347                     validDouble(survey.getLocation().getCoordinate() == null ? null : survey.getLocation().getCoordinate().getMinLatitude(), rule, messages, error, LocationCoordinateAware.PROPERTY_LOCATION_MIN_LATITUDE);
348                     break;
349                 case LONGITUDE_MAX_LOCATION:
350                     validDouble(survey.getLocation().getCoordinate() == null ? null : survey.getLocation().getCoordinate().getMaxLongitude(), rule, messages, error, LocationCoordinateAware.PROPERTY_LOCATION_MAX_LONGITUDE);
351                     break;
352                 case LONGITUDE_MIN_LOCATION:
353                     validDouble(survey.getLocation().getCoordinate() == null ? null : survey.getLocation().getCoordinate().getMinLongitude(), rule, messages, error, LocationCoordinateAware.PROPERTY_LOCATION_MIN_LONGITUDE);
354                     break;
355                 case OBSERVERS:
356                     validCollection(survey.getObservers(), rule, messages, error, SurveyDTO.PROPERTY_OBSERVERS);
357                     break;
358                 default:
359                     break;
360             }
361             if (error.isError() || error.isWarning()) {
362                 survey.addErrors(error);
363                 if (error.isError()) {
364                     isSurveyValid = false;
365                 }
366             }
367 
368         }
369 
370         // Geometries
371         controlGeometry(survey, messages);
372 
373         // Measurements
374         if (!skipSurveyMeasurements && !controlMeasurements(survey,
375                 getRules(survey, ControlElementValues.MEASUREMENT, ControlElementValues.TAXON_MEASUREMENT),
376                 messages,
377                 updateControlDateWhenSucceed,
378                 resetControlDateWhenFailed)) {
379             isSurveyValid = false;
380         }
381 
382         // SamplingOperations
383         List<Integer> validControlledElements = Lists.newArrayList();
384         List<Integer> invalidControlledElements = Lists.newArrayList();
385         for (final SamplingOperationDTO samplingOperation : survey.getSamplingOperations()) {
386             boolean success = executeControlOnSamplingOperation(survey, samplingOperation, messages, updateControlDateWhenSucceed, resetControlDateWhenFailed);
387             if (success && updateControlDateWhenSucceed) {
388                 validControlledElements.add(samplingOperation.getId());
389                 samplingOperation.setControlDate(messages.getControlDate());
390             } else if (!success && resetControlDateWhenFailed) {
391                 invalidControlledElements.add(samplingOperation.getId());
392                 samplingOperation.setControlDate(null);
393             }
394         }
395 
396         // update control date of controlled sampling operations
397         updateSamplingOperationsControlDate(validControlledElements, invalidControlledElements, messages.getControlDate());
398 
399         return isSurveyValid && invalidControlledElements.size() == 0;
400     }
401 
402     private void controlGeometry(SurveyDTO survey, ControlRuleMessagesBean messages) {
403 
404         // Get location geometry
405         if (Geometries.isValid(survey.getCoordinate())
406                 && Geometries.isValid(survey.getLocation().getCoordinate())) {
407 
408             Geometry surveyGeometry = Geometries.getGeometry(survey.getCoordinate());
409             Geometry locationGeometry = Geometries.getGeometry(survey.getLocation().getCoordinate().getWkt()); // Mantis #42564 Use real location geometry
410             ErrorDTO error = newControlError(ControlElementValues.SURVEY);
411             int minDistance = configuration.getControlSurveyLocationMinDistanceInMeter();
412 
413             // warn and skip survey with area geometry
414             if (surveyGeometry.getDimension() > 1) {
415                 LOG.warn("Unable to control a survey with an area geometry");
416                 return;
417             }
418 
419             switch (locationGeometry.getDimension()) {
420                 case 0:
421                     // location is a point
422                     if (surveyGeometry.getDimension() == 0) {
423                         // survey is a point
424                         Double distance = Geometries.getDistanceInMeter(surveyGeometry.getCoordinate(), locationGeometry.getCoordinate());
425                         if (distance != null && distance > minDistance) {
426                             addGeometryMessage(messages, error, t("dali.service.control.geometry.survey.location.aboveMinimum", minDistance),
427                                     SurveyDTO.PROPERTY_LOCATION,
428                                     Coordinate2DAware.PROPERTY_LATITUDE_MIN, Coordinate2DAware.PROPERTY_LONGITUDE_MIN);
429                         }
430                     } else if (surveyGeometry.getDimension() == 1) {
431                         // survey is a line
432                         Coordinate[] coordinates = DistanceOp.nearestPoints(surveyGeometry, locationGeometry);
433                         Double distance = Geometries.getDistanceInMeter(coordinates[0], coordinates[1]);
434                         if (distance != null && distance > minDistance) {
435                             addGeometryMessage(messages, error, t("dali.service.control.geometry.location.survey.aboveMinimum", minDistance),
436                                     SurveyDTO.PROPERTY_LOCATION,
437                                     Coordinate2DAware.PROPERTY_LATITUDE_MIN, Coordinate2DAware.PROPERTY_LONGITUDE_MIN,
438                                     Coordinate2DAware.PROPERTY_LATITUDE_MAX, Coordinate2DAware.PROPERTY_LONGITUDE_MAX);
439                         }
440                     }
441                     break;
442                 case 1:
443                     // location is a line
444                     if (surveyGeometry.getDimension() == 0) {
445                         // survey is a point
446                         Coordinate[] coordinates = DistanceOp.nearestPoints(surveyGeometry, locationGeometry);
447                         Double distance = Geometries.getDistanceInMeter(coordinates[0], coordinates[1]);
448                         if (distance != null && distance > minDistance) {
449                             addGeometryMessage(messages, error, t("dali.service.control.geometry.survey.location.aboveMinimum", minDistance),
450                                     SurveyDTO.PROPERTY_LOCATION,
451                                     Coordinate2DAware.PROPERTY_LATITUDE_MIN, Coordinate2DAware.PROPERTY_LONGITUDE_MIN);
452                         }
453                     } else if (surveyGeometry.getDimension() == 1) {
454                         // survey is a line
455                         Double distanceFromStart = Geometries.getDistanceInMeter(surveyGeometry.getCoordinates()[0], locationGeometry.getCoordinates()[0]);
456                         if (distanceFromStart != null && distanceFromStart > minDistance) {
457                             addGeometryMessage(messages, error, t("dali.service.control.geometry.survey.location.start.aboveMinimum", minDistance),
458                                     SurveyDTO.PROPERTY_LOCATION,
459                                     Coordinate2DAware.PROPERTY_LATITUDE_MIN, Coordinate2DAware.PROPERTY_LONGITUDE_MIN);
460                         }
461                         Double distanceFromEnd = Geometries.getDistanceInMeter(surveyGeometry.getCoordinates()[1], locationGeometry.getCoordinates()[1]);
462                         if (distanceFromEnd != null && distanceFromEnd > minDistance) {
463                             addGeometryMessage(messages, error, t("dali.service.control.geometry.survey.location.end.aboveMinimum", minDistance),
464                                     SurveyDTO.PROPERTY_LOCATION,
465                                     Coordinate2DAware.PROPERTY_LATITUDE_MAX, Coordinate2DAware.PROPERTY_LONGITUDE_MAX);
466                         }
467                     }
468                     break;
469                 default:
470                     // location is an area, simply test if survey bounds are included
471                     if (surveyGeometry.getDimension() >= 0) {
472                         // survey is a point or a line
473                         Point point = GeometryFactory.createPointFromInternalCoord(surveyGeometry.getCoordinates()[0], surveyGeometry);
474                         if (!locationGeometry.covers(point)) {
475                             addGeometryMessage(messages, error, t("dali.service.control.geometry.survey.location.notIncluded"),
476                                     SurveyDTO.PROPERTY_LOCATION,
477                                     Coordinate2DAware.PROPERTY_LATITUDE_MIN, Coordinate2DAware.PROPERTY_LONGITUDE_MIN);
478                         }
479                     }
480                     if (surveyGeometry.getDimension() == 1) {
481                         // survey is a line
482                         Point point = GeometryFactory.createPointFromInternalCoord(surveyGeometry.getCoordinates()[1], surveyGeometry);
483                         if (!locationGeometry.covers(point)) {
484                             addGeometryMessage(messages, error, t("dali.service.control.geometry.survey.location.notIncluded"),
485                                     SurveyDTO.PROPERTY_LOCATION,
486                                     Coordinate2DAware.PROPERTY_LATITUDE_MAX, Coordinate2DAware.PROPERTY_LONGITUDE_MAX);
487                         }
488                     }
489                     break;
490             }
491 
492             if (error.isWarning() || error.isError()) {
493                 survey.addErrors(error);
494             }
495         }
496     }
497 
498     private boolean executeControlOnSamplingOperation(SurveyDTO survey, SamplingOperationDTO samplingOperation, ControlRuleMessagesBean messages, boolean updateControlDateWhenSucceed, boolean resetControlDateWhenFailed) {
499 
500         boolean isSamplingOperationValid = true;
501 
502         // init error list
503         samplingOperation.getErrors().clear();
504 
505         // All sampling operation rules
506         final List<ControlRuleDTO> rules = getRules(survey, ControlElementValues.SAMPLING_OPERATION);
507         for (final ControlRuleDTO rule : rules) {
508 
509             // Enum value
510             final ControlFeatureSamplingOperationValues enumValue = ControlFeatureSamplingOperationValues.getByCode(rule.getControlFeature().getCode());
511             if (enumValue == null) {
512                 throw new DaliTechnicalException(String.format("ControlFeatureSamplingOperationValues with code=%s has not been found", rule.getControlFeature().getCode()));
513             }
514             final ErrorDTO error = newControlError(ControlElementValues.SAMPLING_OPERATION);
515 
516             // Test all features
517             switch (enumValue) {
518                 case TIME:
519                     validInteger(samplingOperation.getTime(), rule, messages, error, SamplingOperationDTO.PROPERTY_TIME);
520                     break;
521                 case COMMENT:
522                     validString(samplingOperation.getComment(), rule, messages, error, SamplingOperationDTO.PROPERTY_COMMENT);
523                     break;
524                 case DEPTH:
525                     validDouble(samplingOperation.getDepth(), rule, messages, error, SamplingOperationDTO.PROPERTY_DEPTH);
526                     break;
527                 case DEPTH_MAX:
528                     validDouble(samplingOperation.getMaxDepth(), rule, messages, error, SamplingOperationDTO.PROPERTY_MAX_DEPTH);
529                     break;
530                 case DEPTH_MIN:
531                     validDouble(samplingOperation.getMinDepth(), rule, messages, error, SamplingOperationDTO.PROPERTY_MIN_DEPTH);
532                     break;
533                 case LATITUDE_REAL:
534                     validDouble(samplingOperation.getCoordinate() == null ? null : samplingOperation.getCoordinate().getMinLatitude(), rule, messages, error, Coordinate1DAware.PROPERTY_LATITUDE);
535                     break;
536                 case LONGITUDE_REAL:
537                     validDouble(samplingOperation.getCoordinate() == null ? null : samplingOperation.getCoordinate().getMinLongitude(), rule, messages, error, Coordinate1DAware.PROPERTY_LONGITUDE);
538                     break;
539                 case NAME:
540                     validString(samplingOperation.getName(), rule, messages, error, SamplingOperationDTO.PROPERTY_NAME);
541                     break;
542                 case POSITIONING:
543                     validObject(samplingOperation.getPositioning(), rule, messages, error, SamplingOperationDTO.PROPERTY_POSITIONING);
544                     break;
545                 case POSITIONING_PRECISION:
546                     if (samplingOperation.getPositioning() != null) {
547                         validString(samplingOperation.getPositioning().getPrecision(), rule, messages, error, SamplingOperationDTO.PROPERTY_POSITIONING);
548                     }
549                     break;
550                 case GEAR:
551                     validObject(samplingOperation.getSamplingEquipment(), rule, messages, error, SamplingOperationDTO.PROPERTY_SAMPLING_EQUIPMENT);
552                     break;
553                 case SIZE:
554                     validDouble(samplingOperation.getSize(), rule, messages, error, SamplingOperationDTO.PROPERTY_SIZE);
555                     break;
556                 case SIZE_UNIT:
557                     validObject(samplingOperation.getSizeUnit(), rule, messages, error, SamplingOperationDTO.PROPERTY_SIZE_UNIT);
558                     break;
559                 case DEPARTMENT:
560                     validObject(samplingOperation.getSamplingDepartment(), rule, messages, error, SamplingOperationDTO.PROPERTY_SAMPLING_DEPARTMENT);
561                     break;
562 
563                 default:
564                     break;
565             }
566 
567             if (error.isError() || error.isWarning()) {
568                 samplingOperation.addErrors(error);
569                 if (error.isError()) {
570                     isSamplingOperationValid = false;
571                 }
572             }
573 
574         }
575 
576         // Control sampling operation measurements
577         boolean isMeasurementsValid = controlMeasurements(samplingOperation,
578                 getRules(survey, ControlElementValues.MEASUREMENT, ControlElementValues.TAXON_MEASUREMENT),
579                 messages,
580                 updateControlDateWhenSucceed,
581                 resetControlDateWhenFailed);
582 
583         return isSamplingOperationValid && isMeasurementsValid;
584     }
585 
586     private boolean controlMeasurements(MeasurementAware bean, List<ControlRuleDTO> rules, ControlRuleMessagesBean messages,
587                                         boolean updateControlDateWhenSucceed, boolean resetControlDateWhenFailed) {
588 
589         List<ErrorDTO> errors = Lists.newArrayList();
590 
591         // update control date of controlled measurements
592         List<Integer> validMeasurementsElements = Lists.newArrayList();
593         List<Integer> invalidMeasurementsElements = Lists.newArrayList();
594         List<Integer> validTaxonMeasurementsElements = Lists.newArrayList();
595         List<Integer> invalidTaxonMeasurementsElements = Lists.newArrayList();
596 
597         // rebuild all potential measurement from pmfms lists
598         List<MeasurementDTO> measurementsToControl = Lists.newArrayList();
599         List<MeasurementDTO> individualMeasurementsToControl = Lists.newArrayList();
600         DaliBeans.populateMeasurementsFromPmfms(bean, measurementsToControl, individualMeasurementsToControl);
601 
602         if (updateControlDateWhenSucceed) {
603             // by default, all non-empty measurements are valid
604             for (MeasurementDTO measurement : measurementsToControl) {
605                 if (!DaliBeans.isMeasurementEmpty(measurement)) {
606                     validMeasurementsElements.add(measurement.getId());
607                 }
608             }
609             for (MeasurementDTO individualMeasurement : individualMeasurementsToControl) {
610                 if (!DaliBeans.isMeasurementEmpty(individualMeasurement)) {
611                     if (DaliBeans.isTaxonMeasurement(individualMeasurement)) {
612                         validTaxonMeasurementsElements.add(individualMeasurement.getId());
613                     } else {
614                         validMeasurementsElements.add(individualMeasurement.getId());
615                     }
616                 }
617             }
618         }
619 
620         for (final ControlRuleDTO rule : rules) {
621 
622             // All measurements
623             for (final MeasurementDTO measurement : measurementsToControl) {
624                 if (isPmfmFoundInRule(measurement.getPmfm(), rule)) {
625                     ErrorDTO error = executeControlOnMeasurement(measurement, rule, false, messages);
626                     if (error.isError() || error.isWarning()) {
627                         errors.add(error);
628                     }
629                     if (error.isError() && resetControlDateWhenFailed) {
630                         invalidMeasurementsElements.add(measurement.getId());
631                         measurement.setControlDate(null);
632                     } else if (!error.isError() && updateControlDateWhenSucceed) {
633                         measurement.setControlDate(messages.getControlDate());
634                     }
635                 }
636             }
637 
638             // All individual measurements
639             for (final MeasurementDTO measurement : individualMeasurementsToControl) {
640                 if (isPmfmFoundInRule(measurement.getPmfm(), rule)) {
641                     ErrorDTO error;
642                     if (DaliBeans.isTaxonMeasurement(measurement)) {
643                         error = executeControlOnTaxonMeasurement(measurement, rule, messages);
644                     } else {
645                         error = executeControlOnMeasurement(measurement, rule, true, messages);
646                     }
647                     if (error.isError() || error.isWarning()) {
648                         errors.add(error);
649                     }
650                     if (error.isError() && resetControlDateWhenFailed) {
651                         if (DaliBeans.isTaxonMeasurement(measurement)) {
652                             invalidTaxonMeasurementsElements.add(measurement.getId());
653                         } else {
654                             invalidMeasurementsElements.add(measurement.getId());
655                         }
656                         measurement.setControlDate(null);
657                     } else if (!error.isError() && updateControlDateWhenSucceed) {
658                         measurement.setControlDate(messages.getControlDate());
659                     }
660                 }
661             }
662         }
663 
664         // remove invalid elements from valid lists
665         validMeasurementsElements.removeAll(invalidMeasurementsElements);
666         validTaxonMeasurementsElements.removeAll(invalidTaxonMeasurementsElements);
667 
668         updateMeasurementsControlDate(validMeasurementsElements, invalidMeasurementsElements, messages.getControlDate());
669         updateTaxonMeasurementsControlDate(validTaxonMeasurementsElements, invalidTaxonMeasurementsElements, messages.getControlDate());
670 
671         // also add measurements errors to bean
672         bean.getErrors().addAll(errors);
673 
674         return invalidMeasurementsElements.size() + invalidTaxonMeasurementsElements.size() == 0;
675     }
676 
677     /**
678      * Control measurement detail.
679      *
680      * @param measurement the measurement to control
681      * @param rule        Rule
682      */
683     private ErrorDTO executeControlOnMeasurement(
684             MeasurementDTO measurement,
685             ControlRuleDTO rule,
686             boolean isIndividual,
687             ControlRuleMessagesBean messages) {
688 
689         // Clear
690         measurement.getErrors().clear();
691 
692         // Enum value
693         final ControlFeatureMeasurementValues enumValue = ControlFeatureMeasurementValues.getByCode(rule.getControlFeature().getCode());
694         if (enumValue == null) {
695             throw new DaliTechnicalException(String.format("ControlFeatureMeasurementValues with code=%s has not been found", rule.getControlFeature().getCode()));
696         }
697         final ErrorDTO error = newControlError(ControlElementValues.MEASUREMENT);
698 
699         // Test all features
700         switch (enumValue) {
701             case ANALYST:
702                 if (!DaliBeans.isMeasurementEmpty(measurement)) {
703                     validObject(measurement.getAnalyst(), rule, messages, error, MeasurementDTO.PROPERTY_ANALYST);
704                 }
705                 break;
706             case PMFM:
707                 validObject(measurement.getPmfm(), rule, messages, error, measurement.getPmfm().getId(), isIndividual ? SurveyDTO.PROPERTY_INDIVIDUAL_PMFMS : SurveyDTO.PROPERTY_PMFMS);
708                 break;
709             case NUMERICAL_VALUE:
710                 validBigDecimal(measurement.getNumericalValue(), rule, messages, error, measurement.getPmfm().getId(), isIndividual ? SurveyDTO.PROPERTY_INDIVIDUAL_PMFMS : SurveyDTO.PROPERTY_PMFMS);
711                 break;
712             case QUALITATIVE_VALUE:
713                 validObject(measurement.getQualitativeValue(), rule, messages, error, measurement.getPmfm().getId(), isIndividual ? SurveyDTO.PROPERTY_INDIVIDUAL_PMFMS : SurveyDTO.PROPERTY_PMFMS);
714                 break;
715             default:
716                 break;
717         }
718 
719         if (error.isError() || error.isWarning()) {
720             if (isIndividual) {
721                 error.setIndividualId(measurement.getIndividualId());
722             }
723             if (measurement.getErrors() == null) {
724                 measurement.setErrors(new ArrayList<>());
725             }
726             measurement.addErrors(error);
727         }
728 
729         return error;
730     }
731 
732     /**
733      * Control measurement detail.
734      *
735      * @param measurement the measurement to control
736      * @param rule        Rule
737      */
738     private ErrorDTO executeControlOnTaxonMeasurement(
739             MeasurementDTO measurement,
740             ControlRuleDTO rule,
741             ControlRuleMessagesBean messages) {
742 
743         // Clear
744         measurement.getErrors().clear();
745 
746         // Enum value
747         final ControlFeatureTaxonMeasurementValues enumValue = ControlFeatureTaxonMeasurementValues.getByCode(rule.getControlFeature().getCode());
748         if (enumValue == null) {
749             throw new DaliTechnicalException(String.format("ControlFeatureTaxonMeasurementValues with code=%s has not been found", rule.getControlFeature().getCode()));
750         }
751         final ErrorDTO error = newControlError(ControlElementValues.TAXON_MEASUREMENT);
752 
753         // Test all features
754         switch (enumValue) {
755             case ANALYST:
756                 if (!DaliBeans.isMeasurementEmpty(measurement)) {
757                     validObject(measurement.getAnalyst(), rule, messages, error, MeasurementDTO.PROPERTY_ANALYST);
758                 }
759                 break;
760             case PMFM:
761                 validObject(measurement.getPmfm(), rule, messages, error, measurement.getPmfm().getId(), SurveyDTO.PROPERTY_INDIVIDUAL_PMFMS);
762                 break;
763             case NUMERICAL_VALUE:
764                 validBigDecimal(measurement.getNumericalValue(), rule, messages, error, measurement.getPmfm().getId(), SurveyDTO.PROPERTY_INDIVIDUAL_PMFMS);
765                 break;
766             case QUALITATIVE_VALUE:
767                 validObject(measurement.getQualitativeValue(), rule, messages, error, measurement.getPmfm().getId(), SurveyDTO.PROPERTY_INDIVIDUAL_PMFMS);
768                 break;
769             case TAXON:
770                 validObject(measurement.getTaxon(), rule, messages, error, MeasurementDTO.PROPERTY_TAXON);
771                 break;
772             case TAXON_GROUP:
773                 validObject(measurement.getTaxonGroup(), rule, messages, error, MeasurementDTO.PROPERTY_TAXON_GROUP);
774                 break;
775             default:
776                 break;
777         }
778 
779         if (error.isError() || error.isWarning()) {
780             error.setIndividualId(measurement.getIndividualId());
781             if (measurement.getErrors() == null) {
782                 measurement.setErrors(new ArrayList<>());
783             }
784             measurement.addErrors(error);
785         }
786 
787         return error;
788     }
789 
790     /**
791      * Valid object with rule control.
792      *
793      * @param object        Object to test
794      * @param rule          Rule apply
795      * @param messages      the messages bean
796      * @param error         the Error object
797      * @param propertyNames the property names
798      */
799     private void validObject(Object object, ControlRuleDTO rule, ControlRuleMessagesBean messages, ErrorDTO error, String... propertyNames) {
800         validObject(object, rule, messages, error, null, propertyNames);
801     }
802 
803     /**
804      * Valid object with rule control.
805      *
806      * @param object        Object to test
807      * @param rule          Rule apply
808      * @param messages      the messages bean
809      * @param error         the Error object
810      * @param pmfmId        the pmfmId (optional)
811      * @param propertyNames the property names
812      */
813     private void validObject(Object object, ControlRuleDTO rule, ControlRuleMessagesBean messages, ErrorDTO error, Integer pmfmId, String... propertyNames) {
814 
815         switch (ControlFunctionValues.getFunctionValue(rule.getFunction().getId())) {
816 
817             case IS_EMPTY:
818                 if (object != null) {
819                     addMessage(messages, rule, error, pmfmId, propertyNames);
820                 }
821                 break;
822 
823             case NOT_EMPTY:
824                 if (object == null) {
825                     addMessage(messages, rule, error, pmfmId, propertyNames);
826                 }
827                 break;
828 
829             case IS_AMONG:
830                 if (rule.getAllowedValues() != null) {
831                     // get all allowed values names
832                     List<String> allowedValues = Lists.newArrayList(rule.getAllowedValues().split(configuration.getValueSeparator()));
833                     if (allowedValues.isEmpty()) break;
834 
835                     if (object instanceof BaseReferentialDTO) {
836                         BaseReferentialDTO baseObject = (BaseReferentialDTO) object;
837 
838                         // get the type
839                         if (StringUtils.isNumeric(allowedValues.get(0))) {
840 
841                             if (baseObject instanceof CodeOnly) {
842 
843                                 // can't test a CodeOnly object against numeric allowed values
844                                 if (LOG.isDebugEnabled()) {
845                                     LOG.debug(String.format("the %s '%s' is not comparable with allowed values %s",
846                                             baseObject.getClass(), ((CodeOnly) baseObject).getCode(), allowedValues));
847                                 }
848                                 addMessage(messages, rule, error, pmfmId, propertyNames);
849 
850                             } else {
851 
852                                 // the object's id must be in allowed ids
853                                 if (!allowedValues.contains(baseObject.getId().toString())) {
854                                     if (LOG.isDebugEnabled()) {
855                                         LOG.debug(String.format("the %s '%s' is not in allowed values %s",
856                                                 baseObject.getClass(), baseObject.getId(), allowedValues));
857                                     }
858                                     addMessage(messages, rule, error, pmfmId, propertyNames);
859                                 }
860                             }
861 
862                         } else {
863 
864                             if (baseObject instanceof CodeOnly) {
865 
866                                 // the object's code must be in allowed names
867                                 CodeOnly codeBaseObject = (CodeOnly) baseObject;
868                                 if (!allowedValues.contains(codeBaseObject.getCode())) {
869                                     if (LOG.isDebugEnabled()) {
870                                         LOG.debug(String.format("the %s '%s' is not in allowed values %s",
871                                                 baseObject.getClass(), codeBaseObject.getCode(), allowedValues));
872                                     }
873                                     addMessage(messages, rule, error, pmfmId, propertyNames);
874                                 }
875 
876                             } else {
877 
878                                 // the object's name must be in allowed names
879                                 if (!allowedValues.contains(baseObject.getName())) {
880                                     if (LOG.isDebugEnabled()) {
881                                         LOG.debug(String.format("the %s '%s' is not in allowed values %s",
882                                                 baseObject.getClass(), baseObject.getName(), allowedValues));
883                                     }
884                                     addMessage(messages, rule, error, pmfmId, propertyNames);
885                                 }
886                             }
887                         }
888                     } else {
889                         // the object is not controllable or null
890                         addMessage(messages, rule, error, pmfmId, propertyNames);
891                     }
892                 }
893                 break;
894             default:
895                 // Do nothing
896                 break;
897         }
898     }
899 
900     private void validCollection(Collection collection, ControlRuleDTO rule, ControlRuleMessagesBean messages, ErrorDTO error, String... propertyNames) {
901 
902         switch (ControlFunctionValues.getFunctionValue(rule.getFunction().getId())) {
903 
904             case IS_EMPTY:
905                 if (CollectionUtils.isNotEmpty(collection)) {
906                     addMessage(messages, rule, error, null, propertyNames);
907                 }
908                 break;
909 
910             case NOT_EMPTY:
911                 if (CollectionUtils.isEmpty(collection)) {
912                     addMessage(messages, rule, error, null, propertyNames);
913                 }
914                 break;
915 
916             case IS_AMONG:
917                 if (rule.getAllowedValues() != null) {
918                     if (CollectionUtils.isNotEmpty(collection)) {
919 
920                         // control each object in collection with the current IS_AMONG rule
921                         for (Object object : collection) {
922 
923                             validObject(object, rule, messages, error, propertyNames);
924 
925                             // stop loop if error occurs
926                             if (error.isWarning() || error.isError()) break;
927                         }
928 
929                     } else {
930                         addMessage(messages, rule, error, null, propertyNames);
931                     }
932                 }
933                 break;
934             default:
935                 // Do nothing
936                 break;
937         }
938 
939     }
940 
941     /**
942      * Valid date with rule control.
943      *
944      * @param dateValue     Date
945      * @param rule          Rule apply
946      * @param messages      the messages bean
947      * @param error         the Error object
948      * @param propertyNames the property names
949      */
950     private void validDate(Date dateValue, ControlRuleDTO rule, ControlRuleMessagesBean messages, ErrorDTO error, String... propertyNames) {
951 
952         switch (ControlFunctionValues.getFunctionValue(rule.getFunction().getId())) {
953 
954             case IS_EMPTY:
955                 if (dateValue != null) {
956                     addMessage(messages, rule, error, null, propertyNames);
957                 }
958                 break;
959 
960             case NOT_EMPTY:
961                 if (dateValue == null) {
962                     addMessage(messages, rule, error, null, propertyNames);
963                 }
964                 break;
965 
966             case MIN_MAX_DATE:
967                 if (dateValue == null) {
968                     addMessage(messages, rule, error, null, propertyNames);
969                 } else {
970 
971                     // Min value
972                     Date minDate = null;
973                     if (rule.getMin() != null) {
974                         minDate = (Date) rule.getMin();
975                     }
976 
977                     // Max value
978                     Date maxDate = null;
979                     if (rule.getMax() != null) {
980                         maxDate = (Date) rule.getMax();
981                     }
982 
983                     // Date between minDate & maxDate
984                     if (minDate != null && maxDate != null) {
985                         if (dateValue.before(minDate) || dateValue.after(maxDate)) {
986                             addMessage(messages, rule, error, null, propertyNames);
987                         }
988                     } else if (minDate != null) {
989                         if (dateValue.before(minDate)) {
990                             addMessage(messages, rule, error, null, propertyNames);
991                         }
992                     } else if (maxDate != null) {
993                         if (dateValue.after(maxDate)) {
994                             addMessage(messages, rule, error, null, propertyNames);
995                         }
996                     }
997                 }
998                 break;
999 
1000             default:
1001                 // Do nothing
1002                 break;
1003         }
1004 
1005     }
1006 
1007     /**
1008      * Valid local date with rule control.
1009      *
1010      * @param dateValue     Date
1011      * @param rule          Rule apply
1012      * @param messages      the messages bean
1013      * @param error         the Error object
1014      * @param propertyNames the property names
1015      */
1016     private void validLocalDate(LocalDate dateValue, ControlRuleDTO rule, ControlRuleMessagesBean messages, ErrorDTO error, String... propertyNames) {
1017 
1018         switch (ControlFunctionValues.getFunctionValue(rule.getFunction().getId())) {
1019 
1020             case IS_EMPTY:
1021                 if (dateValue != null) {
1022                     addMessage(messages, rule, error, null, propertyNames);
1023                 }
1024                 break;
1025 
1026             case NOT_EMPTY:
1027                 if (dateValue == null) {
1028                     addMessage(messages, rule, error, null, propertyNames);
1029                 }
1030                 break;
1031 
1032             case MIN_MAX_DATE:
1033                 if (dateValue == null) {
1034                     addMessage(messages, rule, error, null, propertyNames);
1035                 } else {
1036 
1037                     // Min value
1038                     LocalDate minDate = null;
1039                     if (rule.getMin() != null) {
1040                         Object min = rule.getMin();
1041                         if (min instanceof Date) {
1042                             minDate = Dates.convertToLocalDate((Date) min, configuration.getDbTimezone());
1043                         } else if (min instanceof LocalDate) {
1044                             minDate = (LocalDate) min;
1045                         } else {
1046                             throw new DaliTechnicalException(String.format("the min date in rule %s is invalid : %s", rule.getCode(), min));
1047                         }
1048                     }
1049 
1050                     // Max value
1051                     LocalDate maxDate = null;
1052                     if (rule.getMax() != null) {
1053                         Object max = rule.getMax();
1054                         if (max instanceof Date) {
1055                             maxDate = Dates.convertToLocalDate((Date) max, configuration.getDbTimezone());
1056                         } else if (max instanceof LocalDate) {
1057                             maxDate = (LocalDate) max;
1058                         } else {
1059                             throw new DaliTechnicalException(String.format("the max date in rule %s is invalid : %s", rule.getCode(), max));
1060                         }
1061                     }
1062 
1063                     // Date between minDate & maxDate
1064                     if (minDate != null && maxDate != null) {
1065                         if (dateValue.isBefore(minDate) || dateValue.isAfter(maxDate)) {
1066                             addMessage(messages, rule, error, null, propertyNames);
1067                         }
1068                     } else if (minDate != null) {
1069                         if (dateValue.isBefore(minDate)) {
1070                             addMessage(messages, rule, error, null, propertyNames);
1071                         }
1072                     } else if (maxDate != null) {
1073                         if (dateValue.isAfter(maxDate)) {
1074                             addMessage(messages, rule, error, null, propertyNames);
1075                         }
1076                     }
1077                 }
1078                 break;
1079 
1080             default:
1081                 // Do nothing
1082                 break;
1083         }
1084 
1085     }
1086 
1087     /**
1088      * Valid integer value with rule control.
1089      *
1090      * @param integerValue  Integer value to test
1091      * @param rule          Rule apply
1092      * @param messages      the messages bean
1093      * @param error         the Error object
1094      * @param propertyNames the property names
1095      */
1096     @SuppressWarnings("unused")
1097     private void validInteger(Integer integerValue, ControlRuleDTO rule, ControlRuleMessagesBean messages, ErrorDTO error, String... propertyNames) {
1098 
1099         switch (ControlFunctionValues.getFunctionValue(rule.getFunction().getId())) {
1100 
1101             case IS_EMPTY:
1102                 if (integerValue != null) {
1103                     addMessage(messages, rule, error, null, propertyNames);
1104                 }
1105                 break;
1106 
1107             case NOT_EMPTY:
1108                 if (integerValue == null) {
1109                     addMessage(messages, rule, error, null, propertyNames);
1110                 }
1111                 break;
1112 
1113             case MIN_MAX:
1114                 if (integerValue == null) {
1115                     addMessage(messages, rule, error, null, propertyNames);
1116                 } else {
1117 
1118                     // Min value
1119                     Integer minValue = null;
1120                     if (rule.getMin() != null) {
1121                         minValue = (Integer) rule.getMin();
1122                     }
1123 
1124                     // Max value
1125                     Integer maxValue = null;
1126                     if (rule.getMax() != null) {
1127                         maxValue = (Integer) rule.getMax();
1128                     }
1129 
1130                     // Integer between minValue & maxValue
1131                     if (minValue != null && maxValue != null) {
1132                         if (integerValue < minValue && integerValue > maxValue) {
1133                             addMessage(messages, rule, error, null, propertyNames);
1134                         }
1135                     }
1136                     if (minValue != null) {
1137                         if (integerValue < minValue) {
1138                             addMessage(messages, rule, error, null, propertyNames);
1139                         }
1140                     }
1141                     if (maxValue != null) {
1142                         if (integerValue > maxValue) {
1143                             addMessage(messages, rule, error, null, propertyNames);
1144                         }
1145                     }
1146                 }
1147                 break;
1148 
1149             case IS_AMONG:
1150                 if (rule.getAllowedValues() != null) {
1151 
1152                     // Integer values
1153                     final List<Integer> integerValues = new ArrayList<>();
1154 
1155                     // String values
1156                     final String[] stringValues = rule.getAllowedValues().split(configuration.getValueSeparator());
1157                     for (final String stringValue : stringValues) {
1158                         integerValues.add(Integer.parseInt(stringValue));
1159                     }
1160 
1161                     // integerValue must be into integerValues
1162                     if (!integerValues.contains(integerValue)) {
1163                         addMessage(messages, rule, error, null, propertyNames);
1164                     }
1165                 }
1166                 break;
1167 
1168             default:
1169                 // Do nothing
1170                 break;
1171         }
1172 
1173     }
1174 
1175     /**
1176      * Valid double value with rule control.
1177      *
1178      * @param doubleValue   Double value to test
1179      * @param rule          Rule apply
1180      * @param messages      the messages bean
1181      * @param error         the Error object
1182      * @param propertyNames the property names
1183      */
1184     private void validDouble(Double doubleValue, ControlRuleDTO rule, ControlRuleMessagesBean messages, ErrorDTO error, String... propertyNames) {
1185         validDouble(doubleValue, rule, messages, error, null, propertyNames);
1186     }
1187 
1188     /**
1189      * Valid double value with rule control.
1190      *
1191      * @param doubleValue   Double value to test
1192      * @param rule          Rule apply
1193      * @param messages      the messages bean
1194      * @param error         the Error object
1195      * @param pmfmId        the pmfm id (optional)
1196      * @param propertyNames the property names
1197      */
1198     private void validDouble(Double doubleValue, ControlRuleDTO rule, ControlRuleMessagesBean messages, ErrorDTO error, Integer pmfmId, String... propertyNames) {
1199 
1200         switch (ControlFunctionValues.getFunctionValue(rule.getFunction().getId())) {
1201 
1202             case IS_EMPTY:
1203                 // Object must be null
1204                 if (doubleValue != null) {
1205                     addMessage(messages, rule, error, pmfmId, propertyNames);
1206                 }
1207                 break;
1208 
1209             case NOT_EMPTY:
1210                 // Object must not be null
1211                 if (doubleValue == null) {
1212                     addMessage(messages, rule, error, pmfmId, propertyNames);
1213                 }
1214                 break;
1215 
1216             case MIN_MAX:
1217                 if (doubleValue == null) {
1218                     addMessage(messages, rule, error, pmfmId, propertyNames);
1219                 } else {
1220 
1221                     // Min value
1222                     Double minValue = null;
1223                     if (rule.getMin() != null) {
1224                         minValue = (Double) rule.getMin();
1225                     }
1226 
1227                     // Max value
1228                     Double maxValue = null;
1229                     if (rule.getMax() != null) {
1230                         maxValue = (Double) rule.getMax();
1231                     }
1232 
1233                     // Double between minValue & maxValue (if exist)
1234                     if (minValue != null && maxValue != null) {
1235                         if (doubleValue < minValue || doubleValue > maxValue) {
1236                             addMessage(messages, rule, error, pmfmId, propertyNames);
1237                         }
1238                     } else if (minValue != null) {
1239                         if (doubleValue < minValue) {
1240                             addMessage(messages, rule, error, pmfmId, propertyNames);
1241                         }
1242                     } else if (maxValue != null) {
1243                         if (doubleValue > maxValue) {
1244                             addMessage(messages, rule, error, pmfmId, propertyNames);
1245                         }
1246                     }
1247                 }
1248                 break;
1249 
1250             case IS_AMONG:
1251                 if (rule.getAllowedValues() != null) {
1252 
1253                     // Double values
1254                     final List<Double> doubleValues = new ArrayList<>();
1255 
1256                     // String values
1257                     final String[] stringValues = rule.getAllowedValues().split(configuration.getValueSeparator());
1258                     for (final String stringValue : stringValues) {
1259                         try {
1260                             doubleValues.add(Double.parseDouble(stringValue));
1261                         } catch (NumberFormatException nfe) {
1262                             if (LOG.isErrorEnabled()) {
1263                                 LOG.error(String.format("this value '%s' can't be cast as Double", stringValue));
1264                             }
1265                         }
1266                     }
1267 
1268                     // doubleValue must be into doubleValues
1269                     if (!doubleValues.contains(doubleValue)) {
1270                         if (LOG.isDebugEnabled()) {
1271                             LOG.debug(String.format("the double value %s is not in allowed values %s", doubleValue, doubleValues));
1272                         }
1273 
1274                         addMessage(messages, rule, error, pmfmId, propertyNames);
1275                     }
1276                 }
1277                 break;
1278 
1279             default:
1280                 // Do nothing
1281                 break;
1282         }
1283     }
1284 
1285     /**
1286      * Valid BigDecimal (as double) value with rule control.
1287      *
1288      * @param bigDecimalValue BigDecimal value to test
1289      * @param rule            Rule apply
1290      * @param messages        the messages bean
1291      * @param error           the Error object
1292      * @param pmfmId          the pmfm Id
1293      * @param propertyNames   the property names
1294      */
1295     private void validBigDecimal(BigDecimal bigDecimalValue, ControlRuleDTO rule, ControlRuleMessagesBean messages, ErrorDTO error, Integer pmfmId, String... propertyNames) {
1296 
1297         Double doubleValue = bigDecimalValue == null ? null : bigDecimalValue.doubleValue();
1298         validDouble(doubleValue, rule, messages, error, pmfmId, propertyNames);
1299     }
1300 
1301     /**
1302      * Valid String value with rule control.
1303      *
1304      * @param stringValue   String value to test
1305      * @param rule          Rule apply
1306      * @param messages      the messages bean
1307      * @param error         the Error object
1308      * @param propertyNames the property names
1309      */
1310     private void validString(String stringValue, ControlRuleDTO rule, ControlRuleMessagesBean messages, ErrorDTO error, String... propertyNames) {
1311 
1312         switch (ControlFunctionValues.getFunctionValue(rule.getFunction().getId())) {
1313 
1314             case IS_EMPTY:
1315                 // Object have to be null or empty
1316                 if (StringUtils.isNotBlank(stringValue)) {
1317                     addMessage(messages, rule, error, null, propertyNames);
1318                 }
1319                 break;
1320 
1321             case NOT_EMPTY:
1322                 // Object should not be null or empty
1323                 if (StringUtils.isBlank(stringValue)) {
1324                     addMessage(messages, rule, error, null, propertyNames);
1325                 }
1326                 break;
1327 
1328             case IS_AMONG:
1329                 if (rule.getAllowedValues() != null) {
1330                     // String values
1331                     final List<String> stringValues = new ArrayList<>();
1332 
1333                     // String values
1334                     final String[] stringTabValues = rule.getAllowedValues().split(configuration.getValueSeparator());
1335                     Collections.addAll(stringValues, stringTabValues);
1336 
1337                     // stringValue must be into stringValues
1338                     if (!stringValues.contains(stringValue)) {
1339                         addMessage(messages, rule, error, null, propertyNames);
1340                     }
1341                 }
1342                 break;
1343 
1344             default:
1345                 // Do nothing
1346                 break;
1347         }
1348     }
1349 
1350     private void addMessage(ControlRuleMessagesBean messages, ControlRuleDTO rule, ErrorDTO error, Integer pmfmId, String... propertyNames) {
1351         error.setPropertyName(Arrays.asList(propertyNames));
1352         error.setPmfmId(pmfmId);
1353         error.setMessage(getMessage(rule));
1354         if (rule.isBlocking()) {
1355             error.setError(true);
1356             messages.addErrorMessage(error.getMessage());
1357         } else {
1358             error.setWarning(true);
1359             messages.addWarningMessage(error.getMessage());
1360         }
1361     }
1362 
1363     private void addGeometryMessage(ControlRuleMessagesBean messages, ErrorDTO error, String message, String... propertyNames) {
1364         error.setPropertyName(Arrays.asList(propertyNames));
1365         error.setWarning(true);
1366         error.setMessage(message);
1367         messages.addWarningMessage(message);
1368     }
1369 
1370     private String getMessage(ControlRuleDTO rule) {
1371         if (StringUtils.isNotBlank(rule.getMessage())) {
1372             return rule.getMessage();
1373         }
1374 
1375         // compute a generic message
1376         return t("dali.service.control.invalid.message", rule.getCode());
1377     }
1378 
1379     private ErrorDTO newControlError(ControlElementValues controlElementValue) {
1380         ErrorDTO error = DaliBeanFactory.newErrorDTO();
1381         error.setWarning(false);
1382         error.setError(false);
1383         error.setControl(true);
1384         error.setControlElementCode(controlElementValue.getCode());
1385         return error;
1386     }
1387 
1388     private boolean isPmfmFoundInRule(PmfmDTO pmfm, ControlRuleDTO rule) {
1389 
1390         if (rule.isRulePmfmsEmpty()) {
1391             // don't check if no pmfm in rule
1392             return true;
1393         }
1394 
1395         for (RulePmfmDTO rulePmfm : rule.getRulePmfms()) {
1396 
1397             // if the quintuplet is found
1398             if (rulePmfm.getPmfm().getParameter().equals(pmfm.getParameter())
1399                     && (rulePmfm.getPmfm().getMatrix() == null || rulePmfm.getPmfm().getMatrix().equals(pmfm.getMatrix()))
1400                     && (rulePmfm.getPmfm().getFraction() == null || rulePmfm.getPmfm().getFraction().equals(pmfm.getFraction()))
1401                     && (rulePmfm.getPmfm().getMethod() == null || rulePmfm.getPmfm().getMethod().equals(pmfm.getMethod()))
1402                     && (rulePmfm.getPmfm().getUnit() == null || rulePmfm.getPmfm().getUnit().equals(pmfm.getUnit()))
1403             ) {
1404                 return true;
1405             }
1406         }
1407 
1408         return false;
1409     }
1410 
1411     private void updateSurveysControlDate(Collection<Integer> validControlledElementsPks, Collection<Integer> invalidControlledElementsPks, Date controlDate) {
1412 
1413         if (CollectionUtils.isNotEmpty(validControlledElementsPks)) {
1414             surveyDao.updateSurveysControlDate(validControlledElementsPks, controlDate);
1415         }
1416 
1417         if (CollectionUtils.isNotEmpty(invalidControlledElementsPks)) {
1418             surveyDao.updateSurveysControlDate(invalidControlledElementsPks, null);
1419         }
1420 
1421     }
1422 
1423     private void updateSamplingOperationsControlDate(Collection<Integer> validControlledElementsPks, Collection<Integer> invalidControlledElementsPks, Date controlDate) {
1424 
1425         if (CollectionUtils.isNotEmpty(validControlledElementsPks)) {
1426             samplingOperationDao.updateSamplingOperationsControlDate(validControlledElementsPks, controlDate);
1427         }
1428 
1429         if (CollectionUtils.isNotEmpty(invalidControlledElementsPks)) {
1430             samplingOperationDao.updateSamplingOperationsControlDate(invalidControlledElementsPks, null);
1431         }
1432 
1433     }
1434 
1435     private void updateMeasurementsControlDate(Collection<Integer> validControlledElementsPks, Collection<Integer> invalidControlledElementsPks, Date controlDate) {
1436 
1437         if (CollectionUtils.isNotEmpty(validControlledElementsPks)) {
1438             measurementDao.updateMeasurementsControlDate(validControlledElementsPks, controlDate);
1439         }
1440 
1441         if (CollectionUtils.isNotEmpty(invalidControlledElementsPks)) {
1442             measurementDao.updateMeasurementsControlDate(invalidControlledElementsPks, null);
1443         }
1444 
1445     }
1446 
1447     private void updateTaxonMeasurementsControlDate(Collection<Integer> validControlledElementsPks, Collection<Integer> invalidControlledElementsPks, Date controlDate) {
1448 
1449         if (CollectionUtils.isNotEmpty(validControlledElementsPks)) {
1450             measurementDao.updateTaxonMeasurementsControlDate(validControlledElementsPks, controlDate);
1451         }
1452 
1453         if (CollectionUtils.isNotEmpty(invalidControlledElementsPks)) {
1454             measurementDao.updateTaxonMeasurementsControlDate(invalidControlledElementsPks, null);
1455         }
1456 
1457     }
1458 
1459 }