View Javadoc
1   package fr.ifremer.reefdb.dao.system.rule;
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.collect.ImmutableList;
25  import com.google.common.collect.Lists;
26  import fr.ifremer.quadrige3.core.dao.system.rule.*;
27  import fr.ifremer.quadrige3.core.dao.technical.Assert;
28  import fr.ifremer.quadrige3.core.dao.technical.QuadrigeEnumerationDef;
29  import fr.ifremer.quadrige3.core.dao.technical.hibernate.TemporaryDataHelper;
30  import fr.ifremer.quadrige3.core.exception.QuadrigeTechnicalException;
31  import fr.ifremer.reefdb.config.ReefDbConfiguration;
32  import fr.ifremer.reefdb.dao.technical.Daos;
33  import fr.ifremer.reefdb.dto.FunctionDTO;
34  import fr.ifremer.reefdb.dto.ReefDbBeanFactory;
35  import fr.ifremer.reefdb.dto.ReefDbBeans;
36  import fr.ifremer.reefdb.dto.configuration.control.*;
37  import fr.ifremer.reefdb.dto.enums.*;
38  import org.apache.commons.collections4.CollectionUtils;
39  import org.apache.commons.collections4.ListValuedMap;
40  import org.apache.commons.collections4.MapUtils;
41  import org.apache.commons.collections4.MultiMapUtils;
42  import org.apache.commons.lang3.StringUtils;
43  import org.apache.commons.lang3.mutable.MutableBoolean;
44  import org.apache.commons.lang3.time.DateUtils;
45  import org.apache.commons.logging.Log;
46  import org.apache.commons.logging.LogFactory;
47  import org.hibernate.Query;
48  import org.hibernate.SessionFactory;
49  import org.hibernate.type.DateType;
50  import org.hibernate.type.IntegerType;
51  import org.hibernate.type.StringType;
52  import org.nuiton.i18n.I18n;
53  import org.nuiton.util.DateUtil;
54  import org.springframework.beans.factory.InitializingBean;
55  import org.springframework.beans.factory.annotation.Autowired;
56  import org.springframework.dao.DataRetrievalFailureException;
57  import org.springframework.stereotype.Repository;
58  
59  import javax.annotation.Resource;
60  import java.text.ParseException;
61  import java.util.*;
62  
63  /**
64   * @author peck7 on 03/07/2018.
65   */
66  @Repository("reefDbRuleDao")
67  public class ReefDbRuleDaoImpl extends RuleDaoImpl implements ReefDbRuleDao, InitializingBean {
68  
69      private static final Log LOG = LogFactory.getLog(ReefDbRuleDaoImpl.class);
70      public static final String DATE_PATTERN = "yyyy-MM-dd";
71  
72      @Resource
73      protected ReefDbConfiguration config;
74  
75      @Resource(name = "rulePreconditionDao")
76      private RulePreconditionExtendDao rulePreconditionDao;
77      @Resource(name = "ruleGroupDao")
78      private RuleGroupExtendDao ruleGroupDao;
79      @Resource(name = "reefDbRulePmfmDao")
80      protected ReefDbRulePmfmDao rulePmfmDao;
81      @Resource(name = "ruleParameterDao")
82      protected RuleParameterDao ruleParameterDao;
83  
84      private int functionIdMinMax;
85      private int functionIdIn;
86      private int functionIdDateMinMax;
87      private int functionParameterIdMin;
88      private int functionParameterIdMax;
89      private int functionParameterIdIn;
90      private int functionParameterIdDateMin;
91      private int functionParameterIdDateMax;
92  
93      /**
94       * Constructor used by Spring
95       *
96       * @param sessionFactory sessionFactory
97       */
98      @Autowired
99      public ReefDbRuleDaoImpl(SessionFactory sessionFactory) {
100         super(sessionFactory);
101     }
102 
103     @Override
104     public void afterPropertiesSet() {
105         initConstants();
106     }
107 
108     private void initConstants() {
109 
110         functionIdMinMax = ControlFunctionValues.MIN_MAX.getFunctionId();
111         functionIdDateMinMax = ControlFunctionValues.MIN_MAX_DATE.getFunctionId();
112         functionIdIn = ControlFunctionValues.IS_AMONG.getFunctionId();
113 //        functionIdEmpty = ControlFunctionValues.IS_EMPTY.getFunctionId();
114 //        functionIdNotEmpty = ControlFunctionValues.NOT_EMPTY.getFunctionId();
115 
116         functionParameterIdMin = FunctionParameterId.MIN.getValue();
117         functionParameterIdMax = FunctionParameterId.MAX.getValue();
118         functionParameterIdIn = FunctionParameterId.IN.getValue();
119         functionParameterIdDateMin = FunctionParameterId.DATE_MIN.getValue();
120         functionParameterIdDateMax = FunctionParameterId.DATE_MAX.getValue();
121     }
122 
123     @Override
124     public List<ControlRuleDTO> getControlRulesByRuleListCode(String ruleListCode, boolean onlyActive, MutableBoolean incompatibleRule) {
125         Assert.notBlank(ruleListCode);
126 
127         Iterator<Object[]> it = queryIterator("rulesForControlOnlyByRuleListCode",
128                 "ruleListCode", StringType.INSTANCE, ruleListCode,
129                 "functionIdMinMax", IntegerType.INSTANCE, functionIdMinMax,
130                 "functionParameterIdMin", IntegerType.INSTANCE, functionParameterIdMin,
131                 "functionParameterIdMax", IntegerType.INSTANCE, functionParameterIdMax,
132                 "functionIdDateMinMax", IntegerType.INSTANCE, functionIdDateMinMax,
133                 "functionParameterIdDateMin", IntegerType.INSTANCE, functionParameterIdDateMin,
134                 "functionParameterIdDateMax", IntegerType.INSTANCE, functionParameterIdDateMax,
135                 "functionIdIn", IntegerType.INSTANCE, functionIdIn,
136                 "functionParameterIdIn", IntegerType.INSTANCE, functionParameterIdIn,
137                 "ruleIsActive", StringType.INSTANCE, onlyActive ? Daos.convertToString(true) : null);
138 
139         List<ControlRuleDTO> result = Lists.newArrayList();
140         while (it.hasNext()) {
141             Object[] source = it.next();
142             ControlRuleDTO rule = toRuleDTO(Arrays.asList(source).iterator());
143             if (rule != null) {
144                 // Add this rule if valid (!= null)
145                 result.add(rule);
146             } else if (incompatibleRule != null) {
147                 incompatibleRule.setTrue();
148             }
149         }
150 
151         for (ControlRuleDTO rule : result) {
152 
153             // add pmfm list
154             rule.setRulePmfms(rulePmfmDao.getRulePmfmByRuleCode(rule.getCode()));
155         }
156 
157         return result;
158 
159     }
160 
161     @Override
162     public List<ControlRuleDTO> getPreconditionedRulesByRuleListCode(String ruleListCode, boolean onlyActive, MutableBoolean incompatibleRule) {
163 
164         List<ControlRuleDTO> result = new ArrayList<>();
165         List<PreconditionRuleDTO> preconditionRules = getPreconditionsByRuleListCode(ruleListCode, onlyActive, incompatibleRule);
166 
167         // group preconditions by their name
168         ListValuedMap<String, PreconditionRuleDTO> preconditionRulesByName = MultiMapUtils.newListValuedHashMap();
169         for (PreconditionRuleDTO preconditionRule : preconditionRules) {
170             preconditionRulesByName.put(preconditionRule.getName(), preconditionRule);
171         }
172 
173         // iterate on the map to created preconditioned ('virtual') control rule
174         for (String name : preconditionRulesByName.keySet()) {
175             ControlRuleDTO preconditionedControlRule = ReefDbBeanFactory.newControlRuleDTO();
176             // set the name as rule code
177             preconditionedControlRule.setCode(name);
178             // set control element to measurement
179             preconditionedControlRule.setControlElement(ControlElementValues.MEASUREMENT.toControlElementDTO());
180             // set control attribute to qualitative value
181             preconditionedControlRule.setControlFeature(ControlFeatureMeasurementValues.QUALITATIVE_VALUE.toControlFeatureDTO());
182 
183             // add all preconditions
184             boolean active = true;
185             ControlFunctionValues function = null;
186             Set<String> pmfmPairKeys = new HashSet<>();
187             for (PreconditionRuleDTO preconditionRule: preconditionRulesByName.get(name)) {
188                 active &= preconditionRule.isActive();
189 
190                 // find the function
191                 if (function == null) {
192                     function = getFunction(preconditionRule);
193                 } else {
194                     ControlFunctionValues thisFunction = getFunction(preconditionRule);
195                     if (!function.equals(thisFunction)) {
196                         if (LOG.isWarnEnabled()) {
197                             LOG.warn(String.format("Different precondition functions found (preconditionLb=%s)", preconditionedControlRule.getCode()));
198                         }
199                         if (incompatibleRule != null) incompatibleRule.setTrue();
200                         // skip it
201                         continue;
202                     }
203                 }
204 
205                 // compute pmfm pair key and add it to the hash set
206                 pmfmPairKeys.add(String.format("%s#%s",
207                     getRulePmfmKey(preconditionRule.getBaseRule().getRulePmfms(0)),
208                     getRulePmfmKey(preconditionRule.getUsedRule().getRulePmfms(0))));
209                 // affect parent link
210                 preconditionRule.setRule(preconditionedControlRule);
211                 preconditionedControlRule.addPreconditions(preconditionRule);
212             }
213             // set the active flag (if 1 precondition is inactive, all control rule is inactive)
214             preconditionedControlRule.setActive(active);
215             // set the function
216             if (function == null) {
217                 if (LOG.isWarnEnabled()) {
218                     LOG.warn(String.format("Precondition function not found (preconditionLb=%s)", preconditionedControlRule.getCode()));
219                 }
220                 if (incompatibleRule != null) incompatibleRule.setTrue();
221                 // skip it
222                 continue;
223             }
224             preconditionedControlRule.setFunction(function.toFunctionDTO());
225 
226             // check rule pmfm equality
227             if (pmfmPairKeys.size() > 1) {
228                 // if more than 1 pmfm is detected, it is an incompatible rule precondition
229                 if (LOG.isWarnEnabled()) {
230                     LOG.warn(String.format("Only 1 pair of PMFMU is allowed in preconditioned rules (preconditionLb=%s)",
231                             preconditionedControlRule.getCode()));
232                 }
233                 if (incompatibleRule != null) incompatibleRule.setTrue();
234                 // skip it
235                 continue;
236             }
237             // populate the rulePmfm from the first precondition
238             preconditionedControlRule.addRulePmfms(preconditionedControlRule.getPreconditions(0).getBaseRule().getRulePmfms(0));
239             preconditionedControlRule.addRulePmfms(preconditionedControlRule.getPreconditions(0).getUsedRule().getRulePmfms(0));
240 
241             result.add(preconditionedControlRule);
242         }
243 
244         return result;
245     }
246 
247     /**
248      *  Create virtual control rule with rule groups
249      *  For now only the group with TaxonGroup, Taxon and PMFM value is allowed (with OR logical)
250      *
251      * @param ruleListCode
252      * @param onlyActive
253      * @param incompatibleRule
254      * @return
255      */
256     @Override
257     public List<ControlRuleDTO> getGroupedRulesByRuleListCode(String ruleListCode, boolean onlyActive, MutableBoolean incompatibleRule) {
258 
259         List<ControlRuleDTO> result = new ArrayList<>();
260         List<RuleGroupDTO> groups = getGroupsByRuleListCode(ruleListCode, onlyActive, incompatibleRule);
261 
262         // group groups by their name
263         ListValuedMap<String, RuleGroupDTO> groupsByName = MultiMapUtils.newListValuedHashMap();
264         for (RuleGroupDTO group : groups) {
265             groupsByName.put(group.getName(), group);
266         }
267 
268         // iterate on the map to created grouped ('virtual') control rule
269         for (String name : groupsByName.keySet()) {
270             ControlRuleDTO groupedRule = ReefDbBeanFactory.newControlRuleDTO();
271             groupedRule.setCode(name);
272             // set control element to measurement
273             groupedRule.setControlElement(ControlElementValues.MEASUREMENT.toControlElementDTO());
274             // set control attribute to PMFM
275             groupedRule.setControlFeature(ControlFeatureMeasurementValues.PMFM.toControlFeatureDTO());
276             // set function (only this function is allowed)
277             groupedRule.setFunction(ControlFunctionValues.NOT_EMPTY_CONDITIONAL.toFunctionDTO());
278 
279             boolean active = true;
280             // check and add all rules
281             for (RuleGroupDTO group: groupsByName.get(name)) {
282                 active &= group.isActive();
283 
284                 if (!group.isIsOr()) {
285                     if (LOG.isWarnEnabled()) {
286                         LOG.warn(String.format("a group with AND logical operator found (ruleGroupLb=%s), group ignored", group.getName()));
287                     }
288                     if (incompatibleRule != null) incompatibleRule.setTrue();
289                     // skip it
290                     continue;
291                 }
292 
293                 groupedRule.addGroups(group);
294             }
295             // set the active flag (if 1 group is inactive, all control rule is inactive)
296             groupedRule.setActive(active);
297 
298             // check the 3 rules = taxon_group not empty, taxon not empty and a pmfm value not empty
299             Collection<RulePmfmDTO> rulePmfms = null;
300             if (groupedRule.sizeGroups() == 3) {
301                 boolean taxonGroupRuleFound = false;
302                 boolean taxonRuleFound = false;
303                 boolean pmfmRuleFound = false;
304                 for (RuleGroupDTO group : groupedRule.getGroups()) {
305                     if (ControlFunctionValues.NOT_EMPTY.equals(group.getRule().getFunction())
306                         && ControlElementValues.MEASUREMENT.equals(group.getRule().getControlElement())
307                         && ControlFeatureMeasurementValues.TAXON_GROUP.equals(group.getRule().getControlFeature())) {
308                         taxonGroupRuleFound = true;
309                     }
310                     if (ControlFunctionValues.NOT_EMPTY.equals(group.getRule().getFunction())
311                         && ControlElementValues.MEASUREMENT.equals(group.getRule().getControlElement())
312                         && ControlFeatureMeasurementValues.TAXON.equals(group.getRule().getControlFeature())) {
313                         taxonRuleFound = true;
314                     }
315                     if (ControlFunctionValues.NOT_EMPTY.equals(group.getRule().getFunction())
316                         && ControlElementValues.MEASUREMENT.equals(group.getRule().getControlElement())
317                         && ControlFeatureMeasurementValues.PMFM.equals(group.getRule().getControlFeature())) {
318                         pmfmRuleFound = true;
319                         rulePmfms = group.getRule().getRulePmfms();
320                     }
321                 }
322                 if (!taxonGroupRuleFound || !taxonRuleFound || !pmfmRuleFound) {
323                     if (LOG.isWarnEnabled()) {
324                         LOG.warn(String.format("the 3 rules have not been found : taxon_group=%s taxon=%s pmfmu=%s (ruleGroupLb=%s), group ignored",
325                                 taxonGroupRuleFound, taxonRuleFound, pmfmRuleFound,
326                                 groupedRule.getCode()));
327                     }
328                     if (incompatibleRule != null) incompatibleRule.setTrue();
329                     continue;
330                 }
331 
332             } else {
333                 if (LOG.isWarnEnabled()) {
334                     LOG.warn(String.format("a group without 3 rules has been found (ruleGroupLb=%s), group ignored", groupedRule.getCode()));
335                 }
336                 if (incompatibleRule != null) incompatibleRule.setTrue();
337                 continue;
338             }
339 
340             // populate the pmfms on virtual rule
341             groupedRule.addAllRulePmfms(rulePmfms);
342 
343             // set description and message from first rule (should be the same on all rules)
344             groupedRule.setDescription(groupedRule.getGroups(0).getRule().getDescription());
345             groupedRule.setMessage(groupedRule.getGroups(0).getRule().getMessage());
346 
347             result.add(groupedRule);
348         }
349 
350         return result;
351     }
352 
353     /**
354      * {@inheritDoc}
355      */
356     @Override
357     public List<ControlRuleDTO> findActiveControlRules(Date date, String programCode, Integer departmentId) {
358 
359         List<ControlRuleDTO> result = Lists.newArrayList();
360         Iterator<String> ruleListCodeIt = getActiveRuleListCodes(date, programCode, departmentId);
361         while (ruleListCodeIt.hasNext()) {
362             String ruleListCode = ruleListCodeIt.next();
363             result.addAll(getControlRulesByRuleListCode(ruleListCode, true /*active only*/, null));
364         }
365 
366         return result;
367     }
368 
369     @Override
370     public List<ControlRuleDTO> findActivePreconditionedRules(Date date, String programCode, Integer departmentId) {
371 
372         return findActivePreconditionedRules(getActiveRuleListCodes(date, programCode, departmentId));
373     }
374 
375     @Override
376     public List<ControlRuleDTO> findActiveGroupedRules(Date date, String programCode, Integer departmentId) {
377 
378         return findActiveGroupedRules(getActiveRuleListCodes(date, programCode, departmentId));
379     }
380 
381     @Override
382     public List<ControlRuleDTO> findActivePreconditionedRules(List<String> programCodes) {
383 
384         return findActivePreconditionedRules(getActiveRuleListCodes(programCodes));
385     }
386 
387     private List<ControlRuleDTO> findActivePreconditionedRules(Iterator<String> ruleListCodeIt) {
388         List<ControlRuleDTO> result = Lists.newArrayList();
389         while (ruleListCodeIt.hasNext()) {
390             String ruleListCode = ruleListCodeIt.next();
391             result.addAll(getPreconditionedRulesByRuleListCode(ruleListCode, true /*active only*/, null));
392         }
393         return result;
394     }
395 
396     private List<ControlRuleDTO> findActiveGroupedRules(Iterator<String> ruleListCodeIt) {
397         List<ControlRuleDTO> result = Lists.newArrayList();
398         while (ruleListCodeIt.hasNext()) {
399             String ruleListCode = ruleListCodeIt.next();
400             result.addAll(getGroupedRulesByRuleListCode(ruleListCode, true /*active only*/, null));
401         }
402         return result;
403     }
404 
405 
406     @Override
407     public void save(final ControlRuleDTO source, String ruleListCd) {
408         Assert.notNull(source);
409         Assert.notNull(source.getFunction());
410         Assert.notNull(source.getFunction().getId());
411 
412         // Parent
413         RuleList parent = get(RuleListImpl.class, ruleListCd);
414 
415         if (source.getFunction().getId() > 0) {
416             save(source, parent);
417         } else if (!source.isPreconditionsEmpty()) {
418             saveWithPreconditions(source, parent);
419         } else if (!source.isGroupsEmpty()) {
420             saveWithGroups(source, parent);
421         }
422     }
423 
424     private void save(ControlRuleDTO source, RuleList parent) {
425 
426         Assert.isTrue(source.isPreconditionsEmpty());
427         Assert.isTrue(source.isGroupsEmpty());
428 
429         // delete previous rule with the same code (Mantis #45374) but not itself (Mantis #45919)
430         deleteObsoleteRulePreconditions(source.getCode());
431         deleteObsoleteRuleGroups(source.getCode());
432 
433         // Load entity
434         Rule entity = get(source.getCode());
435         boolean isNew = false;
436         if (entity == null) {
437             entity = Rule.Factory.newInstance();
438             entity.setRuleCd(source.getCode());
439             parent.addRules(entity);
440             entity.setRuleList(parent);
441             isNew = true;
442         }
443 
444         // DTO -> VO
445         beanToEntity(source, entity);
446 
447         // Save it
448         if (isNew) {
449             getSession().save(entity);
450         } else {
451             getSession().update(entity);
452         }
453 
454         // Save rule parameters
455         {
456             Integer functionId = source.getFunction().getId();
457             Map<Integer, RuleParameter> ruleParametersByFunctionParamIds = ReefDbBeans.mapByProperty(entity.getRuleParameters(), "functionParameter.functionParId");
458             final Iterator<Integer> ruleParIdSequence = TemporaryDataHelper.getNewNegativeTemporarySequence(getSession(), RuleParameterImpl.class, true);
459 
460             // MinMax function
461             if (functionId.equals(functionIdMinMax)) {
462                 if (source.getMin() != null && source.getMin() instanceof Double) {
463                     updateOrCreateRuleParameter(entity,
464                             ruleParIdSequence,
465                             ruleParametersByFunctionParamIds,
466                             functionParameterIdMin,
467                             source.getMin().toString());
468                 }
469                 if (source.getMax() != null && source.getMax() instanceof Double) {
470                     updateOrCreateRuleParameter(entity,
471                             ruleParIdSequence,
472                             ruleParametersByFunctionParamIds,
473                             functionParameterIdMax,
474                             source.getMax().toString());
475                 }
476             }
477 
478             // MinMax Date function
479             else if (functionId.equals(functionIdDateMinMax)) {
480                 if (source.getMin() != null && source.getMin() instanceof Date) {
481                     updateOrCreateRuleParameter(entity,
482                             ruleParIdSequence,
483                             ruleParametersByFunctionParamIds,
484                             functionParameterIdDateMin,
485                             DateUtil.formatDate((Date) source.getMin(), DATE_PATTERN)
486                     );
487                 }
488                 if (source.getMax() != null && source.getMax() instanceof Date) {
489                     updateOrCreateRuleParameter(entity,
490                             ruleParIdSequence,
491                             ruleParametersByFunctionParamIds,
492                             functionParameterIdDateMax,
493                             DateUtil.formatDate((Date) source.getMax(), DATE_PATTERN)
494                     );
495                 }
496             }
497 
498             // In function
499             else if (functionId.equals(functionIdIn) && source.getAllowedValues() != null) {
500                 updateOrCreateRuleParameter(entity,
501                         ruleParIdSequence,
502                         ruleParametersByFunctionParamIds,
503                         functionParameterIdIn,
504                         source.getAllowedValues());
505             }
506 
507             // Remove unused rule parameters
508             if (MapUtils.isNotEmpty(ruleParametersByFunctionParamIds)) {
509                 ruleParameterDao.remove(ruleParametersByFunctionParamIds.values());
510                 entity.getRuleParameters().removeAll(ruleParametersByFunctionParamIds.values());
511             }
512 
513             getSession().update(entity);
514         }
515 
516         getSession().flush();
517 
518         // Save rule pmfms
519         {
520             final Map<Integer, RulePmfm> rulePmfmsToRemove = ReefDbBeans.mapByProperty(entity.getRulePmfms(), "rulePmfmId");
521             if (CollectionUtils.isNotEmpty(source.getRulePmfms())) {
522                 source.getRulePmfms().forEach(rulePmfm -> {
523                     rulePmfm = rulePmfmDao.save(rulePmfm, source.getCode());
524                     rulePmfmsToRemove.remove(rulePmfm.getId());
525                 });
526             }
527 
528             // Remove unused rule pmfms
529             if (MapUtils.isNotEmpty(rulePmfmsToRemove)) {
530                 rulePmfmDao.removeByIds(rulePmfmsToRemove.keySet());
531                 entity.getRulePmfms().removeAll(rulePmfmsToRemove.values());
532             }
533         }
534 
535         // reset this flag to avoid code changes
536         source.setNewCode(false);
537     }
538 
539     private void saveWithPreconditions(ControlRuleDTO source, RuleList parent) {
540 
541         Assert.isTrue(!source.isPreconditionsEmpty());
542         Assert.isTrue(source.isGroupsEmpty());
543 
544         // delete previous rule with the same code (Mantis #45374)
545         deleteObsoleteRule(source.getCode());
546         // delete also obsolete rule groups
547         deleteObsoleteRuleGroups(source.getCode());
548 
549         final Iterator<Integer> preconditionIdSequence = TemporaryDataHelper.getNewNegativeTemporarySequence(getSession(), RulePreconditionImpl.class, true);
550 
551         for (PreconditionRuleDTO precondition: source.getPreconditions()) {
552 
553             // save base and used rules first
554             save(precondition.getBaseRule(), parent);
555             save(precondition.getUsedRule(), parent);
556 
557             RulePrecondition entity = precondition.getId() != null ? rulePreconditionDao.get(precondition.getId()) : null;
558             boolean isNew = false;
559             if (entity == null) {
560                 entity = RulePrecondition.Factory.newInstance();
561                 entity.setRulePrecondId(preconditionIdSequence.next());
562                 isNew = true;
563             }
564 
565             Rule baseRule = get(precondition.getBaseRule().getCode());
566             Assert.notNull(baseRule);
567             Rule usedRule = get(precondition.getUsedRule().getCode());
568             Assert.notNull(usedRule);
569 
570             entity.setRule(baseRule);
571             entity.setUsedRule(usedRule);
572             // the precondition is active if user active the virtual rule
573             entity.setRulePrecondIsActive(Daos.convertToString(source.isActive()));
574             entity.setRulePrecondIsBidir(Daos.convertToString(precondition.isBidirectional()));
575             // use the virtual rule code
576             entity.setRulePrecondLb(source.getCode());
577 
578             if (isNew) {
579                 getSession().save(entity);
580                 precondition.setId(entity.getRulePrecondId());
581             } else {
582                 getSession().update(entity);
583             }
584         }
585 
586     }
587 
588     private void saveWithGroups(ControlRuleDTO source, RuleList parent) {
589 
590         Assert.isTrue(!source.isGroupsEmpty());
591         Assert.isTrue(source.isPreconditionsEmpty());
592         final Iterator<Integer> groupIdSequence = TemporaryDataHelper.getNewNegativeTemporarySequence(getSession(), RuleGroupImpl.class, true);
593 
594         // delete previous rule with the same code (Mantis #45374)
595         deleteObsoleteRule(source.getCode());
596         // delete also obsolete rule preconditions
597         deleteObsoleteRulePreconditions(source.getCode());
598 
599         for (RuleGroupDTO group: source.getGroups()) {
600 
601             // get the rule
602             ControlRuleDTO groupedRule = group.getRule();
603             // copy some properties from the virtual rule
604             groupedRule.setDescription(source.getDescription());
605             groupedRule.setMessage(source.getMessage());
606             // save grouped rule first
607             save(groupedRule, parent);
608 
609             RuleGroup entity = group.getId() != null ? ruleGroupDao.get(group.getId()) : null;
610             boolean isNew = false;
611             if (entity == null) {
612                 entity = RuleGroup.Factory.newInstance();
613                 entity.setRuleGroupId(groupIdSequence.next());
614                 isNew = true;
615             }
616 
617             Rule rule = get(group.getRule().getCode());
618             Assert.notNull(rule);
619 
620             entity.setRule(rule);
621             // the precondition is active if user active the virtual rule
622             entity.setRuleGroupIsActive(Daos.convertToString(source.isActive()));
623             entity.setRuleGroupIsOr(Daos.convertToString(group.isIsOr()));
624             // use the virtual rule code
625             entity.setRuleGroupLb(source.getCode());
626 
627             if (isNew) {
628                 getSession().save(entity);
629                 group.setId(entity.getRuleGroupId());
630             } else {
631                 getSession().update(entity);
632             }
633         }
634     }
635 
636     /**
637      * {@inheritDoc}
638      */
639     @Override
640     public List<FunctionDTO> getAllFunction() {
641 
642         // TODO by query or by FunctionDao ??
643 
644         Iterator<Function> it = queryIteratorTyped("allFunctionEntity");
645 
646         List<FunctionDTO> result = Lists.newArrayList();
647         while (it.hasNext()) {
648             Function source = it.next();
649             FunctionDTO target = toFunctionDTO(source, true /*Could return null if not found in rule function enumeration*/);
650             if (target != null) {
651                 result.add(target);
652             }
653         }
654 
655         return result;
656     }
657 
658     @Override
659     public boolean ruleExists(String ruleCode) {
660         Assert.notBlank(ruleCode);
661 
662         return queryUnique("ruleExists", "ruleCode", StringType.INSTANCE, ruleCode) != null;
663     }
664 
665     /* -- private methods -- */
666 
667     private void deleteObsoleteRule(String ruleCode) {
668         Rule rule = get(ruleCode);
669         if (rule != null) {
670             remove(rule);
671         }
672     }
673 
674     private void deleteObsoleteRulePreconditions(String ruleCode) {
675         List<RulePrecondition> rulePreconditions = rulePreconditionDao.getByCode(ruleCode);
676         if (CollectionUtils.isNotEmpty(rulePreconditions)) {
677             rulePreconditionDao.remove(rulePreconditions);
678         }
679     }
680 
681     private void deleteObsoleteRuleGroups(String ruleCode) {
682         List<RuleGroup> ruleGroups = ruleGroupDao.getByCode(ruleCode);
683         if (CollectionUtils.isNotEmpty(ruleGroups)) {
684             ruleGroupDao.remove(ruleGroups);
685         }
686     }
687 
688     private Iterator<String> getActiveRuleListCodes(Date date, String programCode, Integer departmentId) {
689 
690         // find active rule list corresponding to criteria
691         return queryIteratorTyped("activeRuleListCodesByCriteria",
692                 "programCode", StringType.INSTANCE, programCode,
693                 "departmentId", IntegerType.INSTANCE, departmentId,
694                 "date", DateType.INSTANCE, date);
695 
696     }
697 
698     @SuppressWarnings("unchecked")
699     private Iterator<String> getActiveRuleListCodes(List<String> programCodes) {
700 
701         // find active rule list corresponding to program codes
702         Query query = createQuery("activeRuleListCodesByProgramCodes").setParameterList("programCodes", programCodes);
703         return query.iterate();
704 
705     }
706 
707     private ControlRuleDTO getRule(String ruleCode) {
708 
709         Object[] row = queryUnique("ruleByCode",
710                 "ruleCode", StringType.INSTANCE, ruleCode,
711                 "functionIdMinMax", IntegerType.INSTANCE, functionIdMinMax,
712                 "functionParameterIdMin", IntegerType.INSTANCE, functionParameterIdMin,
713                 "functionParameterIdMax", IntegerType.INSTANCE, functionParameterIdMax,
714                 "functionIdDateMinMax", IntegerType.INSTANCE, functionIdDateMinMax,
715                 "functionParameterIdDateMin", IntegerType.INSTANCE, functionParameterIdDateMin,
716                 "functionParameterIdDateMax", IntegerType.INSTANCE, functionParameterIdDateMax,
717                 "functionIdIn", IntegerType.INSTANCE, functionIdIn,
718                 "functionParameterIdIn", IntegerType.INSTANCE, functionParameterIdIn);
719 
720         if (row == null)
721             throw new DataRetrievalFailureException("can't load rule with code = " + ruleCode);
722 
723         // the returned value can be null is rule is incompatible
724         ControlRuleDTO controlRule = toRuleDTO(Arrays.asList(row).iterator());
725         if (controlRule != null) {
726             controlRule.setRulePmfms(rulePmfmDao.getRulePmfmByRuleCode(controlRule.getCode()));
727         }
728         return controlRule;
729     }
730 
731     private List<PreconditionRuleDTO> getPreconditionsByRuleListCode(String ruleListCode, boolean onlyActive, MutableBoolean incompatibleRule) {
732 
733         Iterator<Object[]> rows = queryIterator("rulePreconditionsByRuleListCode",
734                 "ruleListCode", StringType.INSTANCE, ruleListCode,
735                 "rulePreconditionIsActive", StringType.INSTANCE, onlyActive ? Daos.convertToString(true) : null);
736 
737         List<PreconditionRuleDTO> result = new ArrayList<>();
738         while (rows.hasNext()) {
739             Object[] row = rows.next();
740             PreconditionRuleDTO preconditionRule = toPreconditionRuleDTO(Arrays.asList(row).iterator());
741             if (preconditionRule != null) {
742                 // a null preconditionRule means one the rule is incompatible
743                 result.add(preconditionRule);
744             } else if (incompatibleRule != null) {
745                 incompatibleRule.setTrue();
746             }
747         }
748         return result;
749     }
750 
751     private PreconditionRuleDTO toPreconditionRuleDTO(Iterator<Object> source) {
752         PreconditionRuleDTO result = ReefDbBeanFactory.newPreconditionRuleDTO();
753         result.setId((Integer) source.next());
754         result.setName((String) source.next());
755         result.setActive(Daos.safeConvertToBoolean(source.next()));
756         result.setBidirectional(Daos.safeConvertToBoolean(source.next()));
757 
758         // get base and used rules
759         ControlRuleDTO baseRule = getRule((String) source.next());
760         ControlRuleDTO usedRule = getRule((String) source.next());
761 
762         // if one of the rules is incompatible the precondition also
763         if (baseRule == null || usedRule == null) return null;
764 
765         for (ControlRuleDTO rule : ImmutableList.of(baseRule, usedRule)) {
766             // for now, accept only rules on measurement's qualitative or numerical value
767             if (
768                     (!ControlElementValues.MEASUREMENT.equals(rule.getControlElement()))
769                             ||
770                             (!ControlFeatureMeasurementValues.QUALITATIVE_VALUE.equals(rule.getControlFeature())
771                                     && !ControlFeatureMeasurementValues.NUMERICAL_VALUE.equals(rule.getControlFeature())
772                             )) {
773                 if (LOG.isWarnEnabled()) {
774                     LOG.warn(String.format("Only measurement's qualitative or numerical value are allowed in preconditioned rule (precondition=%s, rule=%s)",
775                             result.getId(), rule.getCode()));
776                 }
777                 return null;
778             }
779 
780             // accept only with 1 PMFM
781             if (rule.sizeRulePmfms() != 1) {
782                 if (LOG.isWarnEnabled()) {
783                     LOG.warn(String.format("Only 1 PMFMU is allowed in preconditioned rule (precondition=%s, rule=%s)",
784                             result.getId(), rule.getCode()));
785                 }
786                 return null;
787             }
788         }
789 
790         // affect if all ok
791         result.setBaseRule(baseRule);
792         result.setUsedRule(usedRule);
793         return result;
794     }
795 
796     private List<RuleGroupDTO> getGroupsByRuleListCode(String ruleListCode, boolean onlyActive, MutableBoolean incompatibleRule) {
797 
798         Iterator<Object[]> rows = queryIterator("ruleGroupsByRuleListCode",
799                 "ruleListCode", StringType.INSTANCE, ruleListCode,
800                 "ruleGroupIsActive", StringType.INSTANCE, onlyActive ? Daos.convertToString(true) : null);
801 
802         List<RuleGroupDTO> result = new ArrayList<>();
803         while (rows.hasNext()) {
804             Object[] row = rows.next();
805             RuleGroupDTO ruleGroup = toRuleGroupDTO(Arrays.asList(row).iterator());
806             if (ruleGroup != null) {
807                 // a null preconditionRule means one the rule is incompatible
808                 result.add(ruleGroup);
809             } else if (incompatibleRule != null) {
810                 incompatibleRule.setTrue();
811             }
812         }
813         return result;
814     }
815 
816     private RuleGroupDTO toRuleGroupDTO(Iterator<Object> source) {
817         RuleGroupDTO result = ReefDbBeanFactory.newRuleGroupDTO();
818         result.setId((Integer) source.next());
819         result.setName((String) source.next());
820         result.setActive(Daos.safeConvertToBoolean(source.next()));
821         result.setIsOr(Daos.safeConvertToBoolean(source.next()));
822 
823         // get the grouped rule
824         ControlRuleDTO rule = getRule((String) source.next());
825 
826         // if the rule is incompatible the group also
827         if (rule == null) return null;
828         result.setRule(rule);
829 
830         return result;
831     }
832 
833     private ControlRuleDTO toRuleDTO(Iterator<Object> source) {
834         ControlRuleDTO result = ReefDbBeanFactory.newControlRuleDTO();
835         result.setCode((String) source.next());
836 
837         String controlledElementString = (String) source.next();
838 
839         if (controlledElementString != null) {
840             int separatorIndex = controlledElementString.lastIndexOf(config.getAttributeSeparator());
841             // Check if a separator exists (if not: skip controlled element)
842             if (separatorIndex == -1 || separatorIndex == controlledElementString.length() - 1) {
843                 if (LOG.isWarnEnabled()) {
844                     LOG.warn(I18n.t("reefdb.error.dao.ruleList.parse.controlledAttribute", controlledElementString, result.getCode()));
845                 }
846                 return null;
847             } else {
848                 String controlElementCode = controlledElementString.substring(0, separatorIndex);
849                 String controlFeatureCode = controlledElementString.substring(separatorIndex + 1);
850 
851                 // get control element by its code
852                 ControlElementValues controlElementValue = ControlElementValues.getByCode(controlElementCode);
853                 if (controlElementValue != null) {
854                     result.setControlElement(controlElementValue.toControlElementDTO());
855 
856                     ControlFeatureDTO controlFeature = null;
857                     // get control feature depending in control element
858                     switch (controlElementValue) {
859 
860                         case MEASUREMENT:
861                             controlFeature = ControlFeatureMeasurementValues.toControlFeatureDTO(controlFeatureCode);
862                             break;
863                         case SAMPLING_OPERATION:
864                             controlFeature = ControlFeatureSamplingOperationValues.toControlFeatureDTO(controlFeatureCode);
865                             break;
866                         case SURVEY:
867                             controlFeature = ControlFeatureSurveyValues.toControlFeatureDTO(controlFeatureCode);
868                             break;
869                     }
870 
871                     if (controlFeature != null) {
872                         result.setControlFeature(controlFeature);
873                     } else {
874                         if (LOG.isWarnEnabled()) {
875                             LOG.warn(I18n.t("reefdb.error.dao.ruleList.rule.skip", result.getCode(), controlledElementString));
876                         }
877                         return null;
878                     }
879                 } else {
880                     if (LOG.isWarnEnabled()) {
881                         LOG.warn(I18n.t("reefdb.error.dao.ruleList.rule.skip", result.getCode(), controlElementCode));
882                     }
883                     return null;
884                 }
885             }
886         }
887 
888         result.setDescription((String) source.next());
889         result.setActive(Daos.safeConvertToBoolean(source.next()));
890         result.setBlocking(Daos.safeConvertToBoolean(source.next()));
891         result.setMessage((String) source.next());
892 
893         // function entity
894         Function function = (Function) source.next();
895         result.setFunction(toFunctionDTO(function, false/*Could not return null value*/));
896 
897         // function parameters
898         String valueMin = (String) source.next();
899         String valueMax = (String) source.next();
900         String dateMin = (String) source.next();
901         String dateMax = (String) source.next();
902         if (result.getFunction().getId().equals(functionIdDateMinMax)) {
903             try {
904                 result.setMin(dateMin == null ? null : parseDate(dateMin));
905             } catch (ParseException e) {
906                 LOG.warn(String.format("Error when parsing %s as date", dateMin), e);
907                 return null;
908             }
909             try {
910                 result.setMax(dateMax == null ? null : parseDate(dateMax));
911             } catch (ParseException e) {
912                 LOG.warn(String.format("Error when parsing %s as date", dateMax), e);
913                 return null;
914             }
915         } else {
916             result.setMin(valueMin == null ? null : Double.parseDouble(valueMin));
917             result.setMax(valueMax == null ? null : Double.parseDouble(valueMax));
918         }
919         result.setAllowedValues((String) source.next());
920 
921         return result;
922     }
923 
924     private Date parseDate(String s) throws ParseException {
925         return StringUtils.isNumeric(s) ? new Date(Long.parseLong(s)) : DateUtils.parseDate(s, DATE_PATTERN);
926     }
927 
928     private FunctionDTO toFunctionDTO(Function function, boolean couldBeNull) {
929         FunctionDTO result = ReefDbBeanFactory.newFunctionDTO();
930         result.setId(function.getFunctionId());
931         ControlFunctionValues functionControlValue = ControlFunctionValues.getFunctionValue(function.getFunctionId());
932         if (functionControlValue == null) {
933             String errorMessage = String.format("Could not found enumeration for Function with id [%s]. Make sure enumeration file has a property like '%s functionId.(...)=%s'",
934                     function.getFunctionId(),
935                     QuadrigeEnumerationDef.CONFIG_OPTION_PREFIX,
936                     function.getFunctionId());
937             if (!couldBeNull) {
938                 throw new QuadrigeTechnicalException(errorMessage);
939             }
940 
941             LOG.warn(errorMessage);
942             return null;
943         }
944 
945         result.setName(functionControlValue.getLabel());
946         return result;
947     }
948 
949     private String getRulePmfmKey(RulePmfmDTO rulePmfm) {
950 
951         return String.format("%s|%s|%s|%s|%s",
952                 rulePmfm.getPmfm().getParameter().getCode(),
953                 rulePmfm.getPmfm().getMatrix() != null ? rulePmfm.getPmfm().getMatrix().getId() : "null",
954                 rulePmfm.getPmfm().getFraction() != null ? rulePmfm.getPmfm().getFraction().getId() : "null",
955                 rulePmfm.getPmfm().getMethod() != null ? rulePmfm.getPmfm().getMethod().getId() : "null",
956                 rulePmfm.getPmfm().getUnit() != null ? rulePmfm.getPmfm().getUnit().getId() : "null"
957         );
958     }
959 
960     private void beanToEntity(ControlRuleDTO source, Rule target) {
961         // Basic properties
962         target.setRuleDc(source.getDescription());
963         String controlledAttribute = source.getControlElement().getCode()
964                 .concat(config.getAttributeSeparator())
965                 .concat(source.getControlFeature().getCode());
966         target.setRuleControledAttribut(controlledAttribute);
967         target.setRuleErrorMsg(source.getMessage());
968         target.setRuleIsActive(Daos.convertToString(source.isActive()));
969         target.setRuleIsBlocking(Daos.convertToString(source.isBlocking()));
970 
971         // Function
972         Function function;
973         if (source.getFunction() != null) {
974             function = load(FunctionImpl.class, source.getFunction().getId());
975         } else {
976             throw new DataRetrievalFailureException("ControlRuleDTO object has no Function");
977         }
978         if (function == null) {
979             throw new DataRetrievalFailureException(String.format("function not found with id=%s", source.getFunction().getId()));
980         }
981         target.setFunction(function);
982     }
983 
984     private void updateOrCreateRuleParameter(Rule parent,
985                                              Iterator<Integer> ruleParIdSequence,
986                                              Map<Integer, RuleParameter> ruleParametersByFunctionParamIds,
987                                              int functionParameterId,
988                                              String ruleParameterValue) {
989         RuleParameter rp = ruleParametersByFunctionParamIds.remove(functionParameterId);
990         boolean isNew = false;
991         if (rp == null) {
992             rp = RuleParameterImpl.Factory.newInstance();
993             rp.setFunctionParameter(load(FunctionParameterImpl.class, functionParameterId));
994             rp.setRuleParId(ruleParIdSequence.next());
995             parent.addRuleParameters(rp);
996             rp.setRule(parent);
997             isNew = true;
998         }
999 
1000         // Update properties
1001         rp.setRuleParValue(ruleParameterValue);
1002 
1003         // Save to session
1004         if (isNew) {
1005             getSession().save(rp);
1006         } else {
1007             getSession().update(rp);
1008         }
1009 
1010     }
1011 
1012     private ControlFunctionValues getFunction(PreconditionRuleDTO preconditionRule) {
1013         if (ControlFunctionValues.IS_AMONG.equals(preconditionRule.getBaseRule().getFunction())
1014                 && ControlFunctionValues.IS_AMONG.equals(preconditionRule.getUsedRule().getFunction())) {
1015             return ControlFunctionValues.PRECONDITION_QUALITATIVE;
1016         } else if (
1017                 (ControlFunctionValues.IS_AMONG.equals(preconditionRule.getBaseRule().getFunction())
1018                         && ControlFunctionValues.MIN_MAX.equals(preconditionRule.getUsedRule().getFunction()))
1019                         ||
1020                         (ControlFunctionValues.MIN_MAX.equals(preconditionRule.getBaseRule().getFunction())
1021                                 && ControlFunctionValues.IS_AMONG.equals(preconditionRule.getUsedRule().getFunction()))) {
1022             return ControlFunctionValues.PRECONDITION_NUMERICAL;
1023         }
1024         return null;
1025     }
1026 }