View Javadoc
1   package fr.ifremer.reefdb.service.rulescontrol;
2   
3   /*
4    * #%L
5    * Reef DB :: 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.base.Joiner;
27  import com.google.common.collect.HashMultimap;
28  import com.google.common.collect.Multimap;
29  import fr.ifremer.quadrige3.core.dao.system.rule.RuleListDao;
30  import fr.ifremer.quadrige3.core.dao.technical.Assert;
31  import fr.ifremer.quadrige3.core.dao.technical.Dates;
32  import fr.ifremer.quadrige3.core.dao.technical.factorization.pmfm.AllowedQualitativeValuesMap;
33  import fr.ifremer.quadrige3.core.security.AuthenticationInfo;
34  import fr.ifremer.quadrige3.core.vo.system.rule.RuleListVO;
35  import fr.ifremer.quadrige3.synchro.service.client.SynchroRestClientService;
36  import fr.ifremer.quadrige3.ui.core.dto.referential.BaseReferentialDTO;
37  import fr.ifremer.reefdb.config.ReefDbConfiguration;
38  import fr.ifremer.reefdb.dao.system.rule.ReefDbRuleDao;
39  import fr.ifremer.reefdb.dao.system.rule.ReefDbRuleListDao;
40  import fr.ifremer.reefdb.dto.ReefDbBeanFactory;
41  import fr.ifremer.reefdb.dto.ReefDbBeans;
42  import fr.ifremer.reefdb.dto.configuration.control.*;
43  import fr.ifremer.reefdb.dto.data.measurement.MeasurementDTO;
44  import fr.ifremer.reefdb.dto.data.survey.SurveyDTO;
45  import fr.ifremer.reefdb.dto.enums.ControlElementValues;
46  import fr.ifremer.reefdb.dto.enums.ControlFeatureMeasurementValues;
47  import fr.ifremer.reefdb.dto.enums.ControlFunctionValues;
48  import fr.ifremer.reefdb.dto.referential.pmfm.QualitativeValueDTO;
49  import fr.ifremer.reefdb.service.ReefDbDataContext;
50  import fr.ifremer.reefdb.service.referential.ReferentialService;
51  import org.apache.commons.collections4.CollectionUtils;
52  import org.apache.commons.lang3.StringUtils;
53  import org.apache.commons.lang3.mutable.MutableInt;
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.util.*;
62  import java.util.stream.Collectors;
63  
64  /**
65   * Implementation rules control service.
66   */
67  @Service("reefDbRuleListService")
68  public class RuleListServiceImpl implements RuleListService {
69  
70      private static final Log LOG = LogFactory.getLog(RuleListServiceImpl.class);
71  
72      @Resource(name = "reefDbRuleListDao")
73      protected ReefDbRuleListDao ruleListDao;
74      @Resource(name = "reefDbRuleDao")
75      protected ReefDbRuleDao ruleDao;
76      @Resource(name = "reefdbDataContext")
77      protected ReefDbDataContext dataContext;
78      @Resource(name = "synchroRestClientService")
79      protected SynchroRestClientService synchroRestClientService;
80      @Resource(name = "reefdbReferentialService")
81      protected ReferentialService referentialService;
82      @Resource(name = "reefDbControlRuleService")
83      protected ControlRuleService controlRuleService;
84      @Autowired
85      protected ReefDbConfiguration configuration;
86  
87      /**
88       * {@inheritDoc}
89       */
90      @Override
91      public RuleListDTO getRuleList(String ruleListCode) {
92          Assert.notBlank(ruleListCode);
93          return ruleListDao.getRuleList(ruleListCode);
94      }
95  
96      /**
97       * {@inheritDoc}
98       */
99      @Override
100     public List<RuleListDTO> getRuleLists() {
101         return ruleListDao.getRuleLists();
102     }
103 
104     @Override
105     public List<RuleListDTO> getRuleListsForProgram(String programCode) {
106         return ruleListDao.getRuleListsForProgram(programCode);
107     }
108 
109     @Override
110     public boolean ruleListCodeExists(String ruleListCode) {
111         return ruleListDao.ruleListExists(ruleListCode);
112     }
113 
114     /**
115      * {@inheritDoc}
116      */
117     @Override
118     public void saveRuleLists(AuthenticationInfo authenticationInfo, List<? extends RuleListDTO> ruleLists) {
119 
120         Assert.notNull(ruleLists);
121 
122         // get rule lists to save
123         List<RuleListDTO> dirtyRuleList = ruleLists.stream().filter(RuleListDTO::isDirty).collect(Collectors.toList());
124 
125         // nothing to save
126         if (CollectionUtils.isEmpty(dirtyRuleList)) return;
127 
128         // Collect rule list codes
129         Set<String> ruleListCodes = dirtyRuleList.stream().map(ruleList -> {
130 
131             // preconditions
132             Assert.notBlank(ruleList.getCode());
133             Assert.notEmpty(ruleList.getPrograms());
134 
135             // save locally
136             ruleListDao.saveRuleList(ruleList, dataContext.getRecorderPersonId());
137 
138             return ruleList.getCode();
139 
140         }).collect(Collectors.toSet());
141 
142         // Save remote rule lists
143         saveRuleListsOnServer(authenticationInfo, ruleListCodes);
144 
145         // Reset newCode flags
146         dirtyRuleList.forEach(ruleList -> {
147             ruleList.setDirty(false);
148             ruleList.setNewCode(false);
149             ruleList.getControlRules().forEach(controlRule -> controlRule.setNewCode(false));
150         });
151     }
152 
153     private void saveRuleListsOnServer(AuthenticationInfo authenticationInfo, Set<String> ruleListCodes) {
154 
155         if (CollectionUtils.isEmpty(ruleListCodes)) return;
156 
157         if (LOG.isDebugEnabled()) {
158             LOG.debug(String.format("Sending rule lists [%s] to server", Joiner.on(',').join(ruleListCodes)));
159         }
160 
161         // get VOs
162         List<RuleListVO> ruleListToSend = ruleListCodes.stream()
163                 .map(ruleListCode -> (RuleListVO) ruleListDao.load(RuleListDao.TRANSFORM_RULELISTVO, ruleListCode))
164                 .filter(ruleListVO -> !ReefDbBeans.isLocalStatus(ruleListVO.getStatusCd())) // REMOTE RULE LIST ONLY !!!
165                 .collect(Collectors.toList());
166 
167         if (ruleListToSend.isEmpty()) return;
168 
169         // send to server
170         List<RuleListVO> savedRuleLists = synchroRestClientService.saveRuleLists(authenticationInfo, ruleListToSend);
171 
172         if (LOG.isDebugEnabled()) {
173             LOG.debug(String.format("Saving rule lists [%s] from server response", Joiner.on(',').join(savedRuleLists.stream().map(RuleListVO::getRuleListCd).collect(Collectors.toList()))));
174         }
175 
176         // save locally (update)
177         for (RuleListVO savedRuleList : savedRuleLists) {
178             ruleListDao.save(savedRuleList);
179         }
180 
181     }
182 
183     /**
184      * {@inheritDoc}
185      */
186     @Override
187     public void deleteRuleLists(AuthenticationInfo authenticationInfo, List<RuleListDTO> ruleLists) {
188         if (CollectionUtils.isEmpty(ruleLists)) return;
189 
190         // delete locally
191         ruleLists.forEach(ruleList -> ruleListDao.remove(ruleList.getCode()));
192 
193         // delete remote rule list
194         deleteRuleListsOnServer(authenticationInfo, ruleLists);
195     }
196 
197     private void deleteRuleListsOnServer(AuthenticationInfo authenticationInfo, List<RuleListDTO> ruleLists) {
198 
199         if (LOG.isDebugEnabled()) {
200             LOG.debug(String.format("Delete rule lists [%s] from server", Joiner.on(',').join(ruleLists.stream().map(RuleListDTO::getCode).collect(Collectors.toList()))));
201         }
202 
203         // Check if each ruleLists exists on server (it happens that a deleted ruleList had not been saved before)
204         Set<RuleListDTO> ruleListsToDelete = ruleLists.stream()
205                 .filter(ruleList -> {
206                     if (ReefDbBeans.isLocalStatus(ruleList.getStatus())) return false; // filter only remote rule lists
207                     if (synchroRestClientService.getRuleListByCode(authenticationInfo, ruleList.getCode()) != null) {
208                         return true;
209                     } else {
210                         if (LOG.isDebugEnabled()) {
211                             LOG.debug(String.format("Rule List [%s] doesn't exists on server, skip it", ruleList));
212                         }
213                         return false;
214                     }
215                 })
216                 .collect(Collectors.toSet());
217 
218         if (CollectionUtils.isNotEmpty(ruleListsToDelete)) {
219 
220             List<String> ruleListCodesToDelete = ruleListsToDelete.stream().map(RuleListDTO::getCode).collect(Collectors.toList());
221 
222             // remote delete
223             synchroRestClientService.deleteRuleLists(authenticationInfo, ruleListCodesToDelete);
224 
225             if (LOG.isDebugEnabled()) {
226                 LOG.debug(String.format("Rule lists [%s] deleted from server", Joiner.on(',').join(ruleListCodesToDelete)));
227             }
228         }
229     }
230 
231     /**
232      * {@inheritDoc}
233      */
234     @Override
235     public RuleListDTO duplicateRuleList(RuleListDTO ruleList, String newCode, boolean isDuplicateRulesEnabled) {
236 
237         RuleListDTO duplicatedRuleList = ReefDbBeans.clone(ruleList);
238 
239         duplicatedRuleList.setCode(newCode);
240         duplicatedRuleList.setNewCode(true);
241         duplicatedRuleList.setStatus(ruleList.getStatus());
242         duplicatedRuleList.setDepartments(ReefDbBeans.clone(ruleList.getDepartments()));
243         duplicatedRuleList.setPrograms(ReefDbBeans.clone(ruleList.getPrograms()));
244         duplicatedRuleList.setErrors(new ArrayList<>());
245 
246         duplicatedRuleList.setControlRules(new ArrayList<>());
247         if (isDuplicateRulesEnabled && CollectionUtils.isNotEmpty(ruleList.getControlRules())) {
248             MutableInt suffixIndex = getUniqueMutableIndex();
249             for (ControlRuleDTO controlRule : ruleList.getControlRules()) {
250                 ControlRuleDTO duplicatedControlRule = duplicateControlRule(controlRule, duplicatedRuleList);
251                 // clone preconditions if exists
252                 if (!controlRule.isPreconditionsEmpty()) {
253                     duplicatePreconditions(controlRule, duplicatedControlRule, suffixIndex);
254                 }
255                 duplicatedRuleList.addControlRules(duplicatedControlRule);
256             }
257         }
258 
259         return duplicatedRuleList;
260     }
261 
262     private ControlRuleDTO duplicateControlRule(ControlRuleDTO controlRule, RuleListDTO ruleList) {
263 
264         ControlRuleDTO duplicatedControlRule = ReefDbBeans.clone(controlRule);
265 
266         duplicatedControlRule.setCode(ruleList != null ? getNextRuleCode(ruleList) : null);
267         duplicatedControlRule.setNewCode(true);
268         duplicatedControlRule.setErrors(null);
269         duplicatedControlRule.setPreconditions(null);
270         duplicatedControlRule.setRulePmfms(null);
271 
272         // clone pmfm list and reset RULE_PMFM ids
273         for (RulePmfmDTO rulePmfm : controlRule.getRulePmfms()) {
274             RulePmfmDTO duplicatedRulePmfm = ReefDbBeans.clone(rulePmfm);
275             duplicatedRulePmfm.setId(null);
276             duplicatedControlRule.addRulePmfms(duplicatedRulePmfm);
277         }
278 
279         return duplicatedControlRule;
280     }
281 
282     private void duplicatePreconditions(ControlRuleDTO controlRule, ControlRuleDTO duplicatedControlRule, MutableInt suffixIndex) {
283 
284         for (PreconditionRuleDTO precondition : controlRule.getPreconditions()) {
285             PreconditionRuleDTO duplicatedPrecondition = ReefDbBeans.clone(precondition);
286             duplicatedPrecondition.setId(null);
287             duplicatedPrecondition.setRule(duplicatedControlRule);
288             duplicatedPrecondition.setName(duplicatedControlRule.getCode());
289             duplicatedPrecondition.setBaseRule(duplicateControlRule(precondition.getBaseRule(), null));
290             duplicatedPrecondition.getBaseRule().setCode(getNextRuleCode(duplicatedControlRule.getCode(), suffixIndex));
291             duplicatedPrecondition.setUsedRule(duplicateControlRule(precondition.getUsedRule(), null));
292             duplicatedPrecondition.getUsedRule().setCode(getNextRuleCode(duplicatedControlRule.getCode(), suffixIndex));
293             duplicatedControlRule.addPreconditions(duplicatedPrecondition);
294         }
295 
296     }
297 
298     /**
299      * {@inheritDoc}
300      */
301     @Override
302     public ControlRuleDTO newControlRule(RuleListDTO ruleList) {
303 
304         Assert.notNull(ruleList);
305         Assert.notBlank(ruleList.getCode());
306 
307         ControlRuleDTO rule = ReefDbBeanFactory.newControlRuleDTO();
308 
309         rule.setCode(getNextRuleCode(ruleList));
310         rule.setNewCode(true);
311         rule.setActive(true);
312         rule.setBlocking(false);
313 
314         return rule;
315     }
316 
317     @Override
318     public boolean ruleCodeExists(String ruleCode) {
319         Assert.notBlank(ruleCode);
320 
321         return ruleDao.ruleExists(ruleCode);
322     }
323 
324     @Override
325     public List<ControlRuleDTO> getPreconditionedControlRulesForSurvey(SurveyDTO survey) {
326         Assert.notNull(survey);
327         Assert.notNull(survey.getDate());
328         Assert.notNull(survey.getProgram());
329         Assert.notNull(survey.getRecorderDepartment());
330 
331         List<ControlRuleDTO> preconditionedRules = ruleDao.findActivePreconditionedRules(
332                 Dates.convertToDate(survey.getDate(), configuration.getDbTimezone()),
333                 survey.getProgram().getCode(),
334                 survey.getRecorderDepartment().getId());
335         buildPmfmInformation(preconditionedRules);
336         return preconditionedRules;
337     }
338 
339     @Override
340     public List<ControlRuleDTO> getGroupedControlRulesForSurvey(SurveyDTO survey) {
341         Assert.notNull(survey);
342         Assert.notNull(survey.getDate());
343         Assert.notNull(survey.getProgram());
344         Assert.notNull(survey.getRecorderDepartment());
345 
346         List<ControlRuleDTO> preconditionedRules = ruleDao.findActiveGroupedRules(
347                 Dates.convertToDate(survey.getDate(), configuration.getDbTimezone()),
348                 survey.getProgram().getCode(), survey.getRecorderDepartment().getId());
349         buildPmfmInformation(preconditionedRules);
350         return preconditionedRules;
351     }
352 
353     @Override
354     public List<ControlRuleDTO> getPreconditionedControlRulesForProgramCodes(List<String> programCodes) {
355         Assert.notEmpty(programCodes);
356 
357         List<ControlRuleDTO> preconditionedRules = ruleDao.findActivePreconditionedRules(programCodes);
358         buildPmfmInformation(preconditionedRules);
359         return preconditionedRules;
360 
361     }
362 
363     @Override
364     public void buildAllowedValuesByPmfmId(MeasurementDTO measurement,
365                                            Collection<Integer> targetPmfmIds,
366                                            Collection<PreconditionRuleDTO> preconditions,
367                                            AllowedQualitativeValuesMap allowedQualitativeValuesMap) {
368 
369         int sourcePmfmId = measurement.getPmfm().getId();
370 
371         for (PreconditionRuleDTO precondition : preconditions) {
372 
373             ControlRuleDTO sourceRule;
374             ControlRuleDTO targetRule;
375 
376             // determine base and used rule
377             if (!precondition.isBidirectional() || Objects.equals(precondition.getBaseRule().getRulePmfms(0).getPmfm().getId(), sourcePmfmId)) {
378                 // simple case for mono directional rule or if the base rule pmfm is the actual pmfm
379                 sourceRule = precondition.getBaseRule();
380                 targetRule = precondition.getUsedRule();
381             } else {
382                 // source and target are switched
383                 sourceRule = precondition.getUsedRule();
384                 targetRule = precondition.getBaseRule();
385             }
386 
387             // find now the target column, and stop here if not found
388             int targetPmfmId = targetRule.getRulePmfms(0).getPmfm().getId();
389             if (!targetPmfmIds.contains(targetPmfmId)) continue; // if the target pmfm is not part of target pmfms, continue to next precondition
390 
391             // valid source && target rules
392             if (ReefDbBeans.isQualitativeControlRule(targetRule)) {
393 
394                 if (ReefDbBeans.isQualitativeControlRule(sourceRule)) {
395                     BaseReferentialDTO sourceValue = measurement.getQualitativeValue();
396 
397                     // control the value on qualitative source rule
398                     if (controlRuleService.controlUniqueObject(sourceRule, sourceValue)) {
399 
400                         // add allowed values from the target rule
401                         allowedQualitativeValuesMap.addTargetValues(targetPmfmId, sourcePmfmId, sourceValue.getId(), getAllowedValueIds(targetRule));
402 
403                     } else {
404 
405                         // if this rule is invalid, try to reset allowed values if possible
406                         AllowedQualitativeValuesMap.AllowedValues allowedValues =
407                                 allowedQualitativeValuesMap.getAllowedValues(targetPmfmId, sourcePmfmId, sourceValue != null ? sourceValue.getId() : null);
408                         if (allowedValues != null && sourceValue == null) {
409                             allowedValues.setAllAllowed();
410                         }
411 
412                     }
413 
414                 } else if (ReefDbBeans.isNumericalControlRule(sourceRule)) {
415                     BigDecimal sourceValue = measurement.getNumericalValue();
416 
417                     // control the value on min max source rule
418                     if (controlRuleService.controlUniqueObject(sourceRule, sourceValue)) {
419 
420                         // add allowed values from the target rule
421                         allowedQualitativeValuesMap.addTargetValues(targetPmfmId, sourcePmfmId, sourceValue, getAllowedValueIds(targetRule));
422 
423                     } else {
424 
425                         // if this rule is invalid, try to reset allowed values if possible
426                         AllowedQualitativeValuesMap.AllowedValues allowedValues =
427                                 allowedQualitativeValuesMap.getAllowedValues(targetPmfmId, sourcePmfmId, sourceValue);
428                         if (allowedValues != null && sourceValue == null) {
429                             allowedValues.setAllAllowed();
430                         }
431 
432                     }
433 
434 
435                 } else {
436 
437                     // invalid source rule
438                     LOG.warn(String.format("invalid source rule %s", sourceRule.getCode()));
439                 }
440 
441             } else if (ReefDbBeans.isNumericalControlRule(targetRule)) {
442 
443                 // not implemented
444 //                LOG.warn("not yet implemented");
445 
446             } else {
447 
448                 // invalid target rule
449                 LOG.warn(String.format("invalid target rule %s", targetRule.getCode()));
450             }
451 
452         }
453     }
454 
455     private Set<Integer> getAllowedValueIds(ControlRuleDTO rule) {
456         return ReefDbBeans.getIntegerSetFromString(rule.getAllowedValues(), configuration.getValueSeparator());
457     }
458 
459     @Override
460     public Multimap<QualitativeValueDTO, QualitativeValueDTO> buildQualitativeValueMapFromPreconditions(Collection<PreconditionRuleDTO> preconditions) {
461         Multimap<QualitativeValueDTO, QualitativeValueDTO> multimap = HashMultimap.create();
462         for (PreconditionRuleDTO precondition : preconditions) {
463 
464             List<QualitativeValueDTO> baseValues = referentialService.getQualitativeValues(
465                     ReefDbBeans.getIntegerSetFromString(precondition.getBaseRule().getAllowedValues(), configuration.getValueSeparator()));
466             for (QualitativeValueDTO baseValue : baseValues) {
467                 multimap.putAll(
468                         baseValue,
469                         referentialService.getQualitativeValues(
470                                 ReefDbBeans.getIntegerSetFromString(precondition.getUsedRule().getAllowedValues(), configuration.getValueSeparator()))
471                 );
472             }
473         }
474         return multimap;
475     }
476 
477     @Override
478     public void buildPreconditionsFromQualitativeValueMap(ControlRuleDTO rule, Multimap<QualitativeValueDTO, QualitativeValueDTO> multimap) {
479         Map<String, PreconditionRuleDTO> existingRulePreconditionsByBaseValues = getRulePreconditionsByBaseValues(rule);
480 
481         MutableInt suffixIndex = getUniqueMutableIndex();
482 
483         for (QualitativeValueDTO baseValue : multimap.keySet()) {
484             PreconditionRuleDTO precondition = existingRulePreconditionsByBaseValues.remove(baseValue.getId().toString());
485             if (precondition == null) {
486                 precondition = createPreconditionRule(rule);
487             }
488             precondition.setActive(rule.isActive());
489             precondition.setName(rule.getCode());
490 
491             ControlRuleDTO baseRule = precondition.getBaseRule();
492             if (baseRule == null) {
493                 baseRule = createQualitativeControlRule(rule, suffixIndex);
494                 baseRule.addRulePmfms(cloneRulePmfm(rule.getRulePmfms(0)));
495                 baseRule.setAllowedValues(baseValue.getId().toString());
496                 precondition.setBaseRule(baseRule);
497             }
498 
499             ControlRuleDTO usedRule = precondition.getUsedRule();
500             if (usedRule == null) {
501                 usedRule = createQualitativeControlRule(rule, suffixIndex);
502                 usedRule.addRulePmfms(cloneRulePmfm(rule.getRulePmfms(1)));
503                 precondition.setUsedRule(usedRule);
504             }
505             usedRule.setAllowedValues(ReefDbBeans.joinIds(multimap.get(baseValue), configuration.getValueSeparator()));
506 
507         }
508 
509         // remove unused preconditions
510         rule.removeAllPreconditions(existingRulePreconditionsByBaseValues.values());
511 
512     }
513 
514     @Override
515     public List<NumericPreconditionDTO> buildNumericPreconditionListFromPreconditions(Collection<PreconditionRuleDTO> preconditions) {
516         List<NumericPreconditionDTO> list = new ArrayList<>();
517         for (PreconditionRuleDTO precondition : preconditions) {
518             NumericPreconditionDTO numericPrecondition = ReefDbBeanFactory.newNumericPreconditionDTO();
519             Set<Integer> qvIds = ReefDbBeans.getIntegerSetFromString(precondition.getBaseRule().getAllowedValues(), configuration.getValueSeparator());
520             if (qvIds.isEmpty()) continue;
521             numericPrecondition.setQualitativeValue(referentialService.getQualitativeValue(qvIds.iterator().next()));
522             numericPrecondition.setMin((Double) precondition.getUsedRule().getMin());
523             numericPrecondition.setMax((Double) precondition.getUsedRule().getMax());
524             list.add(numericPrecondition);
525         }
526 
527         return list;
528     }
529 
530     @Override
531     public void buildPreconditionsFromNumericPreconditionList(ControlRuleDTO rule, List<NumericPreconditionDTO> list) {
532         Map<String, PreconditionRuleDTO> existingRulePreconditionsByBaseValues = getRulePreconditionsByBaseValues(rule);
533 
534         MutableInt suffixIndex = getUniqueMutableIndex();
535 
536         for (NumericPreconditionDTO baseValue : list) {
537             PreconditionRuleDTO precondition = existingRulePreconditionsByBaseValues.remove(baseValue.getQualitativeValue().getId().toString());
538             if (precondition == null) {
539                 precondition = createPreconditionRule(rule);
540             }
541             precondition.setActive(rule.isActive());
542             precondition.setName(rule.getCode());
543 
544             ControlRuleDTO baseRule = precondition.getBaseRule();
545             if (baseRule == null) {
546                 baseRule = createQualitativeControlRule(rule, suffixIndex);
547                 baseRule.addRulePmfms(cloneRulePmfm(rule.getRulePmfms(0)));
548                 baseRule.setAllowedValues(baseValue.getQualitativeValue().getId().toString());
549                 precondition.setBaseRule(baseRule);
550             }
551 
552             ControlRuleDTO usedRule = precondition.getUsedRule();
553             if (usedRule == null) {
554                 usedRule = createNumericalControlRule(rule, suffixIndex);
555                 usedRule.addRulePmfms(cloneRulePmfm(rule.getRulePmfms(1)));
556                 precondition.setUsedRule(usedRule);
557             }
558             usedRule.setMin(baseValue.getMin());
559             usedRule.setMax(baseValue.getMax());
560         }
561 
562         // remove unused preconditions
563         rule.removeAllPreconditions(existingRulePreconditionsByBaseValues.values());
564 
565     }
566 
567     @Override
568     public void buildNotEmptyConditionalGroupsFromGroupedRule(ControlRuleDTO groupedRule) {
569 
570         // if no pmfm, just rest
571         if (groupedRule.isRulePmfmsEmpty() || !ReefDbBeans.isGroupedRule(groupedRule)) {
572             groupedRule.setGroups(null);
573             return;
574         }
575 
576         RuleGroupDTO taxonGroupRuleGroup = null;
577         RuleGroupDTO taxonRuleGroup = null;
578         RuleGroupDTO pmfmRuleGroup = null;
579         MutableInt suffixIndex = getUniqueMutableIndex();
580 
581         // try find the 3 rules
582         for (RuleGroupDTO group: groupedRule.getGroups()) {
583             if (group.getRule() == null) continue;
584             if (ControlFunctionValues.NOT_EMPTY.equals(group.getRule().getFunction())
585                     && ControlElementValues.MEASUREMENT.equals(group.getRule().getControlElement())
586                     && ControlFeatureMeasurementValues.TAXON_GROUP.equals(group.getRule().getControlFeature())) {
587                 taxonGroupRuleGroup = group;
588             }
589             if (ControlFunctionValues.NOT_EMPTY.equals(group.getRule().getFunction())
590                     && ControlElementValues.MEASUREMENT.equals(group.getRule().getControlElement())
591                     && ControlFeatureMeasurementValues.TAXON.equals(group.getRule().getControlFeature())) {
592                 taxonRuleGroup = group;
593             }
594             if (ControlFunctionValues.NOT_EMPTY.equals(group.getRule().getFunction())
595                     && ControlElementValues.MEASUREMENT.equals(group.getRule().getControlElement())
596                     && ControlFeatureMeasurementValues.PMFM.equals(group.getRule().getControlFeature())) {
597                 pmfmRuleGroup = group;
598             }
599         }
600 
601         if (taxonGroupRuleGroup == null) {
602             taxonGroupRuleGroup = ReefDbBeanFactory.newRuleGroupDTO();
603             taxonGroupRuleGroup.setIsOr(true);
604             groupedRule.addGroups(taxonGroupRuleGroup);
605 
606             ControlRuleDTO taxonGroupRule = ReefDbBeanFactory.newControlRuleDTO();
607             taxonGroupRule.setCode(getNextRuleCode(groupedRule.getCode(), suffixIndex));
608             taxonGroupRule.setFunction(ControlFunctionValues.NOT_EMPTY.toFunctionDTO());
609             taxonGroupRule.setControlElement(ControlElementValues.MEASUREMENT.toControlElementDTO());
610             taxonGroupRule.setControlFeature(ControlFeatureMeasurementValues.TAXON_GROUP.toControlFeatureDTO());
611             taxonGroupRule.setActive(false); // must be false
612             taxonGroupRuleGroup.setRule(taxonGroupRule);
613         }
614         taxonGroupRuleGroup.setName(groupedRule.getCode());
615         taxonGroupRuleGroup.setActive(groupedRule.isActive());
616         taxonGroupRuleGroup.getRule().setDescription(groupedRule.getDescription());
617         taxonGroupRuleGroup.getRule().setMessage(groupedRule.getMessage());
618 
619         if (taxonRuleGroup == null) {
620             taxonRuleGroup = ReefDbBeanFactory.newRuleGroupDTO();
621             taxonRuleGroup.setIsOr(true);
622             groupedRule.addGroups(taxonRuleGroup);
623 
624             ControlRuleDTO taxonRule = ReefDbBeanFactory.newControlRuleDTO();
625             taxonRule.setCode(getNextRuleCode(groupedRule.getCode(), suffixIndex));
626             taxonRule.setFunction(ControlFunctionValues.NOT_EMPTY.toFunctionDTO());
627             taxonRule.setControlElement(ControlElementValues.MEASUREMENT.toControlElementDTO());
628             taxonRule.setControlFeature(ControlFeatureMeasurementValues.TAXON.toControlFeatureDTO());
629             taxonRule.setActive(false); // must be false
630             taxonRuleGroup.setRule(taxonRule);
631         }
632         taxonRuleGroup.setName(groupedRule.getCode());
633         taxonRuleGroup.setActive(groupedRule.isActive());
634         taxonRuleGroup.getRule().setDescription(groupedRule.getDescription());
635         taxonRuleGroup.getRule().setMessage(groupedRule.getMessage());
636 
637         if (pmfmRuleGroup == null) {
638             pmfmRuleGroup = ReefDbBeanFactory.newRuleGroupDTO();
639             pmfmRuleGroup.setIsOr(true);
640             groupedRule.addGroups(pmfmRuleGroup);
641 
642             ControlRuleDTO pmfmRule = ReefDbBeanFactory.newControlRuleDTO();
643             pmfmRule.setCode(getNextRuleCode(groupedRule.getCode(), suffixIndex));
644             pmfmRule.setFunction(ControlFunctionValues.NOT_EMPTY.toFunctionDTO());
645             pmfmRule.setControlElement(ControlElementValues.MEASUREMENT.toControlElementDTO());
646             pmfmRule.setControlFeature(ControlFeatureMeasurementValues.PMFM.toControlFeatureDTO());
647             pmfmRule.setActive(false); // must be false
648             pmfmRuleGroup.setRule(pmfmRule);
649         }
650         pmfmRuleGroup.setName(groupedRule.getCode());
651         pmfmRuleGroup.setActive(groupedRule.isActive());
652         pmfmRuleGroup.getRule().setRulePmfms(new ArrayList<>(groupedRule.getRulePmfms()));
653         pmfmRuleGroup.getRule().setDescription(groupedRule.getDescription());
654         pmfmRuleGroup.getRule().setMessage(groupedRule.getMessage());
655 
656         // check
657         Assert.equals(groupedRule.sizeGroups(), 3, "the groups count must be 3");
658     }
659 
660     private void buildPmfmInformation(List<ControlRuleDTO> controlRules) {
661         if (controlRules != null) {
662             for (ControlRuleDTO controlRule : controlRules) {
663                 // Affect all pmfmId with the real pmfm Id from the referential: Allow better handling in UI
664                 for (RulePmfmDTO rulePmfm : controlRule.getRulePmfms()) {
665                     rulePmfm.getPmfm().setId(referentialService.getUniquePmfmIdFromPmfm(rulePmfm.getPmfm()));
666                 }
667                 for (PreconditionRuleDTO precondition : controlRule.getPreconditions()) {
668                     precondition.getBaseRule().getRulePmfms(0).getPmfm().setId(referentialService.getUniquePmfmIdFromPmfm(precondition.getBaseRule().getRulePmfms(0).getPmfm()));
669                     precondition.getUsedRule().getRulePmfms(0).getPmfm().setId(referentialService.getUniquePmfmIdFromPmfm(precondition.getUsedRule().getRulePmfms(0).getPmfm()));
670                 }
671                 for (RuleGroupDTO group: controlRule.getGroups()) {
672                     for (RulePmfmDTO rulePmfm : group.getRule().getRulePmfms()) {
673                         rulePmfm.getPmfm().setId(referentialService.getUniquePmfmIdFromPmfm(rulePmfm.getPmfm()));
674                     }
675                 }
676             }
677         }
678     }
679 
680     /**
681      * rule code = <code_liste_contrôles>_<numéro_règle_incrémenté_automatiquement>
682      *
683      * @param ruleList the rule list
684      * @return the next rule code
685      */
686     private String getNextRuleCode(RuleListDTO ruleList) {
687         Assert.notNull(ruleList);
688         int maxSuffix = 0;
689 
690         if (CollectionUtils.isNotEmpty(ruleList.getControlRules())) {
691 
692             for (ControlRuleDTO r : ruleList.getControlRules()) {
693                 String ruleCode = r.getCode();
694 
695                 if (StringUtils.isNotBlank(ruleCode)
696                         && ruleCode.startsWith(ruleList.getCode())) {
697                     int lastIndex = ruleCode.lastIndexOf('_');
698                     if (lastIndex == -1) {
699                         lastIndex = ruleCode.lastIndexOf('-');
700                     }
701 
702                     if (lastIndex != -1 && lastIndex != ruleCode.length() - 1) {
703                         String suffix = ruleCode.substring(lastIndex + 1);
704                         try {
705                             maxSuffix = Math.max(maxSuffix, Integer.parseInt(suffix));
706                         } catch (NumberFormatException e) {
707                             // skip this rule code
708                         }
709                     }
710                 }
711             }
712         }
713 
714         // return (max + 1)
715         return String.format("%s_%d", ruleList.getCode(), maxSuffix + 1);
716     }
717 
718     @Override
719     public MutableInt getUniqueMutableIndex() {
720         return new MutableInt(System.currentTimeMillis() / 1000);
721     }
722 
723     /**
724      * Compute unique rule code with target name and integer suffix
725      *
726      * @param targetName  the target name
727      * @param suffixIndex the integer suffix
728      * @return a unique and not existing code
729      */
730     @Override
731     public String getNextRuleCode(String targetName, MutableInt suffixIndex) {
732         String ruleCode;
733         do {
734             // trim to 40 characters
735             String suffix = Integer.toString(suffixIndex.getAndIncrement());
736             int suffixLength = suffix.length() + 1;
737             int totalLength = targetName.length() + suffixLength;
738             if (totalLength > 40) {
739                 targetName = targetName.substring(0, 40 - suffixLength);
740             }
741             ruleCode = String.format("%s_%s", targetName, suffix);
742         } while (ruleCodeExists(ruleCode));
743         return ruleCode;
744     }
745 
746     private Map<String, PreconditionRuleDTO> getRulePreconditionsByBaseValues(ControlRuleDTO rule) {
747         return ReefDbBeans.mapByProperty(rule.getPreconditions(), PreconditionRuleDTO.PROPERTY_BASE_RULE + "." + ControlRuleDTO.PROPERTY_ALLOWED_VALUES);
748     }
749 
750     private PreconditionRuleDTO createPreconditionRule(ControlRuleDTO parentRule) {
751         PreconditionRuleDTO precondition = ReefDbBeanFactory.newPreconditionRuleDTO();
752         precondition.setRule(parentRule);
753         precondition.setBidirectional(true); // by default
754         parentRule.addPreconditions(precondition);
755         return precondition;
756     }
757 
758     private ControlRuleDTO createQualitativeControlRule(ControlRuleDTO parentRule, MutableInt suffixIndex) {
759         ControlRuleDTO rule = ReefDbBeanFactory.newControlRuleDTO();
760         rule.setCode(getNextRuleCode(parentRule.getCode(), suffixIndex));
761         rule.setFunction(ControlFunctionValues.IS_AMONG.toFunctionDTO());
762         rule.setControlElement(ControlElementValues.MEASUREMENT.toControlElementDTO());
763         rule.setControlFeature(ControlFeatureMeasurementValues.QUALITATIVE_VALUE.toControlFeatureDTO());
764         rule.setActive(false); // must be false
765         return rule;
766     }
767 
768     private ControlRuleDTO createNumericalControlRule(ControlRuleDTO parentRule, MutableInt suffixIndex) {
769         ControlRuleDTO rule = ReefDbBeanFactory.newControlRuleDTO();
770         rule.setCode(getNextRuleCode(parentRule.getCode(), suffixIndex));
771         rule.setFunction(ControlFunctionValues.MIN_MAX.toFunctionDTO());
772         rule.setControlElement(ControlElementValues.MEASUREMENT.toControlElementDTO());
773         rule.setControlFeature(ControlFeatureMeasurementValues.NUMERICAL_VALUE.toControlFeatureDTO());
774         rule.setActive(false); // must be false
775         return rule;
776     }
777 
778     private RulePmfmDTO cloneRulePmfm(RulePmfmDTO rulePmfm) {
779         RulePmfmDTO result = ReefDbBeanFactory.newRulePmfmDTO();
780         result.setPmfm(ReefDbBeans.clone(rulePmfm.getPmfm()));
781         return result;
782     }
783 
784 }