View Javadoc
1   package fr.ifremer.tutti.service.catches;
2   
3   /*
4    * #%L
5    * Tutti :: Service
6    * %%
7    * Copyright (C) 2012 - 2014 Ifremer
8    * %%
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU General Public License as
11   * published by the Free Software Foundation, either version 3 of the 
12   * License, or (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 General Public 
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/gpl-3.0.html>.
22   * #L%
23   */
24  
25  import com.google.common.collect.Lists;
26  import com.google.common.collect.Maps;
27  import fr.ifremer.tutti.persistence.InvalidBatchModelException;
28  import fr.ifremer.tutti.persistence.ProgressionModel;
29  import fr.ifremer.tutti.persistence.entities.data.AccidentalBatch;
30  import fr.ifremer.tutti.persistence.entities.data.BatchContainer;
31  import fr.ifremer.tutti.persistence.entities.data.CatchBatch;
32  import fr.ifremer.tutti.persistence.entities.data.Cruise;
33  import fr.ifremer.tutti.persistence.entities.data.FishingOperation;
34  import fr.ifremer.tutti.persistence.entities.data.IndividualObservationBatch;
35  import fr.ifremer.tutti.persistence.entities.data.MarineLitterBatch;
36  import fr.ifremer.tutti.persistence.entities.data.SpeciesBatch;
37  import fr.ifremer.tutti.persistence.entities.data.SpeciesBatchFrequency;
38  import fr.ifremer.tutti.persistence.entities.protocol.SpeciesProtocol;
39  import fr.ifremer.tutti.persistence.entities.protocol.TuttiProtocol;
40  import fr.ifremer.tutti.persistence.entities.protocol.TuttiProtocols;
41  import fr.ifremer.tutti.persistence.entities.referential.Species;
42  import fr.ifremer.tutti.service.AbstractTuttiService;
43  import fr.ifremer.tutti.service.DecoratorService;
44  import fr.ifremer.tutti.service.PersistenceService;
45  import fr.ifremer.tutti.service.TuttiDataContext;
46  import fr.ifremer.tutti.service.TuttiServiceContext;
47  import fr.ifremer.tutti.service.ValidationService;
48  import org.apache.commons.collections4.CollectionUtils;
49  import org.apache.commons.collections4.MapUtils;
50  import org.apache.commons.io.FileUtils;
51  import org.apache.commons.logging.Log;
52  import org.apache.commons.logging.LogFactory;
53  import org.nuiton.decorator.Decorator;
54  import org.nuiton.jaxx.application.ApplicationBusinessException;
55  import org.nuiton.jaxx.application.ApplicationTechnicalException;
56  import org.nuiton.validator.NuitonValidatorResult;
57  import org.nuiton.validator.NuitonValidatorScope;
58  
59  import java.io.File;
60  import java.io.IOException;
61  import java.util.ArrayList;
62  import java.util.LinkedHashMap;
63  import java.util.LinkedHashSet;
64  import java.util.List;
65  import java.util.Map;
66  import java.util.Set;
67  import java.util.stream.Collectors;
68  
69  import static org.nuiton.i18n.I18n.t;
70  
71  /**
72   * Service to validate the operations of a cruise
73   *
74   * @author Kevin Morin - kmorin@codelutin.com
75   * @since 1.4
76   */
77  public class ValidateCruiseOperationsService extends AbstractTuttiService {
78  
79      private static final Log log = LogFactory.getLog(ValidateCruiseOperationsService.class);
80  
81      protected PersistenceService persistenceService;
82  
83      protected ValidationService validationService;
84  
85      protected WeightComputingService weightComputingService;
86  
87      protected DecoratorService decoratorService;
88  
89      @Override
90      public void setServiceContext(TuttiServiceContext context) {
91          super.setServiceContext(context);
92          persistenceService = getService(PersistenceService.class);
93          validationService = getService(ValidationService.class);
94          weightComputingService = getService(WeightComputingService.class);
95          decoratorService = getService(DecoratorService.class);
96      }
97  
98      /**
99       * Validates the given cruise id.
100      *
101      * @return the validation results
102      */
103     public NuitonValidatorResult validateCruise(ProgressionModel progressionModel, Integer cruiseId) {
104 
105         progressionModel.increments(t("tutti.service.validateCruise.cruise.loading", cruiseId));
106         Cruise cruise = persistenceService.getCruise(cruiseId);
107 
108         Decorator<Cruise> decorator = decoratorService.getDecoratorByType(Cruise.class);
109         progressionModel.increments(t("tutti.service.validateCruise.cruise.check", cruiseId, decorator.toString(cruise)));
110 
111         return validationService.validateValidateCruise(cruise);
112 
113     }
114 
115     /**
116      * Validates the operations of the given operation ids.
117      *
118      * @return a map containing the operations and the validation results
119      */
120     public LinkedHashMap<FishingOperation, NuitonValidatorResult> validateOperations(ProgressionModel progressionModel, List<Integer> operationIds) {
121 
122         LinkedHashMap<FishingOperation, NuitonValidatorResult> result = new LinkedHashMap<>();
123 
124         Decorator<FishingOperation> decorator = decoratorService.getDecoratorByType(FishingOperation.class);
125         for (Integer operationId : operationIds) {
126 
127             progressionModel.increments(t("tutti.service.validateCruise.operations.loading", operationId));
128             FishingOperation operation = persistenceService.getFishingOperation(operationId);
129 
130             progressionModel.increments(t("tutti.service.validateCruise.operations.check", operationId, decorator.toString(operation)));
131             NuitonValidatorResult validator = validationService.validateValidateFishingOperation(operation);
132 
133             checkOperation(operation, validator);
134             result.put(operation, validator);
135 
136         }
137 
138         return result;
139 
140     }
141 
142     /**
143      * Validates the given cruise.
144      *
145      * @return the validation result
146      */
147     public NuitonValidatorResult validateCruiseCruise(Cruise cruise) {
148         return validationService.validateValidateCruise(cruise);
149     }
150 
151     /**
152      * Validates the operation of the currently selected cruise whose id is the given id.
153      *
154      * @return the validation results
155      */
156     public NuitonValidatorResult validateCruiseOperation(FishingOperation operation) {
157         NuitonValidatorResult validator = validationService.validateValidateFishingOperation(operation);
158         checkOperation(operation, validator);
159         return validator;
160     }
161 
162     /**
163      * Validates the operation of the currently selected cruise whose id is the given id.
164      *
165      * @return the validation results
166      */
167     public NuitonValidatorResult validateCruiseOperation(CatchBatch catches) {
168         FishingOperation operation = persistenceService.getFishingOperation(catches.getFishingOperation().getIdAsInt());
169         NuitonValidatorResult validator = validationService.validateValidateFishingOperation(operation);
170 
171         checkOperation(operation, catches, validator);
172         return validator;
173     }
174 
175     /**
176      * @param file              where to write result
177      * @param validationResults result of validation
178      */
179     public void exportValidationResults(File file, Map<FishingOperation, NuitonValidatorResult> validationResults) {
180         try {
181             List<String> lines = Lists.newArrayList();
182             for (FishingOperation operation : validationResults.keySet()) {
183                 lines.addAll(getExportLines(operation, validationResults.get(operation)));
184                 lines.add("");
185             }
186             FileUtils.writeLines(file, lines);
187 
188         } catch (IOException e) {
189             throw new ApplicationTechnicalException(t("tutti.service.validateCruise.exportResult.error", file));
190         }
191     }
192 
193     /**
194      * @param file             where to write result
195      * @param operation        operation to check
196      * @param validationResult result of validation
197      */
198     public void exportValidationResult(File file, FishingOperation operation, NuitonValidatorResult validationResult) {
199         try {
200             List<String> lines = getExportLines(operation, validationResult);
201 
202             FileUtils.writeLines(file, lines);
203 
204         } catch (IOException e) {
205             throw new ApplicationTechnicalException(t("tutti.service.validateCruise.exportResult.error", file));
206         }
207     }
208 
209     /*
210      *  Internal methods
211      */
212 
213     /**
214      * Adds additional messages to the validation results
215      *
216      * @param fishingOperation the operation to validate
217      * @param validator        the validatpr containing the messages.
218      */
219     protected void checkOperation(FishingOperation fishingOperation, NuitonValidatorResult validator) {
220 
221         Integer fishingOperationId = fishingOperation.getIdAsInt();
222 
223         boolean withCatchBatch =
224                 persistenceService.isFishingOperationWithCatchBatch(
225                         fishingOperationId);
226 
227         if (!withCatchBatch) {
228             if (log.isWarnEnabled()) {
229                 log.warn("Skip fishing operation " + fishingOperation +
230                                  " since no catchBatch associated.");
231             }
232             Map<String, List<String>> errorMap = Maps.newHashMap();
233             errorMap.put("catches", Lists.newArrayList(t("tutti.validator.warning.fishingOperation.batch.notFound")));
234             validator.addMessagesForScope(NuitonValidatorScope.WARNING, errorMap);
235         } else {
236             try {
237                 CatchBatch catchBatch =
238                         persistenceService.getCatchBatchFromFishingOperation(fishingOperationId);
239 
240                 checkOperation(fishingOperation, catchBatch, validator);
241             } catch (InvalidBatchModelException e) {
242 
243                 // batch is not compatible with Tutti
244                 if (log.isDebugEnabled()) {
245                     log.debug("Invalid batch model", e);
246                 }
247                 Map<String, List<String>> errorMap = Maps.newHashMap();
248                 errorMap.put("catches", Lists.newArrayList(t("tutti.validator.warning.fishingOperation.invalid.batch.model")));
249                 validator.addMessagesForScope(NuitonValidatorScope.WARNING, errorMap);
250             }
251         }
252     }
253 
254     protected void transfertValidatorResult(NuitonValidatorResult validatorResult, List<String> errors) {
255         if (validatorResult.hasFatalMessages()) {
256             errors.addAll(validatorResult.getMessagesForScope(NuitonValidatorScope.FATAL));
257         }
258 
259         if (validatorResult.hasErrorMessagess()) {
260             errors.addAll(validatorResult.getMessagesForScope(NuitonValidatorScope.ERROR));
261         }
262     }
263 
264     /**
265      * Adds additional messages to the validation results
266      *
267      * @param fishingOperation the operation to validate
268      * @param catchBatch       the catchBatch to validate
269      * @param validator        the validatpr containing the messages.
270      */
271     protected void checkOperation(FishingOperation fishingOperation,
272                                   CatchBatch catchBatch,
273                                   NuitonValidatorResult validator) {
274         if (log.isDebugEnabled()) {
275             log.debug("Will check fishingOperation: " + fishingOperation);
276         }
277 
278         List<String> errors = Lists.newArrayList();
279         Integer fishingOperationId = fishingOperation.getIdAsInt();
280 
281         NuitonValidatorResult fishingOperationValidationResult = validationService.validateValidateFishingOperation(fishingOperation);
282         transfertValidatorResult(fishingOperationValidationResult, errors);
283 
284         BatchContainer<SpeciesBatch> rootSpeciesBatch = null;
285         boolean isCatchBatch = persistenceService.isFishingOperationWithCatchBatch(fishingOperationId);
286         boolean error = !isCatchBatch;
287 
288         if (isCatchBatch) {
289 
290             NuitonValidatorResult catchBatchValidatorResult = validationService.validateValidateCatchBatch(catchBatch);
291             transfertValidatorResult(catchBatchValidatorResult, errors);
292 
293             rootSpeciesBatch = persistenceService.getRootSpeciesBatch(fishingOperationId, true);
294 
295             if (rootSpeciesBatch != null) {
296                 List<SpeciesBatch> roots = rootSpeciesBatch.getChildren();
297 
298                 for (SpeciesBatch batch : roots) {
299                     NuitonValidatorResult speciesValidatorResult = validationService.validateValidateSpeciesBatch(batch);
300                     transfertValidatorResult(speciesValidatorResult, errors);
301 
302                     try {
303                         weightComputingService.computeSpeciesBatch(batch);
304 
305                     } catch (ApplicationBusinessException e) {
306                         errors.add(e.getMessage());
307                         error = true;
308                     }
309                 }
310             }
311         }
312         if (error) {
313             rootSpeciesBatch = persistenceService.getRootSpeciesBatch(fishingOperationId, true);
314         }
315 
316         BatchContainer<SpeciesBatch> rootBenthosBatch = null;
317         error = false;
318         if (isCatchBatch) {
319             rootBenthosBatch = persistenceService.getRootBenthosBatch(fishingOperationId, true);
320 
321             if (rootBenthosBatch != null) {
322                 List<SpeciesBatch> roots = rootBenthosBatch.getChildren();
323 
324                 for (SpeciesBatch batch : roots) {
325 
326                     NuitonValidatorResult benthosValidatorResult = validationService.validateValidateBenthosBatch(batch);
327                     transfertValidatorResult(benthosValidatorResult, errors);
328 
329                     try {
330                         weightComputingService.computeBenthosBatch(batch);
331 
332                     } catch (ApplicationBusinessException e) {
333                         errors.add(e.getMessage());
334                         error = true;
335                     }
336                 }
337             }
338         }
339         if (error) {
340             rootBenthosBatch = persistenceService.getRootBenthosBatch(fishingOperationId, true);
341         }
342         if (isCatchBatch) {
343             List<AccidentalBatch> accidentalBatchs = persistenceService.getAllAccidentalBatch(fishingOperationId);
344             if (accidentalBatchs != null) {
345                 for (AccidentalBatch accidentalBatch : accidentalBatchs) {
346                     NuitonValidatorResult accidentalBatchValidatorResult = validationService.validateValidateAccidentalBatch(accidentalBatch);
347                     transfertValidatorResult(accidentalBatchValidatorResult, errors);
348                 }
349             }
350             List<IndividualObservationBatch> individualObservationBatchs = persistenceService.getAllIndividualObservationBatchsForFishingOperation(fishingOperationId);
351             if (individualObservationBatchs != null) {
352                 for (IndividualObservationBatch individualObservationBatch : individualObservationBatchs) {
353                     NuitonValidatorResult individualObservationBatchValidatorResult = validationService.validateValidateIndividualObservationBatch(individualObservationBatch);
354                     transfertValidatorResult(individualObservationBatchValidatorResult, errors);
355                 }
356             }
357         }
358 
359         BatchContainer<MarineLitterBatch> rootMarineLitterBatch;
360         try {
361             Float weight = catchBatch == null ? null : catchBatch.getMarineLitterTotalWeight();
362             rootMarineLitterBatch = weightComputingService.getComputedMarineLitterBatches(fishingOperationId, weight);
363 
364         } catch (ApplicationBusinessException e) {
365             errors.add(e.getMessage());
366             rootMarineLitterBatch = persistenceService.getRootMarineLitterBatch(fishingOperationId);
367         }
368 
369         try {
370             if (catchBatch != null) {
371                 weightComputingService.computeCatchBatchWeights(catchBatch,
372                                                                 rootSpeciesBatch,
373                                                                 rootBenthosBatch,
374                                                                 rootMarineLitterBatch);
375             }
376         } catch (ApplicationBusinessException e) {
377             errors.add(e.getMessage());
378         }
379 
380         if (CollectionUtils.isNotEmpty(errors)) {
381             Map<String, List<String>> errorMap = Maps.newHashMap();
382             errorMap.put("catches", errors);
383             validator.addMessagesForScope(NuitonValidatorScope.ERROR, errorMap);
384         }
385 
386         TuttiDataContext dataContext = context.getDataContext();
387         if (dataContext.isProtocolFilled()) {
388 //            TuttiProtocol protocol = dataContext.getProtocol();
389             Map<String, List<String>> warningMap = Maps.newHashMap();
390 
391             Decorator<Species> speciesDecorator = decoratorService.getDecoratorByType(Species.class);
392             if (rootSpeciesBatch != null) {
393                 List<String> warnings = new ArrayList<>();
394                 for (SpeciesBatch batch : rootSpeciesBatch.getChildren()) {
395                     if (isSpeciesBatchInvalid(batch)) {
396                         String species = speciesDecorator.toString(batch.getSpecies());
397                         String categoryValue = decoratorService.getDecorator(batch.getSampleCategoryValue())
398                                                                .toString(batch.getSampleCategoryValue());
399                         warnings.add(t("tutti.validator.warning.species.protocolNotRespected", species, categoryValue));
400                     }
401                 }
402                 if (!warnings.isEmpty()) {
403                     warningMap.put("species", warnings);
404                 }
405             }
406 
407             if (rootBenthosBatch != null) {
408                 List<String> warnings = new ArrayList<>();
409                 for (SpeciesBatch batch : rootBenthosBatch.getChildren()) {
410                     if (isBenthosBatchInvalid(batch)) {
411                         String species = speciesDecorator.toString(batch.getSpecies());
412                         String categoryValue = decoratorService.getDecorator(batch.getSampleCategoryValue())
413                                                                .toString(batch.getSampleCategoryValue());
414                         warnings.add(t("tutti.validator.warning.benthos.protocolNotRespected", species, categoryValue));
415                     }
416                 }
417                 if (!warnings.isEmpty()) {
418                     warningMap.put("benthos", warnings);
419                 }
420             }
421 
422             if (MapUtils.isNotEmpty(warningMap)) {
423                 validator.addMessagesForScope(NuitonValidatorScope.WARNING, warningMap);
424             }
425         }
426     }
427 
428     /**
429      * Is the species batch respecting the protocol recommendations?
430      *
431      * @param batch       the batch to check (it should be a leaf)
432      * @param frequencies the frequencies of the batch
433      * @return true if the batch respects the protocol, false otherwise
434      */
435     public boolean isSpeciesBatchValid(SpeciesBatch batch, List<SpeciesBatchFrequency> frequencies) {
436         boolean result = true;
437 
438         TuttiProtocol protocol = persistenceService.getProtocol();
439 
440         if (protocol != null) {
441             Species species = batch.getSpecies();
442             List<SpeciesProtocol> speciesProtocols = protocol.getSpecies();
443             SpeciesProtocol speciesProtocol = TuttiProtocols.getSpeciesProtocol(species, speciesProtocols);
444 
445             if (speciesProtocol != null) {
446                 List<Integer> mandatoryCategories = speciesProtocol.getMandatorySampleCategoryId();
447 
448                 SpeciesBatch browsingBatch = batch;
449                 while (browsingBatch.getParentBatch() != null) {
450                     mandatoryCategories.remove(browsingBatch.getSampleCategoryId());
451                     browsingBatch = browsingBatch.getParentBatch();
452                 }
453                 result = mandatoryCategories.isEmpty() &&
454                         (speciesProtocol.getLengthStepPmfmId() != null
455                                 || CollectionUtils.isNotEmpty(frequencies)
456                                 || batch.getNumber() != null);
457             }
458         }
459         return result;
460     }
461 
462     /**
463      * Is the species batch respecting the protocol recommendations?
464      *
465      * @param batch the batch to check
466      * @return true if the batch or one of its children does not respect the protocol, false otherwise
467      */
468     protected boolean isSpeciesBatchInvalid(SpeciesBatch batch) {
469         if (batch.isChildBatchsEmpty()) {
470             List<SpeciesBatchFrequency> frequencies =
471                     persistenceService.getAllSpeciesBatchFrequency(batch.getIdAsInt());
472             return !isSpeciesBatchValid(batch, frequencies);
473         }
474 
475         for (SpeciesBatch child : batch.getChildBatchs()) {
476             boolean invalid = isSpeciesBatchInvalid(child);
477             if (invalid) {
478                 return true;
479             }
480         }
481         return false;
482     }
483 
484     /**
485      * Is the benthos batch respecting the protocol recommendations?
486      *
487      * @param batch the batch to check
488      * @return true if the batch or one of its children does not respect the protocol, false otherwise
489      */
490     protected boolean isBenthosBatchInvalid(SpeciesBatch batch) {
491         if (batch.isChildBatchsEmpty()) {
492             List<SpeciesBatchFrequency> frequencies =
493                     persistenceService.getAllBenthosBatchFrequency(batch.getIdAsInt());
494             return !isBenthosBatchValid(
495                     batch,
496                     frequencies);
497         }
498 
499         for (SpeciesBatch child : batch.getChildBatchs()) {
500             boolean invalid = isBenthosBatchInvalid(child);
501             if (invalid) {
502                 return true;
503             }
504         }
505         return false;
506     }
507 
508     /**
509      * @param operation        option to check
510      * @param validationResult result of validation
511      */
512     protected List<String> getExportLines(FishingOperation operation, NuitonValidatorResult validationResult) {
513         List<String> lines = Lists.newArrayList();
514 
515         lines.add(t("tutti.validator.export.operation",
516                     decoratorService.getDecoratorByType(FishingOperation.class).toString(operation)));
517 
518         // poussin 20170105 #8824: to remove duplicate error, we use set (as in UI)
519         Set<String> messages = new LinkedHashSet<String>(validationResult.getMessagesForScope(NuitonValidatorScope.ERROR));
520         lines.addAll(messages.stream().map(message -> t("tutti.validator.export.message.error", t(message))).collect(Collectors.toList()));
521         messages = new LinkedHashSet<String>(validationResult.getMessagesForScope(NuitonValidatorScope.WARNING));
522         lines.addAll(messages.stream().map(message -> t("tutti.validator.export.message.warning", t(message))).collect(Collectors.toList()));
523 
524         return lines;
525     }
526 
527     /**
528      * Is the benthos batch respecting the protocol recommendations?
529      *
530      * @param batch       the batch to check (it should be a leaf)
531      * @param frequencies the frequencies of the batch
532      * @return true if the batch respects the protocol, false otherwise
533      */
534     public boolean isBenthosBatchValid(SpeciesBatch batch, List<SpeciesBatchFrequency> frequencies) {
535 
536         TuttiProtocol protocol = persistenceService.getProtocol();
537 
538         boolean result = true;
539 
540         if (protocol != null) {
541             Species species = batch.getSpecies();
542             List<SpeciesProtocol> speciesProtocols = protocol.getBenthos();
543             SpeciesProtocol speciesProtocol = TuttiProtocols.getSpeciesProtocol(species, speciesProtocols);
544 
545             if (speciesProtocol != null) {
546                 // get the categories which should be set
547                 List<Integer> mandatoryCategories =
548                         speciesProtocol.getMandatorySampleCategoryId();
549 
550                 SpeciesBatch browsingBatch = batch;
551                 while (browsingBatch.getParentBatch() != null) {
552                     mandatoryCategories.remove(browsingBatch.getSampleCategoryId());
553                     browsingBatch = browsingBatch.getParentBatch();
554                 }
555                 result = mandatoryCategories.isEmpty() &&
556                         (speciesProtocol.getLengthStepPmfmId() != null
557                                 || CollectionUtils.isNotEmpty(frequencies)
558                                 || batch.getNumber() != null);
559             }
560         }
561         return result;
562     }
563 
564 }