View Javadoc
1   package fr.ifremer.dali.dao.administration.strategy;
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.base.Joiner;
27  import com.google.common.collect.*;
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.referential.monitoringLocation.DaliMonitoringLocationDao;
32  import fr.ifremer.dali.dao.referential.pmfm.DaliPmfmDao;
33  import fr.ifremer.dali.dao.technical.Daos;
34  import fr.ifremer.dali.dto.DaliBeanFactory;
35  import fr.ifremer.dali.dto.DaliBeans;
36  import fr.ifremer.dali.dto.configuration.programStrategy.*;
37  import fr.ifremer.dali.dto.referential.DepartmentDTO;
38  import fr.ifremer.dali.dto.referential.LocationDTO;
39  import fr.ifremer.dali.service.DaliTechnicalException;
40  import fr.ifremer.quadrige3.core.dao.administration.program.ProgramImpl;
41  import fr.ifremer.quadrige3.core.dao.administration.strategy.*;
42  import fr.ifremer.quadrige3.core.dao.administration.user.DepartmentImpl;
43  import fr.ifremer.quadrige3.core.dao.administration.user.QuserImpl;
44  import fr.ifremer.quadrige3.core.dao.referential.AcquisitionLevel;
45  import fr.ifremer.quadrige3.core.dao.referential.AcquisitionLevelCode;
46  import fr.ifremer.quadrige3.core.dao.referential.AcquisitionLevelImpl;
47  import fr.ifremer.quadrige3.core.dao.referential.monitoringLocation.MonitoringLocationImpl;
48  import fr.ifremer.quadrige3.core.dao.referential.pmfm.PmfmImpl;
49  import fr.ifremer.quadrige3.core.dao.system.synchronization.SynchronizationStatus;
50  import fr.ifremer.quadrige3.core.dao.technical.Assert;
51  import fr.ifremer.quadrige3.core.dao.technical.Dates;
52  import fr.ifremer.quadrige3.core.dao.technical.hibernate.TemporaryDataHelper;
53  import fr.ifremer.quadrige3.core.exception.SaveForbiddenException;
54  import fr.ifremer.quadrige3.ui.core.dto.QuadrigeBeanComparator;
55  import fr.ifremer.quadrige3.ui.core.dto.QuadrigeBeans;
56  import org.apache.commons.collections4.CollectionUtils;
57  import org.apache.commons.lang3.BooleanUtils;
58  import org.apache.commons.logging.Log;
59  import org.apache.commons.logging.LogFactory;
60  import org.hibernate.Query;
61  import org.hibernate.SessionFactory;
62  import org.hibernate.type.DateType;
63  import org.hibernate.type.IntegerType;
64  import org.hibernate.type.StringType;
65  import org.springframework.beans.factory.annotation.Autowired;
66  import org.springframework.stereotype.Repository;
67  
68  import javax.annotation.Resource;
69  import java.time.LocalDate;
70  import java.time.format.DateTimeFormatter;
71  import java.util.*;
72  import java.util.stream.Collectors;
73  
74  /**
75   * <p>DaliStrategyDaoImpl class.</p>
76   *
77   * @author Ludovic
78   */
79  @Repository("daliStrategyDao")
80  public class DaliStrategyDaoImpl extends StrategyDaoImpl implements DaliStrategyDao {
81  
82      private static final Log LOG = LogFactory.getLog(DaliStrategyDaoImpl.class);
83      private static final String DATE_PATTERN = "yyyyMMdd";
84  
85      @Resource(name = "daliDepartmentDao")
86      protected DaliDepartmentDao departmentDao;
87  
88      @Resource(name = "daliPmfmDao")
89      protected DaliPmfmDao pmfmDao;
90  
91      @Resource(name = "daliMonitoringLocationDao")
92      protected DaliMonitoringLocationDao monitoringLocationDao;
93  
94      @Resource
95      protected DaliConfiguration config;
96  
97      @Resource(name = "daliReferentialDao")
98      protected DaliReferentialDao referentialDao;
99  
100     @Resource()
101     protected AppliedStrategyDao appliedStrategyDao;
102 
103     @Resource()
104     protected PmfmStrategyDaoImpl pmfmStrategyDao;
105 
106     /**
107      * <p>Constructor for DaliStrategyDaoImpl.</p>
108      *
109      * @param sessionFactory a {@link SessionFactory} object.
110      */
111     @Autowired
112     public DaliStrategyDaoImpl(SessionFactory sessionFactory) {
113         super(sessionFactory);
114     }
115 
116     /** {@inheritDoc} */
117     @Override
118     public List<StrategyDTO> getStrategiesByProgramCode(String programCode) {
119 
120         Iterator<Object[]> rows = queryIterator("strategiesByProgramCode",
121                 "programCode", StringType.INSTANCE, programCode);
122 
123         List<StrategyDTO> result = Lists.newArrayList();
124         while (rows.hasNext()) {
125             Object[] row = rows.next();
126             result.add(toStrategyDTO(Arrays.asList(row).iterator()));
127         }
128 
129         return ImmutableList.copyOf(result);
130 
131     }
132 
133     /** {@inheritDoc} */
134     @Override
135     public List<AppliedStrategyDTO> getAppliedStrategiesByProgramCode(String programCode) {
136 
137         Iterator<Object[]> rows = queryIterator("appliedStrategiesByProgramCode",
138                 "programCode", StringType.INSTANCE, programCode);
139 
140         List<AppliedStrategyDTO> result = Lists.newArrayList();
141         while (rows.hasNext()) {
142             Iterator<Object> row = Arrays.asList(rows.next()).iterator();
143             result.add(toAppliedStrategyDTO(row));
144         }
145 
146         return ImmutableList.copyOf(result);
147     }
148 
149     /** {@inheritDoc} */
150     @Override
151     public List<AppliedStrategyDTO> getAppliedStrategiesByStrategyId(Integer strategyId) {
152 
153         Iterator<Object[]> rows = queryIterator("appliedStrategiesByStrategyId",
154                 "strategyId", IntegerType.INSTANCE, strategyId);
155 
156         List<AppliedStrategyDTO> result = Lists.newArrayList();
157         while (rows.hasNext()) {
158             Iterator<Object> row = Arrays.asList(rows.next()).iterator();
159             result.add(toAppliedStrategyDTO(row));
160         }
161 
162         return ImmutableList.copyOf(result);
163     }
164 
165     /** {@inheritDoc} */
166     @Override
167     public List<AppliedStrategyDTO> getAppliedPeriodsByStrategyId(Integer strategyId) {
168 
169         Iterator<Object[]> rows = queryIterator("appliedPeriodsByStrategyId",
170                 "strategyId", IntegerType.INSTANCE, strategyId);
171 
172         TimeZone dbTimezone = config.getDbTimezone();
173         List<AppliedStrategyDTO> result = Lists.newArrayList();
174         while (rows.hasNext()) {
175             Iterator<Object> row = Arrays.asList(rows.next()).iterator();
176             result.add(toAppliedPeriod(row, dbTimezone));
177         }
178 
179         return ImmutableList.copyOf(result);
180     }
181 
182     /** {@inheritDoc} */
183     @Override
184     public Multimap<Integer, AppliedStrategyDTO> getAllAppliedPeriodsByProgramCode(String programCode) {
185 
186         Iterator<Object[]> rows = queryIterator("allAppliedPeriodsByProgramCode",
187                 "programCode", StringType.INSTANCE, programCode);
188 
189         Multimap<Integer, AppliedStrategyDTO> result = ArrayListMultimap.create();
190         TimeZone dbTimezone = config.getDbTimezone();
191         while (rows.hasNext()) {
192             Iterator<Object> row = Arrays.asList(rows.next()).iterator();
193             Integer strategyId = (Integer) row.next();
194             AppliedStrategyDTO appliedStrategy = toAppliedPeriod(row, dbTimezone);
195             result.put(strategyId, appliedStrategy);
196         }
197 
198         return result;
199     }
200 
201     /** {@inheritDoc} */
202     @Override
203     public List<PmfmStrategyDTO> getPmfmsAppliedStrategy(Integer strategyId) {
204 
205         Iterator<Object[]> rows = queryIterator("pmfmsAppliedStrategyByStrategyId",
206                 "strategyId", IntegerType.INSTANCE, strategyId);
207 
208         List<PmfmStrategyDTO> result = Lists.newArrayList();
209         PmfmStrategyDTO previousDTO = null;
210         while (rows.hasNext()) {
211             Iterator<Object> row = Arrays.asList(rows.next()).iterator();
212             PmfmStrategyDTO currentDTO = toPmfmStrategyDTO(row);
213             if (previousDTO != null) {
214                 // same pmfm
215                 if (Objects.equals(previousDTO, currentDTO)) {
216                     currentDTO = previousDTO;
217                 }
218                 // new pmfm detected
219                 else {
220                     result.add(previousDTO);
221                     previousDTO = currentDTO;
222                 }
223             } else {
224                 previousDTO = currentDTO;
225             }
226 
227             String acquisitionLevelCode = (String) row.next();
228             currentDTO.setSurvey(BooleanUtils.toBoolean(currentDTO.isSurvey())
229                     || AcquisitionLevelCode.SURVEY.getValue().equals(acquisitionLevelCode));
230             currentDTO.setSampling(BooleanUtils.toBoolean(currentDTO.isSampling())
231                     || AcquisitionLevelCode.SAMPLING_OPERATION.getValue().equals(acquisitionLevelCode));
232         }
233 
234         if (previousDTO != null) {
235             result.add(previousDTO);
236         }
237 
238         // Get restricted list of qualitative values
239         fillQualitativeValues(result);
240 
241         return ImmutableList.copyOf(result);
242     }
243 
244     /** {@inheritDoc} */
245     @Override
246     public Set<PmfmStrategyDTO> getPmfmStrategiesByProgramCodeAndLocation(String programCode, Integer monitoringLocationId, LocalDate date) {
247         Assert.notBlank(programCode);
248         Assert.notNull(monitoringLocationId);
249         Assert.notNull(date);
250 
251         List<ProgStratDTO> appliedStrategies = getAppliedStrategiesByProgramCodeAndMonitoringLocationId(programCode, monitoringLocationId);
252         Set<PmfmStrategyDTO> pmfmStrategies = Sets.newHashSet();
253 
254         for (ProgStratDTO appliedStrategy : appliedStrategies) {
255             // also filter pmfm on date
256             if (appliedStrategy.getStartDate() != null && appliedStrategy.getEndDate() != null && Dates.isBetween(date, appliedStrategy.getStartDate(), appliedStrategy.getEndDate())) {
257                 pmfmStrategies.addAll(getPmfmsAppliedStrategy(appliedStrategy.getId()));
258             }
259         }
260 
261         return ImmutableSet.copyOf(pmfmStrategies);
262     }
263 
264     @Override
265     public Set<PmfmStrategyDTO> getPmfmStrategiesByProgramCodesAndDates(Collection<String> programCodes, LocalDate startDate, LocalDate endDate) {
266         Set<PmfmStrategyDTO> pmfmStrategies = new HashSet<>();
267 
268         List<ProgStratDTO> appliedStrategies = getAppliedStrategiesByProgramCodes(programCodes);
269         for (ProgStratDTO appliedStrategy: appliedStrategies) {
270             // filter on date range
271             if (appliedStrategy.getStartDate() != null && appliedStrategy.getEndDate() != null
272                     && !appliedStrategy.getStartDate().isAfter(endDate) && !appliedStrategy.getEndDate().isBefore(startDate)) {
273                 // add all pmfm strategies in date range
274                 pmfmStrategies.addAll(getPmfmsAppliedStrategy(appliedStrategy.getId()));
275             }
276         }
277 
278         return ImmutableSet.copyOf(pmfmStrategies);
279 
280     }
281 
282     /** {@inheritDoc} */
283     @Override
284     public List<ProgStratDTO> getAppliedStrategiesByProgramCodeAndMonitoringLocationId(String programCode, int monitoringLocationId) {
285         Iterator<Object[]> rows = queryIterator("appliedStrategiesByProgramCodeAndMonitoringLocationId",
286                 "programCode", StringType.INSTANCE, programCode,
287                 "monitoringLocationId", IntegerType.INSTANCE, monitoringLocationId);
288 
289         List<ProgStratDTO> result = Lists.newArrayList();
290         TimeZone dbTimezone = config.getDbTimezone();
291         while (rows.hasNext()) {
292             result.add(toProgStratDTO(Arrays.asList(rows.next()).iterator(), dbTimezone));
293         }
294 
295         return ImmutableList.copyOf(result);
296     }
297 
298     @SuppressWarnings("unchecked")
299     private List<ProgStratDTO> getAppliedStrategiesByProgramCodes(Collection<String> programCodes) {
300         Query query = createQuery("appliedStrategiesByProgramCodes").setParameterList("programCodes", programCodes);
301         Iterator<Object[]> rows = query.iterate();
302 
303         List<ProgStratDTO> result = Lists.newArrayList();
304         TimeZone dbTimezone = config.getDbTimezone();
305         while (rows.hasNext()) {
306             result.add(toProgStratDTO(Arrays.asList(rows.next()).iterator(), dbTimezone));
307         }
308 
309         return result;
310     }
311 
312     @Override
313     public DepartmentDTO getAnalysisDepartmentByAppliedStrategyId(int appliedStrategyId) {
314 
315         List<Integer> depIds = queryListTyped("analysisDepartmentByAppliedStrategyId",
316                 "appliedStrategyId", IntegerType.INSTANCE, appliedStrategyId);
317 
318         depIds = depIds.stream().filter(Objects::nonNull).collect(Collectors.toList());
319 
320         if (CollectionUtils.isEmpty(depIds)) {
321             return null;
322         } else if (depIds.size() > 1) {
323             // the department should be unique
324             LOG.warn(String.format("More than one analysis department %s for applied strategy %s", depIds, appliedStrategyId));
325         }
326 
327         return departmentDao.getDepartmentById(depIds.get(0));
328 
329     }
330 
331     /** {@inheritDoc} */
332     @Override
333     public void saveStrategies(ProgramDTO program) {
334         Assert.notNull(program);
335         Assert.notBlank(program.getCode());
336 
337         List<Integer> remainingStrategieIds = queryListTyped("stratIdsByProgCd",
338                 "programCode", StringType.INSTANCE, program.getCode());
339 
340         for (StrategyDTO strategy : program.getStrategies()) {
341             Strategy target = null;
342 
343             if (strategy.getId() != null) {
344                 target = get(strategy.getId());
345             }
346 
347             boolean isNew = false;
348             if (target == null) {
349                 target = Strategy.Factory.newInstance();
350                 Integer newId = TemporaryDataHelper.<Integer>getNewNegativeIdForTemporaryData(getSession(), target.getClass());
351                 target.setStratId(newId);
352                 target.setProgram(load(ProgramImpl.class, program.getCode()));
353                 target.setStratCreationDt(newCreateDate());
354                 isNew = true;
355             } else {
356                 remainingStrategieIds.remove(target.getStratId());
357             }
358 
359             // Fill entity from DTO
360             strategyDTOToEntity(program, strategy, target);
361 
362             // Save entity
363             if (isNew) {
364                 getSession().save(target);
365                 strategy.setId(target.getStratId());
366             }
367             else {
368                 getSession().update(target);
369             }
370 
371             getSession().flush();
372 
373             TimeZone dbTimezone = config.getDbTimezone();
374 
375             // Save PmfmStrategies before AppliedStrategies
376             if (strategy.isPmfmStrategiesLoaded() || !strategy.isPmfmStrategiesEmpty()) {
377                 if (!strategy.isPmfmStrategiesEmpty()) {
378 
379                     Map<Integer, PmfmStrategy> remainingPmfmStrategy = DaliBeans.mapByProperty(target.getPmfmStrategies(), "pmfmStratId");
380                     int rank = 1;
381 
382                     for (PmfmStrategyDTO pmfmStrategyDTO : strategy.getPmfmStrategies()) {
383 
384                         PmfmStrategy pmfmStrategy = null;
385                         if (pmfmStrategyDTO.getId() != null) {
386                             pmfmStrategy = remainingPmfmStrategy.remove(pmfmStrategyDTO.getId());
387                         }
388                         if (pmfmStrategy == null) {
389                             // create new PmfmStrategy
390                             pmfmStrategy = PmfmStrategy.Factory.newInstance();
391                             Integer newId = TemporaryDataHelper.<Integer>getNewNegativeIdForTemporaryData(getSession(), pmfmStrategy.getClass());
392                             pmfmStrategy.setPmfmStratId(newId);
393                             pmfmStrategy.setPmfmStratParAcquisNumber(1);
394                         }
395 
396                         savePmfmStrategy(pmfmStrategyDTO, pmfmStrategy, rank++);
397                         pmfmStrategy.setStrategy(target);
398                         target.addPmfmStrategies(pmfmStrategy);
399                         getSession().save(pmfmStrategy);
400                         pmfmStrategyDTO.setId(pmfmStrategy.getPmfmStratId());
401 
402                         // Update PMFM_STRAT_PMFM_QUAL_VALUE
403                         saveQualitativeValues(pmfmStrategyDTO);
404 
405                     }
406 
407                     // remove remaining pmfmStrategy
408                     if (!remainingPmfmStrategy.isEmpty()) {
409                         target.getPmfmStrategies().removeAll(remainingPmfmStrategy.values());
410                         List<Integer> pmfmStrategyIds = DaliBeans.collectProperties(remainingPmfmStrategy.values(), "pmfmStratId");
411                         deletePmfmStrategies(pmfmStrategyIds);
412                     }
413 
414                 } else {
415                     if (CollectionUtils.isNotEmpty(target.getPmfmStrategies())) {
416                         // collect pmfmStrategyIds before clearing collection (Mantis #51602)
417                         List<Integer> pmfmStrategyIds = DaliBeans.collectProperties(target.getPmfmStrategies(), "pmfmStratId");
418                         target.getPmfmStrategies().clear();
419                         deletePmfmStrategies(pmfmStrategyIds);
420                     }
421                 }
422             }
423 
424             // Save AppliedStrategies and AppliedPeriods
425             if (strategy.isAppliedStrategiesLoaded() || !strategy.isAppliedStrategiesEmpty()) {
426                 if (!strategy.isAppliedStrategiesEmpty()) {
427 
428                     Map<Integer, AppliedStrategy> remainingAppliedStrategies = DaliBeans.mapByProperty(target.getAppliedStrategies(), "appliedStratId");
429                     Map<String, AppliedStrategy> remainingAppliedStrategyByKey = buildAppliedStrategyMap(remainingAppliedStrategies.values(), dbTimezone);
430                     AppliedStrategy previousAppliedStrategy = null;
431                     // sort AppliedStrategyDTO by natural Id = monitoringLocation.Id
432                     strategy.getAppliedStrategies().sort(new QuadrigeBeanComparator());
433                     for (AppliedStrategyDTO appliedStrategy : strategy.getAppliedStrategies()) {
434 
435                         // save an applied strategy only if dates are valid or department set
436                         // the department is not part of applied period, so don't take care about it (Mantis #43233)
437                         if (appliedStrategy.getStartDate() == null && appliedStrategy.getEndDate() == null /*&& appliedStrategy.getSamplingDepartment() == null*/) {
438                             continue;
439                         }
440 
441                         AppliedStrategy targetAppliedStrategy = null;
442                         if (previousAppliedStrategy != null) {
443                             // check if same AppliedStrategyDTO as previous iteration
444                             Integer previousDepId = previousAppliedStrategy.getDepartment() == null ? 0 : previousAppliedStrategy.getDepartment().getDepId();
445                             if (previousAppliedStrategy.getMonitoringLocation().getMonLocId().equals(appliedStrategy.getId())
446                                     && previousDepId.equals(appliedStrategy.getSamplingDepartment() == null ? 0 : appliedStrategy.getSamplingDepartment().getId())) {
447                                 targetAppliedStrategy = previousAppliedStrategy;
448                             }
449                         }
450                         if (targetAppliedStrategy == null) {
451                             targetAppliedStrategy = remainingAppliedStrategies.remove(appliedStrategy.getAppliedStrategyId());
452                             if (targetAppliedStrategy == null) {
453                                 targetAppliedStrategy = AppliedStrategy.Factory.newInstance();
454                                 Integer newId = TemporaryDataHelper.<Integer>getNewNegativeIdForTemporaryData(getSession(), targetAppliedStrategy.getClass());
455                                 targetAppliedStrategy.setAppliedStratId(newId);
456                                 targetAppliedStrategy.setStrategy(target);
457                                 target.addAppliedStrategies(targetAppliedStrategy);
458                                 targetAppliedStrategy.setMonitoringLocation(load(MonitoringLocationImpl.class, appliedStrategy.getId()));
459                             }
460                             if (appliedStrategy.getSamplingDepartment() != null) {
461                                 targetAppliedStrategy.setDepartment(load(DepartmentImpl.class, appliedStrategy.getSamplingDepartment().getId()));
462                             } else {
463                                 targetAppliedStrategy.setDepartment(null);
464                             }
465                             getSession().save(targetAppliedStrategy);
466                             getSession().flush();
467                             appliedStrategy.setAppliedStrategyId(targetAppliedStrategy.getAppliedStratId());
468                             previousAppliedStrategy = targetAppliedStrategy;
469                         }
470 
471                         // build key for applied period
472                         String appliedStrategyKey = buildAppliedStrategyKey(appliedStrategy);
473                         // get existing applied strategy with applied period
474                         AppliedStrategy existingAppliedStrategy = remainingAppliedStrategyByKey.remove(appliedStrategyKey);
475                         if (existingAppliedStrategy == null && appliedStrategy.getStartDate() != null && appliedStrategy.getEndDate() != null) {
476 
477                             // create period if both dates are set
478                             AppliedPeriod appliedPeriod = AppliedPeriod.Factory.newInstance();
479                             AppliedPeriodPK appliedPeriodPK = new AppliedPeriodPK();
480                             appliedPeriodPK.setAppliedStrategy((AppliedStrategyImpl) targetAppliedStrategy);
481                             appliedPeriodPK.setAppliedPeriodStartDt(Dates.convertToDate(appliedStrategy.getStartDate(), dbTimezone));
482                             appliedPeriod.setAppliedPeriodPk(appliedPeriodPK);
483                             appliedPeriod.setAppliedPeriodEndDt(Dates.convertToDate(appliedStrategy.getEndDate(), dbTimezone));
484 
485                             // if only end date change (PK still the same), the addAppliedPeriods will not works
486                             AppliedPeriod periodToUpdate = null;
487                             for (AppliedPeriod existingPeriod : targetAppliedStrategy.getAppliedPeriods()) {
488                                 LocalDate startDate = Dates.convertToLocalDate(existingPeriod.getAppliedPeriodPk().getAppliedPeriodStartDt(), dbTimezone);
489                                 if (Dates.isSameDay(startDate, appliedStrategy.getStartDate())) {
490                                     periodToUpdate = existingPeriod;
491                                     break;
492                                 }
493                             }
494 
495                             if (periodToUpdate == null) {
496                                 // create new period
497                                 targetAppliedStrategy.addAppliedPeriods(appliedPeriod);
498                             } else {
499                                 // update end date only
500                                 periodToUpdate.setAppliedPeriodEndDt(Dates.convertToDate(appliedStrategy.getEndDate(), dbTimezone));
501                                 // check surveys outside period (Mantis #48011)
502                                 checkDataOutsidePeriod(periodToUpdate);
503                                 // update
504                                 getSession().update(periodToUpdate);
505                             }
506                             getSession().update(targetAppliedStrategy);
507                         }
508 
509                         // save all PmfmStrategies into PmfmAppliedStrategies
510                         savePmfmAppliedStrategies(strategy, target, targetAppliedStrategy, appliedStrategy.getAnalysisDepartment());
511 
512                         // save appliedStrategy
513                         getSession().update(targetAppliedStrategy);
514                     }
515                     // delete remaining data
516                     if (!remainingAppliedStrategyByKey.isEmpty()) {
517                         // some applied period to delete
518                         for (String key : remainingAppliedStrategyByKey.keySet()) {
519                             AppliedStrategy appliedStrategy = remainingAppliedStrategyByKey.get(key);
520                             // if this applied strategy still in remainingAppliedStrategies, it will be remove later
521                             if (remainingAppliedStrategies.containsValue(appliedStrategy)) {
522                                 continue;
523                             }
524                             List<String> value = DaliBeans.split(key, "|");
525                             String startDateValue = value.get(1);
526                             LocalDate startDate = parseAppliedStrategyDate(startDateValue);
527                             String endDateValue = value.get(2);
528                             LocalDate endDate = parseAppliedStrategyDate(endDateValue);
529                             Iterator<AppliedPeriod> it = appliedStrategy.getAppliedPeriods().iterator();
530                             while (it.hasNext()) {
531                                 AppliedPeriod appliedPeriod = it.next();
532                                 LocalDate appliedPeriodStartDate = Dates.convertToLocalDate(appliedPeriod.getAppliedPeriodStartDt(), dbTimezone);
533                                 LocalDate appliedPeriodEndDate = Dates.convertToLocalDate(appliedPeriod.getAppliedPeriodEndDt(), dbTimezone);
534                                 if (Dates.isSameDay(appliedPeriodStartDate, startDate) && Dates.isSameDay(appliedPeriodEndDate, endDate)) {
535                                     // check surveys inside period (Mantis #48011)
536                                     checkDataInsidePeriod(appliedPeriod);
537                                     it.remove();
538                                 }
539                             }
540                             getSession().update(appliedStrategy);
541                         }
542                     }
543 
544                     // delete remaining applied strategies
545                     if (!remainingAppliedStrategies.isEmpty()) {
546                         target.getAppliedStrategies().removeAll(remainingAppliedStrategies.values());
547                         // delete
548                         deleteAppliedStrategies(remainingAppliedStrategies.keySet());
549                     }
550 
551                 } else {
552 
553                     if (CollectionUtils.isNotEmpty(target.getAppliedStrategies())) {
554                         List<Integer> appliedStrategyIds = DaliBeans.collectProperties(target.getAppliedStrategies(), "appliedStratId");
555                         target.getAppliedStrategies().clear();
556                         deleteAppliedStrategies(appliedStrategyIds);
557                     }
558                 }
559             }
560 
561             // save entity
562             update(target);
563 
564             getSession().flush();
565             getSession().clear();
566         }
567 
568         // remove remaining strategies
569         if (!remainingStrategieIds.isEmpty()) {
570             removeByIds(remainingStrategieIds);
571         }
572     }
573 
574     private void checkDataInsidePeriod(AppliedPeriod periodToDelete) {
575         long count = queryCount("countSurveysByProgramLocationAndInsideDates",
576                 "programCode", StringType.INSTANCE, periodToDelete.getAppliedPeriodPk().getAppliedStrategy().getStrategy().getProgram().getProgCd(),
577                 "locationId", IntegerType.INSTANCE, periodToDelete.getAppliedPeriodPk().getAppliedStrategy().getMonitoringLocation().getMonLocId(),
578                 "appliedStrategyId", IntegerType.INSTANCE, periodToDelete.getAppliedPeriodPk().getAppliedStrategy().getAppliedStratId(),
579                 "startDate", DateType.INSTANCE, periodToDelete.getAppliedPeriodPk().getAppliedPeriodStartDt(),
580                 "endDate", DateType.INSTANCE, periodToDelete.getAppliedPeriodEndDt(),
581                 "synchronizationStatusToIgnore", StringType.INSTANCE, SynchronizationStatus.DELETED.getValue()
582         );
583 
584         if (count > 0)
585             throw new SaveForbiddenException(SaveForbiddenException.Type.ATTACHED_DATA, "surveys inside deleted period",
586                     Collections.singleton(periodToDelete.getAppliedPeriodPk().getAppliedStrategy().getStrategy().getStratNm()));
587 
588     }
589 
590     private void checkDataOutsidePeriod(AppliedPeriod periodToUpdate) {
591         long count = queryCount("countSurveysByProgramLocationAndOutsideDates",
592                 "programCode", StringType.INSTANCE, periodToUpdate.getAppliedPeriodPk().getAppliedStrategy().getStrategy().getProgram().getProgCd(),
593                 "locationId", IntegerType.INSTANCE, periodToUpdate.getAppliedPeriodPk().getAppliedStrategy().getMonitoringLocation().getMonLocId(),
594                 "appliedStrategyId", IntegerType.INSTANCE, periodToUpdate.getAppliedPeriodPk().getAppliedStrategy().getAppliedStratId(),
595                 "startDate", DateType.INSTANCE, periodToUpdate.getAppliedPeriodPk().getAppliedPeriodStartDt(),
596                 "endDate", DateType.INSTANCE, periodToUpdate.getAppliedPeriodEndDt(),
597                 "synchronizationStatusToIgnore", StringType.INSTANCE, SynchronizationStatus.DELETED.getValue()
598         );
599 
600         if (count > 0)
601             throw new SaveForbiddenException(SaveForbiddenException.Type.ATTACHED_DATA, "surveys outside modified period",
602                     Collections.singleton(periodToUpdate.getAppliedPeriodPk().getAppliedStrategy().getStrategy().getStratNm()));
603 
604     }
605 
606     private void savePmfmAppliedStrategies(StrategyDTO strategy, Strategy target, AppliedStrategy appliedStrategy, DepartmentDTO analysisDepartment) {
607 
608         if (strategy.isPmfmStrategiesLoaded()) {
609             if (!strategy.isPmfmStrategiesEmpty()) {
610 
611                 Map<Integer, PmfmAppliedStrategy> remainingPmfmAppliedStrategiesByPmfmStrategyId =
612                         DaliBeans.mapByProperty(appliedStrategy.getPmfmAppliedStrategies(), "pmfmAppliedStrategyPk.pmfmStrategy.pmfmStratId");
613 
614                 // pmfmStrategies already in strategy entity
615                 for (PmfmStrategy pmfmStrategy : target.getPmfmStrategies()) {
616 
617                     Integer pmfmStrategyId = pmfmStrategy.getPmfmStratId();
618                     // get already stored
619                     PmfmAppliedStrategy pmfmAppliedStrategy = remainingPmfmAppliedStrategiesByPmfmStrategyId.remove(pmfmStrategyId);
620                     if (pmfmAppliedStrategy == null) {
621                         // create new
622                         pmfmAppliedStrategy = PmfmAppliedStrategy.Factory.newInstance();
623                         PmfmAppliedStrategyPK pmfmAppliedStrategyPK = new PmfmAppliedStrategyPK();
624                         pmfmAppliedStrategyPK.setAppliedStrategy((AppliedStrategyImpl) appliedStrategy);
625                         pmfmAppliedStrategyPK.setPmfmStrategy((PmfmStrategyImpl) pmfmStrategy);
626                         pmfmAppliedStrategy.setPmfmAppliedStrategyPk(pmfmAppliedStrategyPK);
627                     }
628 
629                     // update department
630                     // set analysis department to psfmProgStrat from analysisDepartment parameter (Mantis #43505)
631                     if (analysisDepartment != null) {
632                         pmfmAppliedStrategy.setDepartment(load(DepartmentImpl.class, analysisDepartment.getId()));
633                     } else {
634                         pmfmAppliedStrategy.setDepartment(null);
635                     }
636 
637                     // save and affect to appliedStrategy
638                     getSession().saveOrUpdate(pmfmAppliedStrategy);
639                     appliedStrategy.addPmfmAppliedStrategies(pmfmAppliedStrategy);
640                 }
641 
642                 // remove remaining PmfmAppliedStrategies
643                 if (!remainingPmfmAppliedStrategiesByPmfmStrategyId.isEmpty()) {
644                     appliedStrategy.getPmfmAppliedStrategies().removeAll(remainingPmfmAppliedStrategiesByPmfmStrategyId.values());
645                 }
646 
647             } else {
648                 // remove pmfmAppliedStrategies
649                 if (appliedStrategy.getPmfmAppliedStrategies() != null) {
650                     appliedStrategy.getPmfmAppliedStrategies().clear();
651                 }
652             }
653         }
654     }
655 
656     private void savePmfmStrategy(PmfmStrategyDTO pmfmStrategyDTO, PmfmStrategy pmfmStrategy, int rank) {
657 
658         // presentation rank
659         pmfmStrategy.setPmfmStratPresRk(rank);
660 
661         // pmfm
662         pmfmStrategy.setPmfm(load(PmfmImpl.class, pmfmStrategyDTO.getPmfm().getId()));
663 
664         // grouping (= individualised measurement)
665         pmfmStrategy.setPmfmStratParIsIndiv(Daos.convertToString(pmfmStrategyDTO.isGrouping()));
666 
667         // unique by taxon
668         pmfmStrategy.setPmfmStratIsUniqueByTaxon(Daos.convertToString(pmfmStrategyDTO.isUnique()));
669 
670         // acquisition levels
671         Map<String, AcquisitionLevel> remainingAcquisitionLevels = DaliBeans.mapByProperty(pmfmStrategy.getAcquisitionLevels(), "acquisLevelCd");
672 
673         // SURVEY acquisition level
674         if (BooleanUtils.toBoolean(pmfmStrategyDTO.isSurvey())) {
675             AcquisitionLevel acquisitionLevel = remainingAcquisitionLevels.remove(AcquisitionLevelCode.SURVEY.getValue());
676             if (acquisitionLevel == null) {
677                 // add SURVEY acquisition level
678                 acquisitionLevel = load(AcquisitionLevelImpl.class, AcquisitionLevelCode.SURVEY.getValue());
679                 pmfmStrategy.addAcquisitionLevels(acquisitionLevel);
680             }
681         }
682 
683         // SAMPLING_OPERATION acquisition level
684         if (BooleanUtils.toBoolean(pmfmStrategyDTO.isSampling())) {
685             AcquisitionLevel acquisitionLevel = remainingAcquisitionLevels.remove(AcquisitionLevelCode.SAMPLING_OPERATION.getValue());
686             if (acquisitionLevel == null) {
687                 // add SURVEY acquisition level
688                 acquisitionLevel = load(AcquisitionLevelImpl.class, AcquisitionLevelCode.SAMPLING_OPERATION.getValue());
689                 pmfmStrategy.addAcquisitionLevels(acquisitionLevel);
690             }
691         }
692 
693         // delete remaining acquisition levels
694         if (!remainingAcquisitionLevels.isEmpty()) {
695             pmfmStrategy.getAcquisitionLevels().removeAll(remainingAcquisitionLevels.values());
696         }
697     }
698 
699     /** {@inheritDoc} */
700     @Override
701     public void deleteAppliedStrategies(String programCode, Collection<Integer> monitoringLocationIds) {
702         Assert.notBlank(programCode);
703         if (monitoringLocationIds == null) return;
704         Set<Integer> idsToDelete = monitoringLocationIds.stream().filter(Objects::nonNull).collect(Collectors.toSet());
705         if (CollectionUtils.isEmpty(idsToDelete)) return;
706 
707         Query query = createQuery("deleteAppliedStrategiesByProgramCodeAndMonitoringLocationIds", "programCode", StringType.INSTANCE, programCode);
708         query.setParameterList("monitoringLocationIds", idsToDelete);
709         query.executeUpdate();
710 
711         getSession().flush();
712         getSession().clear();
713     }
714 
715     /** {@inheritDoc} */
716     @Override
717     public void removeByProgramCode(String programCode) {
718         Assert.notBlank(programCode);
719 
720         // delete Strategies
721         List<Integer> strategyIds = DaliBeans.collectIds(getStrategiesByProgramCode(programCode));
722         removeByStrategyIds(strategyIds);
723     }
724 
725     /** {@inheritDoc} */
726     @Override
727     public void removeByStrategyIds(Collection<Integer> strategyIds) {
728         if (strategyIds == null) return;
729         Set<Integer> idsToDelete = strategyIds.stream().filter(Objects::nonNull).collect(Collectors.toSet());
730         if (CollectionUtils.isEmpty(idsToDelete)) return;
731 
732         // the Hibernate remove method works fine (against direct query that can not handle PMFM_STRAT_ACQUIS_LEVEL table)
733         // or execute a first query doing that...
734         idsToDelete.forEach(this::remove);
735     }
736 
737     /** {@inheritDoc} */
738     @Override
739     public void deleteAppliedStrategies(Collection<Integer> appliedStrategyIds) {
740         if (appliedStrategyIds == null) return;
741         Set<Integer> idsToDelete = appliedStrategyIds.stream().filter(Objects::nonNull).collect(Collectors.toSet());
742         if (CollectionUtils.isEmpty(idsToDelete)) return;
743 
744         // call dao instead of bulk delete query to check attached data when deleting period
745         idsToDelete.forEach(idToDelete -> appliedStrategyDao.remove(idToDelete));
746 
747 //        Query query = createQuery("deleteAppliedStrategiesByIds");
748 //        query.setParameterList("appliedStrategyIds", idsToDelete);
749 //        query.executeUpdate();
750 
751     }
752 
753     private void deletePmfmStrategies(List<Integer> pmfmStrategyIds) {
754         if (pmfmStrategyIds == null) return;
755         Set<Integer> idsToDelete = pmfmStrategyIds.stream().filter(Objects::nonNull).collect(Collectors.toSet());
756         if (CollectionUtils.isEmpty(idsToDelete)) return;
757 
758         // Delete associated qualitative values
759         pmfmStrategyDao.deleteQualitativeValues(idsToDelete);
760 
761         // Execute deletion by query (Mantis #51602)
762         Query query = createQuery("deletePmfmAppliedStrategiesByPmfmStrategyIds");
763         query.setParameterList("pmfmStrategyIds", idsToDelete);
764         query.executeUpdate();
765 
766         query = createQuery("deletePmfmStrategiesByIds");
767         query.setParameterList("pmfmStrategyIds", idsToDelete);
768         query.executeUpdate();
769     }
770 
771     /** {@inheritDoc} */
772     @Override
773     public void saveStrategyByLocation(ProgStratDTO strategy, int locationId) {
774 
775         Assert.notNull(strategy);
776         Assert.notNull(strategy.getId());
777         Assert.isTrue((strategy.getStartDate() == null) == (strategy.getEndDate() == null));
778 
779         AppliedStrategyImpl target = get(AppliedStrategyImpl.class, strategy.getAppliedStrategyId());
780         // the applied strategy should exists
781         Assert.notNull(target);
782         // just to be sure
783         Assert.equals(target.getMonitoringLocation().getMonLocId(), locationId);
784 
785         TimeZone dbTimezone = config.getDbTimezone();
786 
787         Collection<AppliedPeriod> appliedPeriods = target.getAppliedPeriods();
788         if (appliedPeriods.size() > 1) {
789             throw new DaliTechnicalException("can't update more than 1 applied periods");
790         }
791         if (strategy.getStartDate() != null && strategy.getEndDate() != null) {
792             // add or update applied period
793             boolean create = false;
794 
795             AppliedPeriod appliedPeriod = null;
796             if (CollectionUtils.isEmpty(appliedPeriods)) {
797                 create = true;
798             } else {
799                 appliedPeriod = appliedPeriods.iterator().next();
800                 if (!Dates.isSameDay(Dates.convertToLocalDate(appliedPeriod.getAppliedPeriodStartDt(), dbTimezone), strategy.getStartDate())) {
801                     // update by delete and create, because update start date doesn't work
802                     target.getAppliedPeriods().clear();
803                     create = true;
804                 }
805             }
806             if (create) {
807                 appliedPeriod = AppliedPeriod.Factory.newInstance();
808                 AppliedPeriodPK appliedPeriodPK = new AppliedPeriodPK();
809                 appliedPeriodPK.setAppliedStrategy(target);
810                 appliedPeriodPK.setAppliedPeriodStartDt(Dates.convertToDate(strategy.getStartDate(), dbTimezone));
811                 appliedPeriod.setAppliedPeriodPk(appliedPeriodPK);
812                 appliedPeriod.setAppliedPeriodEndDt(Dates.convertToDate(strategy.getEndDate(), dbTimezone));
813                 target.addAppliedPeriods(appliedPeriod);
814             } else {
815                 // update end date only
816                 appliedPeriod.setAppliedPeriodEndDt(Dates.convertToDate(strategy.getEndDate(), dbTimezone));
817                 getSession().update(appliedPeriod);
818             }
819             getSession().update(target);
820 
821         } else {
822             // delete applied period if exists
823             target.getAppliedPeriods().clear();
824             getSession().update(target);
825         }
826 
827         getSession().flush();
828         getSession().clear();
829     }
830 
831 // PRIVATE METHODS
832 
833     private Map<String, AppliedStrategy> buildAppliedStrategyMap(Collection<AppliedStrategy> appliedStrategies, TimeZone dbTimezone) {
834         Map<String, AppliedStrategy> result = Maps.newHashMap();
835 
836         for (AppliedStrategy appliedStrategy : appliedStrategies) {
837 
838             if (CollectionUtils.isNotEmpty(appliedStrategy.getAppliedPeriods())) {
839                 for (AppliedPeriod appliedPeriod : appliedStrategy.getAppliedPeriods()) {
840 
841                     Object[] parts = new Object[]{
842                             appliedStrategy.getMonitoringLocation().getMonLocId(),
843                             formatAppliedStrategyDate(Dates.convertToLocalDate(appliedPeriod.getAppliedPeriodStartDt(), dbTimezone)),
844                             formatAppliedStrategyDate(Dates.convertToLocalDate(appliedPeriod.getAppliedPeriodEndDt(), dbTimezone))
845                     };
846                     String key = Joiner.on('|').useForNull("null").join(parts);
847                     result.put(key, appliedStrategy);
848 
849                 }
850             } else {
851 
852                 Object[] parts = new Object[]{
853                         appliedStrategy.getMonitoringLocation().getMonLocId(),
854                         null, null
855                 };
856                 String key = Joiner.on('|').useForNull("null").join(parts);
857                 result.put(key, appliedStrategy);
858             }
859         }
860 
861         return result;
862     }
863 
864     private String buildAppliedStrategyKey(AppliedStrategyDTO appliedStrategy) {
865         Assert.notNull(appliedStrategy);
866         Assert.notNull(appliedStrategy.getId());
867         return Joiner.on('|').useForNull("null").join(
868                 appliedStrategy.getId(),
869                 formatAppliedStrategyDate(appliedStrategy.getStartDate()),
870                 formatAppliedStrategyDate(appliedStrategy.getEndDate())
871         );
872     }
873 
874     private String formatAppliedStrategyDate(LocalDate date) {
875         return date.format(DateTimeFormatter.ofPattern(DATE_PATTERN));
876     }
877 
878     private LocalDate parseAppliedStrategyDate(String string) {
879         return LocalDate.parse(string, DateTimeFormatter.ofPattern(DATE_PATTERN));
880     }
881 
882 
883     private StrategyDTO toStrategyDTO(Iterator<Object> source) {
884         StrategyDTO result = DaliBeanFactory.newStrategyDTO();
885 
886         // Id
887         result.setId((Integer) source.next());
888 
889         // Name
890         result.setName((String) source.next());
891 
892         // Descritpion => Comment
893         result.setComment((String) source.next());
894 
895         return result;
896     }
897 
898     private AppliedStrategyDTO toAppliedStrategyDTO(Iterator<Object> source) {
899         AppliedStrategyDTO target = DaliBeanFactory.newAppliedStrategyDTO();
900 
901         // applied strategy : id
902         target.setAppliedStrategyId((Integer) source.next());
903 
904         // monitoring location: id
905         target.setId((Integer) source.next());
906 
907         // Use the cached location (Mantis #47429)
908         LocationDTO location = monitoringLocationDao.getLocationById(target.getId());
909 
910         // Label
911         target.setLabel(location.getLabel());
912 
913         // Name
914         target.setName(location.getName());
915 
916         // Comments
917         target.setComment(location.getComment());
918 
919         // Status (location)
920         target.setStatus(location.getStatus());
921 
922         // Department
923         Integer departmentId = (Integer) source.next();
924         if (departmentId != null) {
925             target.setSamplingDepartment(departmentDao.getDepartmentById(departmentId));
926         }
927 
928         return target;
929     }
930 
931     private AppliedStrategyDTO toAppliedPeriod(Iterator<Object> source, TimeZone dbTimezone) {
932         // Starts with applied strategy columns
933         AppliedStrategyDTO target = toAppliedStrategyDTO(source);
934 
935         // then start/end date
936         target.setStartDate(Dates.convertToLocalDate(Daos.convertToDate(source.next()), dbTimezone));
937         target.setEndDate(Dates.convertToLocalDate(Daos.convertToDate(source.next()), dbTimezone));
938 
939         // set also previous dates
940         target.setPreviousStartDate(target.getStartDate());
941         target.setPreviousEndDate(target.getEndDate());
942 
943         return target;
944     }
945 
946     private PmfmStrategyDTO toPmfmStrategyDTO(Iterator<Object> source) {
947         PmfmStrategyDTO target = DaliBeanFactory.newPmfmStrategyDTO();
948 
949         // AppliedStrategyId
950         target.setId((Integer) source.next());
951 
952         // Pmfm id
953         target.setPmfm(pmfmDao.getPmfmById((Integer) source.next()));
954 
955         // note SBO (mantis #24206) :
956         //    Actuellement la notion d'individus dans Quadrige est utilisée comme un regroupement de résultats.
957         //  Cette définition correspond bien au besoin BD Recif.
958         //    Ok pour utiliser PMFM_STRATEGY.PMFM_STRAT_PAR_IS_INDIV pour identifier le type de tableau (mesures
959         //  regroupées ou non regroupées) dans lequel le PSFM doit être affiché
960         target.setGrouping(Daos.safeConvertToBoolean(source.next(), false));
961 
962         target.setUnique(Daos.safeConvertToBoolean(source.next(), false));
963 
964         // rank order
965         target.setRankOrder((Integer) source.next());
966 
967         return target;
968     }
969 
970     /**
971      * <p>toProgStratDTO.</p>
972      *
973      * @param source a {@link java.util.Iterator} object.
974      * @return a {@link fr.ifremer.dali.dto.configuration.programStrategy.ProgStratDTO} object.
975      */
976     private ProgStratDTO toProgStratDTO(Iterator<Object> source, TimeZone dbTimezone) {
977         ProgStratDTO target = DaliBeanFactory.newProgStratDTO();
978 
979         // applied strategy id
980         target.setAppliedStrategyId((Integer) source.next());
981 
982         // program
983         {
984             ProgramDTO program = DaliBeanFactory.newProgramDTO();
985             target.setProgram(program);
986 
987             // code, name
988             program.setCode((String) source.next());
989             program.setName((String) source.next());
990             program.setStatus(referentialDao.getStatusByCode((String) source.next()));
991         }
992 
993         // strategy: id, name
994         target.setId((Integer) source.next());
995         target.setName((String) source.next());
996 
997         // then start/end date
998         target.setStartDate(Dates.convertToLocalDate(Daos.convertToDate(source.next()), dbTimezone));
999         target.setEndDate(Dates.convertToLocalDate(Daos.convertToDate(source.next()), dbTimezone));
1000 
1001         Integer depId = (Integer) source.next();
1002         if (depId != null) {
1003             target.setDepartment(departmentDao.getDepartmentById(depId));
1004         }
1005 
1006         return target;
1007     }
1008 
1009     /**
1010      * <p>strategyDTOToEntity.</p>
1011      *
1012      * @param program a {@link fr.ifremer.dali.dto.configuration.programStrategy.ProgramDTO} object.
1013      * @param source a {@link fr.ifremer.dali.dto.configuration.programStrategy.StrategyDTO} object.
1014      * @param target a {@link fr.ifremer.quadrige3.core.dao.administration.strategy.Strategy} object.
1015      */
1016     private void strategyDTOToEntity(ProgramDTO program, StrategyDTO source, Strategy target) {
1017 
1018         target.setStratNm(source.getName());
1019         target.setStratDc(source.getComment());
1020 
1021         // Set update date on strategy (only for local strategy)
1022         if (QuadrigeBeans.isLocalStatus(program.getStatus())) {
1023             target.setUpdateDt(newUpdateTimestamp());
1024         }
1025 
1026         // User privileges: copy manager from program (see mantis #28029)
1027         if (CollectionUtils.isEmpty(program.getManagerPersons())) {
1028             if (CollectionUtils.isNotEmpty(target.getQusers())) {
1029                 target.getQusers().clear();
1030             }
1031         }
1032         else {
1033             Daos.replaceEntities(
1034                     target.getQusers(),
1035                     program.getManagerPersons(),
1036                     person -> load(QuserImpl.class, person.getId())
1037             );
1038         }
1039     }
1040 
1041 
1042     private void fillQualitativeValues(List<PmfmStrategyDTO> pmfmStrategies) {
1043         pmfmStrategies.forEach(pmfmStrategy -> {
1044             // get actual restricted list
1045 
1046             List<Integer> qvIds = pmfmStrategyDao.getQualitativeValueIds(pmfmStrategy.getId());
1047 
1048             pmfmStrategy.setQualitativeValues(
1049                 pmfmStrategy.getPmfm().getQualitativeValues().stream()
1050                     .filter(qualitativeValueDTO -> CollectionUtils.isEmpty(qvIds) || qvIds.contains(qualitativeValueDTO.getId()))
1051                     .collect(Collectors.toList())
1052             );
1053 
1054         });
1055     }
1056 
1057     private void saveQualitativeValues(PmfmStrategyDTO pmfmStrategy) {
1058         // delete all first
1059 
1060         pmfmStrategyDao.deleteQualitativeValues(pmfmStrategy.getId());
1061 
1062         if (pmfmStrategy.sizeQualitativeValues() != pmfmStrategy.getPmfm().sizeQualitativeValues()) {
1063             // then insert if sub-list is different from pmfm qualitative value list
1064             pmfmStrategy.getQualitativeValues()
1065                 .forEach(qualitativeValueDTO -> pmfmStrategyDao.saveQualitativeValue(pmfmStrategy.getId(), pmfmStrategy.getPmfm().getId(), qualitativeValueDTO.getId()));
1066         }
1067     }
1068 
1069 
1070 }