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