View Javadoc
1   package fr.ifremer.dali.dao.system.rule;
2   
3   /*
4    * #%L
5    * Dali :: Core
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2014 - 2015 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU Affero General Public License as published by
13   * the Free Software Foundation, either version 3 of the License, or
14   * (at your option) any later version.
15   *
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   *
21   * You should have received a copy of the GNU Affero General Public License
22   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23   * #L%
24   */
25  
26  import com.google.common.collect.Lists;
27  import com.google.common.collect.Sets;
28  import fr.ifremer.dali.config.DaliConfiguration;
29  import fr.ifremer.dali.dao.administration.user.DaliDepartmentDao;
30  import fr.ifremer.dali.dao.referential.DaliReferentialDao;
31  import fr.ifremer.dali.dao.technical.Daos;
32  import fr.ifremer.dali.dto.DaliBeanFactory;
33  import fr.ifremer.dali.dto.DaliBeans;
34  import fr.ifremer.dali.dto.configuration.control.PreconditionRuleDTO;
35  import fr.ifremer.dali.dto.configuration.control.RuleListDTO;
36  import fr.ifremer.dali.dto.referential.DepartmentDTO;
37  import fr.ifremer.dali.service.administration.program.ProgramStrategyService;
38  import fr.ifremer.dali.service.system.SystemService;
39  import fr.ifremer.quadrige3.core.dao.administration.program.ProgramImpl;
40  import fr.ifremer.quadrige3.core.dao.administration.user.DepartmentImpl;
41  import fr.ifremer.quadrige3.core.dao.administration.user.Quser;
42  import fr.ifremer.quadrige3.core.dao.administration.user.QuserImpl;
43  import fr.ifremer.quadrige3.core.dao.referential.StatusCode;
44  import fr.ifremer.quadrige3.core.dao.referential.StatusImpl;
45  import fr.ifremer.quadrige3.core.dao.system.rule.RuleList;
46  import fr.ifremer.quadrige3.core.dao.system.rule.RuleListDaoImpl;
47  import fr.ifremer.quadrige3.core.dao.technical.Assert;
48  import fr.ifremer.quadrige3.core.dao.technical.Beans;
49  import fr.ifremer.quadrige3.core.dao.technical.Dates;
50  import fr.ifremer.quadrige3.core.security.SecurityContextHelper;
51  import org.apache.commons.collections4.CollectionUtils;
52  import org.apache.commons.lang3.mutable.MutableBoolean;
53  import org.apache.commons.logging.Log;
54  import org.apache.commons.logging.LogFactory;
55  import org.hibernate.SessionFactory;
56  import org.hibernate.type.StringType;
57  import org.springframework.beans.factory.annotation.Autowired;
58  import org.springframework.dao.DataRetrievalFailureException;
59  import org.springframework.stereotype.Repository;
60  
61  import javax.annotation.Resource;
62  import java.time.LocalDate;
63  import java.util.*;
64  
65  /**
66   * <p>DaliRuleListDaoImpl class.</p>
67   *
68   * @author Ludovic
69   */
70  @Repository("daliRuleListDao")
71  public class DaliRuleListDaoImpl extends RuleListDaoImpl implements DaliRuleListDao {
72  
73      private static final Log log = LogFactory.getLog(DaliRuleListDaoImpl.class);
74  
75      @Resource
76      protected DaliConfiguration config;
77  
78      @Resource(name = "daliRuleDao")
79      private DaliRuleDao ruleDao;
80  
81      @Resource(name = "daliDepartmentDao")
82      protected DaliDepartmentDao departmentDao;
83  
84      @Resource(name = "daliProgramStrategyService")
85      private ProgramStrategyService programStrategyService;
86  
87      @Resource(name = "daliReferentialDao")
88      protected DaliReferentialDao referentialDao;
89  
90      @Resource(name = "daliSystemService")
91      protected SystemService systemService;
92  
93      /**
94       * <p>Constructor for DaliRuleListDaoImpl.</p>
95       *
96       * @param sessionFactory a {@link org.hibernate.SessionFactory} object.
97       */
98      @Autowired
99      public DaliRuleListDaoImpl(SessionFactory sessionFactory) {
100         super(sessionFactory);
101     }
102 
103     /**
104      * {@inheritDoc}
105      */
106     @Override
107     public List<RuleListDTO> getAllRuleLists() {
108 
109         Iterator<Object[]> it = queryIterator("allRuleList");
110         return toRuleListDTOs(it);
111     }
112 
113     @Override
114     public List<RuleListDTO> getRuleListsForProgram(String programCode) {
115 
116         Iterator<Object[]> it = queryIterator("allRuleListWithProgramCode",
117                 "programCode", StringType.INSTANCE, programCode);
118         return toRuleListDTOs(it);
119     }
120 
121     @Override
122     public boolean ruleListExists(String ruleListCode) {
123         Assert.notBlank(ruleListCode);
124 
125         return queryUnique("ruleListByCode", "ruleListCode", StringType.INSTANCE, ruleListCode) != null;
126     }
127 
128     /**
129      * {@inheritDoc}
130      */
131     @Override
132     public RuleListDTO getRuleList(String ruleListCode) {
133         Assert.notBlank(ruleListCode);
134 
135         Object[] source = queryUnique("ruleListByCode", "ruleListCode", StringType.INSTANCE, ruleListCode);
136 
137         if (source == null) {
138             throw new DataRetrievalFailureException("can't load rule list with code = " + ruleListCode);
139         }
140         TimeZone dbTimezone = config.getDbTimezone();
141         RuleListDTO result = toRuleListDTO(Arrays.asList(source).iterator(), dbTimezone);
142 
143         if (fillAndValidRuleList(result,
144                 programStrategyService.getManagedProgramCodesByQuserId(SecurityContextHelper.getQuadrigeUserId()),
145                 programStrategyService.getWritableProgramCodesByQuserId(SecurityContextHelper.getQuadrigeUserId())))
146             // return the valid rule list
147             return result;
148 
149         return null;
150     }
151 
152     /**
153      * {@inheritDoc}
154      */
155     @Override
156     public void saveRuleList(RuleListDTO source, Integer quserId) {
157         Assert.notNull(source);
158         Assert.notNull(source.getCode());
159         Assert.notNull(quserId);
160 
161         RuleList target = get(source.getCode());
162         boolean isNew = false;
163         if (target == null) {
164             target = RuleList.Factory.newInstance();
165             target.setRuleListCd(source.getCode());
166             isNew = true;
167         }
168 
169         // DTO -> Entity
170         beanToEntity(source, target, quserId, config.getDbTimezone());
171 
172         // Save it
173         if (isNew) {
174             getSession().save(target);
175         } else {
176             getSession().update(target);
177         }
178 
179         // Save rules
180         final List<String> rulesCdsToRemove = DaliBeans.collectProperties(target.getRules(), "ruleCd");
181 
182         // Save rules
183         if (CollectionUtils.isNotEmpty(source.getControlRules())) {
184             source.getControlRules().forEach(controlRule -> {
185                 ruleDao.save(controlRule, source.getCode());
186                 rulesCdsToRemove.remove(controlRule.getCode());
187                 if (!controlRule.isPreconditionsEmpty()) {
188                     for (PreconditionRuleDTO preconditionRule : controlRule.getPreconditions()) {
189                         rulesCdsToRemove.remove(preconditionRule.getBaseRule().getCode());
190                         rulesCdsToRemove.remove(preconditionRule.getUsedRule().getCode());
191                     }
192                 }
193             });
194         }
195 
196         getSession().flush();
197         getSession().clear();
198 
199         // remove unused rules
200         if (CollectionUtils.isNotEmpty(rulesCdsToRemove)) {
201             ruleDao.removeByCds(Beans.asStringArray(rulesCdsToRemove));
202             // flush again because another get or load can read the removes rules
203             getSession().flush();
204             getSession().clear();
205         }
206     }
207 
208     private boolean fillAndValidRuleList(RuleListDTO ruleList, Set<String> managedProgramCodes, Set<String> writableProgramCodes) {
209 
210         // get programs
211         List<String> programCodes = getProgramCodesByRuleListCode(ruleList.getCode());
212 
213         if (CollectionUtils.isEmpty(programCodes)) {
214             // skip this rule list
215             return false;
216         }
217 
218         // Check program privilege
219         boolean canRead = programCodes.stream().anyMatch(writableProgramCodes::contains);
220         if (!canRead) {
221             if (log.isWarnEnabled()) {
222                 log.warn(String.format("The rule list '%s' is not compatible (writable program list is empty for the current user)", ruleList.getCode()));
223             }
224             // skip this rule list
225             return false;
226         }
227 
228         // the current user is not manager of all programs, set this rule list as read only
229         boolean canWrite = managedProgramCodes.containsAll(programCodes);
230         ruleList.setReadOnly(!canWrite);
231 
232         // add programs
233         ruleList.setPrograms(programStrategyService.getProgramsByCodes(programCodes));
234 
235         // add services
236         ruleList.setDepartments(getControlledDepartmentsByRuleListCode(ruleList.getCode()));
237 
238         // valid services
239 //        if (ruleList.isDepartmentsEmpty()) {
240 //            if (log.isWarnEnabled()) {
241 //                log.warn(String.format("The rule list '%s' is not compatible (service list is empty)", ruleList.getCode()));
242 //            }
243 //            // skip this rule list
244 //            return false;
245 //        }
246 
247         // add rules
248         MutableBoolean incompatibleRule = new MutableBoolean(false);
249         ruleList.addAllControlRules(ruleDao.getRulesByRuleListCode(ruleList.getCode(), false /*active and non-active*/, incompatibleRule));
250 
251         // add preconditioned rules
252         ruleList.addAllControlRules(ruleDao.getPreconditionedRulesByRuleListCode(ruleList.getCode(), false, incompatibleRule));
253 
254         // valid rules
255         if (ruleList.isControlRulesEmpty()) {
256             if (log.isWarnEnabled()) {
257                 log.warn(String.format("The rule list '%s' is not compatible (rule list is empty)", ruleList.getCode()));
258             }
259             // skip this rule list
260             return false;
261         }
262         // set the rule list as read only if at least one rule is not compatible
263         if (incompatibleRule.booleanValue()) ruleList.setReadOnly(true);
264 
265         return true;
266     }
267 
268     private List<String> getProgramCodesByRuleListCode(String ruleListCode) {
269         Assert.notBlank(ruleListCode);
270 
271         return queryListTyped("programCodesByRuleListCode",
272                 "ruleListCode", StringType.INSTANCE, ruleListCode);
273     }
274 
275     /**
276      * <p>getControlledDepartmentsByRuleListCode.</p>
277      *
278      * @param ruleListCode a {@link java.lang.String} object.
279      * @return a {@link java.util.List} object.
280      */
281     private List<DepartmentDTO> getControlledDepartmentsByRuleListCode(String ruleListCode) {
282         Assert.notBlank(ruleListCode);
283 
284         List<Integer> depIds = queryListTyped("departmentIdsByRuleListCode",
285                 "ruleListCode", StringType.INSTANCE, ruleListCode);
286 
287         List<DepartmentDTO> result = Lists.newArrayList();
288         if (CollectionUtils.isNotEmpty(depIds)) {
289             for (Integer depId : new HashSet<>(depIds)) {
290                 result.add(departmentDao.getDepartmentById(depId));
291             }
292         }
293         return result;
294     }
295 
296     // INTERNAL METHODS
297 
298     private List<RuleListDTO> toRuleListDTOs(Iterator<Object[]> it) {
299 
300         Set<String> managedProgramCodes = programStrategyService.getManagedProgramCodesByQuserId(SecurityContextHelper.getQuadrigeUserId());
301         Set<String> writableProgramCodes = programStrategyService.getWritableProgramCodesByQuserId(SecurityContextHelper.getQuadrigeUserId());
302 
303         TimeZone dbTimezone = config.getDbTimezone();
304         List<RuleListDTO> result = Lists.newArrayList();
305         while (it.hasNext()) {
306             Object[] source = it.next();
307             RuleListDTO ruleList = toRuleListDTO(Arrays.asList(source).iterator(), dbTimezone);
308 
309             if (fillAndValidRuleList(ruleList, managedProgramCodes, writableProgramCodes))
310                 // add the valid rule list to result
311                 result.add(ruleList);
312         }
313 
314         return result;
315     }
316 
317     private RuleListDTO toRuleListDTO(Iterator<Object> source, TimeZone dbTimezone) {
318         RuleListDTO result = DaliBeanFactory.newRuleListDTO();
319         result.setCode((String) source.next());
320         result.setActive(Daos.safeConvertToBoolean(source.next()));
321 
322         LocalDate startDate = Dates.convertToLocalDate(Daos.convertToDate(source.next()), dbTimezone);
323         result.setStartMonth(
324             Optional.ofNullable(startDate)
325                 .map(LocalDate::getMonthValue)
326                 .flatMap(month -> systemService.getMonths().stream().filter(monthDTO -> monthDTO.getId().equals(month)).findFirst())
327                 .orElse(null)
328         );
329 
330         LocalDate endDate = Dates.convertToLocalDate(Daos.convertToDate(source.next()), dbTimezone);
331         result.setEndMonth(
332             Optional.ofNullable(endDate)
333                 .map(LocalDate::getMonthValue)
334                 .flatMap(month -> systemService.getMonths().stream().filter(monthDTO -> monthDTO.getId().equals(month)).findFirst())
335                 .orElse(null)
336         );
337 
338         result.setDescription((String) source.next());
339         result.setStatus(referentialDao.getStatusByCode((String) source.next()));
340         result.setCreationDate(Daos.convertToDate(source.next()));
341         result.setUpdateDate(Daos.convertToDate(source.next()));
342 
343         return result;
344     }
345 
346     private void beanToEntity(RuleListDTO source, RuleList target, int quserId, TimeZone dbTimezone) {
347 
348         target.setRuleListDc(source.getDescription());
349         target.setRuleListIsActive(Daos.convertToString(source.isActive()));
350 
351         // convert month to date
352         LocalDate startDate = Optional.ofNullable(source.getStartMonth())
353             .map(monthDTO -> LocalDate.of(LocalDate.now().getYear(), monthDTO.getId(), 1))
354             .orElse(null);
355         target.setRuleListFirstMonth(Dates.convertToDate(startDate, dbTimezone));
356 
357         LocalDate endDate = Optional.ofNullable(source.getEndMonth())
358             .map(monthDTO -> LocalDate.of(LocalDate.now().getYear(), monthDTO.getId(), 1).plusMonths(1).minusDays(1))
359             .orElse(null);
360         target.setRuleListLastMonth(Dates.convertToDate(endDate, dbTimezone));
361 
362         // update date (always null, as set by server)
363         // target.setUpdateDt(newUpdateTimestamp());
364 
365         // creation date
366         if (target.getRuleListCreationDt() == null) {
367             target.setRuleListCreationDt(newCreateDate());
368         }
369 
370         // manager user
371         if (CollectionUtils.isEmpty(target.getQusers())) {
372             // add current user as unique
373             Quser quser = load(QuserImpl.class, quserId);
374             target.setQusers(Sets.newHashSet(quser));
375         } else if (DaliBeans.findByProperty(target.getQusers(), "quserId", quserId) == null) {
376             // add current user to collection
377             Quser quser = load(QuserImpl.class, quserId);
378             target.getQusers().add(quser);
379         }
380 
381         // manager department
382         if (CollectionUtils.isEmpty(target.getRespDepartments())) {
383             // add current user department as unique
384             Quser quser = load(QuserImpl.class, quserId);
385             target.setRespDepartments(Sets.newHashSet(quser.getDepartment()));
386         } else {
387             // add current user's department to collection
388             Quser quser = load(QuserImpl.class, quserId);
389             if (DaliBeans.findByProperty(target.getRespDepartments(), "depId", quser.getDepartment().getDepId()) == null) {
390                 target.getRespDepartments().add(quser.getDepartment());
391             }
392         }
393 
394         // programs
395         if (source.getPrograms() == null) {
396             target.getPrograms().clear();
397         } else {
398             Daos.replaceEntities(target.getPrograms(),
399                     source.getPrograms(),
400                     vo -> load(ProgramImpl.class, Objects.requireNonNull(vo).getCode()));
401         }
402 
403         // do not remove remaining unused programs: links to programs will be automatically deleted when ruleList entity will be saved
404 
405         // departments
406         if (source.getDepartments() == null) {
407             target.getControledDepartments().clear();
408         } else {
409             Daos.replaceEntities(target.getControledDepartments(),
410                     source.getDepartments(),
411                     vo -> load(DepartmentImpl.class, Objects.requireNonNull(vo).getId()));
412         }
413 
414         // status
415         target.setStatus(load(StatusImpl.class, StatusCode.ENABLE.getValue()));
416 
417     }
418 
419 }