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.base.Preconditions;
26  import com.google.common.collect.LinkedHashMultimap;
27  import com.google.common.collect.Multimap;
28  import fr.ifremer.tutti.persistence.entities.data.BatchContainer;
29  import fr.ifremer.tutti.persistence.entities.data.FishingOperation;
30  import fr.ifremer.tutti.persistence.entities.data.SampleCategoryModel;
31  import fr.ifremer.tutti.persistence.entities.data.SampleCategoryModelEntry;
32  import fr.ifremer.tutti.persistence.entities.data.SpeciesBatch;
33  import fr.ifremer.tutti.persistence.entities.data.SpeciesBatchFrequency;
34  import fr.ifremer.tutti.persistence.entities.referential.Species;
35  import fr.ifremer.tutti.service.AbstractTuttiService;
36  import fr.ifremer.tutti.service.DecoratorService;
37  import fr.ifremer.tutti.service.PersistenceService;
38  import fr.ifremer.tutti.service.TuttiServiceContext;
39  import fr.ifremer.tutti.type.WeightUnit;
40  import org.apache.commons.logging.Log;
41  import org.apache.commons.logging.LogFactory;
42  
43  import java.util.ArrayList;
44  import java.util.Collection;
45  import java.util.HashSet;
46  import java.util.List;
47  import java.util.Set;
48  
49  import static fr.ifremer.tutti.service.PersistenceService.FrequencyFunction;
50  import static org.nuiton.i18n.I18n.t;
51  
52  /**
53   * A service to check and clean species and benthos  batches  redundant weights.
54   *
55   * Created on 9/27/13.
56   *
57   * @author Tony Chemit - chemit@codelutin.com
58   * @since 2.6
59   */
60  public class WeightCleaningService extends AbstractTuttiService {
61  
62      private static final Log log = LogFactory.getLog(WeightCleaningService.class);
63  
64      protected PersistenceService persistenceService;
65  
66      protected DecoratorService decoratorService;
67  
68      protected SampleCategoryModel sampleCategoryModel;
69  
70      protected FrequencyFunction speciesFrequencyFunction;
71  
72      protected FrequencyFunction benthosFrequencyFunction;
73  
74      protected WeightUnit benthosWeightUnit;
75  
76      protected WeightUnit speciesWeightUnit;
77  
78      private String speciesBatchPrefix;
79  
80      private String benthosBatchPrefix;
81  
82      @Override
83      public void setServiceContext(TuttiServiceContext context) {
84          super.setServiceContext(context);
85          persistenceService = getService(PersistenceService.class);
86          decoratorService = getService(DecoratorService.class);
87          sampleCategoryModel = context.getSampleCategoryModel();
88          speciesFrequencyFunction = persistenceService.newSpeciesFrequenciesFunction();
89          benthosFrequencyFunction = persistenceService.newBenthosFrequenciesFunction();
90          benthosWeightUnit = context.getConfig().getBenthosWeightUnit();
91          speciesWeightUnit = context.getConfig().getSpeciesWeightUnit();
92          speciesBatchPrefix = t("tutti.service.operations.cleanWeights.species.batch");
93          benthosBatchPrefix = t("tutti.service.operations.cleanWeights.benthos.batch");
94      }
95  
96  //    /**
97  //     * Check if there is some redundant weights that could be cleaned.
98  //     *
99  //     * If no warning found, then return is a empty map, otherwise all errors
100 //     * for any bad fishing operation.
101 //     *
102 //     * Result keys are fishing operation id, values are all batches in error for
103 //     * the fishing operation.
104 //     *
105 //     * @param cruiseId id of the cruise to check.
106 //     * @return map of warnings, or empty map if no error found.
107 //     */
108 //    public Map<Integer, String> checkCruise(Integer cruiseId) {
109 //
110 //        if (log.isDebugEnabled()) {
111 //            log.debug("Will check cruise: " + cruiseId);
112 //        }
113 //
114 //        Map<Integer, String> result = new LinkedHashMap<>();
115 //
116 //        List<FishingOperation> allFishingOperation =
117 //                persistenceService.getAllFishingOperation(cruiseId);
118 //
119 //        for (FishingOperation fishingOperation : allFishingOperation) {
120 //
121 //            Integer fishingOperationId = fishingOperation.getIdAsInt();
122 //
123 //            boolean withCatchBatch =
124 //                    persistenceService.isFishingOperationWithCatchBatch(
125 //                            fishingOperationId);
126 //
127 //            if (!withCatchBatch) {
128 //                if (log.isWarnEnabled()) {
129 //                    log.warn("Skip fishing operation " + fishingOperation + " since no catchBatch associated.");
130 //                }
131 //                continue;
132 //            }
133 //
134 //            Multimap<String, String> errors = checkFishingOperation(fishingOperationId);
135 //
136 //            if (!errors.isEmpty()) {
137 //                result.put(fishingOperationId, errorsToString(errors));
138 //            }
139 //        }
140 //        return result;
141 //    }
142 
143     public String errorsToString(Multimap<String, String> errors) {
144         StringBuilder errorsStr = new StringBuilder("<ul style=\"font-size:11\">");
145         for (String error : errors.keySet()) {
146 
147             errorsStr.append("<li>");
148             Collection<String> strings = errors.get(error);
149             errorsStr.append(error);
150             if (strings.size() == 1) {
151                 errorsStr.append(" ").append(strings.iterator().next());
152             } else {
153                 errorsStr.append("<ul>");
154                 for (String s : strings) {
155                     errorsStr.append("<li>");
156                     errorsStr.append(s);
157                     errorsStr.append("</li>");
158                 }
159                 errorsStr.append("</ul>");
160             }
161             errorsStr.append("</li>");
162         }
163         errorsStr.append("</ul>");
164 
165         return errorsStr.toString();
166     }
167 
168 //    /**
169 //     * Clean all redundant weights for the given cruise.
170 //     *
171 //     * @param cruiseId id of the cruise to clean.
172 //     */
173 //    public void cleanCruise(Integer cruiseId) {
174 //
175 //        if (log.isDebugEnabled()) {
176 //            log.debug("Will clean cruise: " + cruiseId);
177 //        }
178 //
179 //        List<Integer> allFishingOperation = persistenceService.getAllFishingOperationIds(cruiseId);
180 //
181 //        allFishingOperation.forEach(this::cleanFishingOperation);
182 //    }
183 
184     /**
185      * Check for some redundant weights in species or benthos batches of
186      * the given operation.
187      *
188      * @param fishingOperationId id of the fishing operation to check
189      * @return the list of all species / benthos which contains a redundant weight
190      */
191     public Multimap<String, String> checkFishingOperation(Integer fishingOperationId) {
192 
193         if (log.isDebugEnabled()) {
194             log.debug("Will check fishingOperation: " + fishingOperationId);
195         }
196         Multimap<String, String> result = LinkedHashMultimap.create();
197 
198         boolean withCatchBatch = persistenceService.isFishingOperationWithCatchBatch(fishingOperationId);
199 
200         if (withCatchBatch) {
201 
202             // check species batches
203 
204             BatchContainer<SpeciesBatch> speciesBatch = persistenceService.getRootSpeciesBatch(fishingOperationId, true);
205 
206             for (SpeciesBatch batch : speciesBatch.getChildren()) {
207 
208                 checkBatch(speciesBatchPrefix,
209                            speciesWeightUnit,
210                            batch,
211                            result,
212                            speciesFrequencyFunction);
213             }
214 
215             // check benthos batches
216 
217             BatchContainer<SpeciesBatch> benthosBatch = persistenceService.getRootBenthosBatch(fishingOperationId, true);
218 
219             for (SpeciesBatch batch : benthosBatch.getChildren()) {
220 
221                 checkBatch(benthosBatchPrefix,
222                            benthosWeightUnit,
223                            batch,
224                            result,
225                            benthosFrequencyFunction);
226             }
227 
228         } else {
229 
230             // no catch, no check
231 
232             if (log.isWarnEnabled()) {
233                 log.warn("Skip fishing operation " + fishingOperationId + " since no catchBatch associated.");
234             }
235         }
236 
237         return result;
238     }
239 
240     /**
241      * Clean all the redundant weights in species or benthos batches of
242      * the given operation.
243      *
244      * @param fishingOperationId id of the fishing operation to check
245      * @return {@code true} if some weights were deleted, {@code false} otherwise
246      */
247     public boolean cleanFishingOperation(Integer fishingOperationId) {
248 
249         if (log.isDebugEnabled()) {
250             log.debug("Will clean fishingOperation: " + fishingOperationId);
251         }
252 
253         boolean withCatchBatch = persistenceService.isFishingOperationWithCatchBatch(fishingOperationId);
254 
255         boolean result = false;
256 
257         if (withCatchBatch) {
258 
259             // clean species batches
260 
261             Set<SpeciesBatch> speciesToSave = new HashSet<>();
262             BatchContainer<SpeciesBatch> speciesBatch = persistenceService.getRootSpeciesBatch(fishingOperationId, true);
263 
264             for (SpeciesBatch batch : speciesBatch.getChildren()) {
265 
266                 cleanBatch(speciesBatchPrefix,
267                            speciesWeightUnit,
268                            batch,
269                            speciesToSave,
270                            speciesFrequencyFunction);
271             }
272 
273             // clean benthos batches
274 
275             Set<SpeciesBatch> benthosToSave = new HashSet<>();
276             BatchContainer<SpeciesBatch> benthosBatch = persistenceService.getRootBenthosBatch(fishingOperationId, true);
277 
278             for (SpeciesBatch batch : benthosBatch.getChildren()) {
279 
280                 cleanBatch(benthosBatchPrefix,
281                            benthosWeightUnit,
282                            batch,
283                            benthosToSave,
284                            benthosFrequencyFunction);
285             }
286 
287             if (!(speciesToSave.isEmpty() && benthosToSave.isEmpty())) {
288 
289                 // save modified batches
290 
291                 result = true;
292 
293                 FishingOperation fishingOperation = persistenceService.getFishingOperation(fishingOperationId);
294 
295                 for (SpeciesBatch toSave : speciesToSave) {
296                     toSave.setFishingOperation(fishingOperation);
297                     persistenceService.saveSpeciesBatch(toSave);
298                 }
299 
300                 for (SpeciesBatch toSave : benthosToSave) {
301                     toSave.setFishingOperation(fishingOperation);
302                     persistenceService.saveBenthosBatch(toSave);
303                 }
304             }
305 
306         } else {
307 
308             // no catch, no clean
309 
310             if (log.isWarnEnabled()) {
311                 log.warn("Skip fishing operation " + fishingOperationId + " since no catchBatch associated.");
312             }
313         }
314 
315         return result;
316 
317     }
318 
319     protected void checkBatch(String batchPrefix, WeightUnit weightUnit, SpeciesBatch batch, Multimap<String, String> result, FrequencyFunction frequencyFunction) {
320 
321         if (!batch.isChildBatchsEmpty()) {
322 
323             // check childs
324             for (SpeciesBatch childBatch : batch.getChildBatchs()) {
325 
326                 // check batch sample category weight (before childs)
327                 String message = checkSampleCategoryWeightRedundant(weightUnit, batch, childBatch);
328                 addMessageIfNotExist(message, batchPrefix, weightUnit, childBatch, result);
329 
330                 // check childs
331                 checkBatch(batchPrefix, weightUnit, childBatch, result, frequencyFunction);
332             }
333         } else {
334 
335             { // check batch weight
336                 String message = checkWeightRedundant(weightUnit, batch);
337                 addMessageIfNotExist(message, batchPrefix, weightUnit, batch, result);
338             }
339 
340             { // check batch frequencies weight
341                 String message = checkFrequencyWeightRedundant(weightUnit, batch, frequencyFunction);
342                 addMessageIfNotExist(message, batchPrefix, weightUnit, batch, result);
343             }
344         }
345     }
346 
347     protected void cleanBatch(String batchPrefix,
348                               WeightUnit weightUnit,
349                               SpeciesBatch batch,
350                               Set<SpeciesBatch> result,
351                               FrequencyFunction frequencyFunction) {
352 
353         if (!batch.isChildBatchsEmpty()) {
354 
355             // check childs
356             for (SpeciesBatch childBatch : batch.getChildBatchs()) {
357 
358                 // check batch sample category weight
359                 String message = checkSampleCategoryWeightRedundant(weightUnit, batch, childBatch);
360 
361                 // clean childs before
362                 cleanBatch(batchPrefix, weightUnit, childBatch, result, frequencyFunction);
363 
364                 if (message != null) {
365 
366                     // always clean batch after cleaning childs
367                     buildLabelAndLogMessage(message, batchPrefix, weightUnit, childBatch);
368                     batch.setSampleCategoryWeight(null);
369                     result.add(batch);
370                 }
371             }
372         } else {
373 
374             boolean removeWeight = false;
375 
376             { // check batch frequencies weight
377                 String message = checkFrequencyWeightRedundant(weightUnit, batch, frequencyFunction);
378 
379                 if (message != null) {
380                     buildLabelAndLogMessage(message, batchPrefix, weightUnit, batch);
381                     removeWeight = true;
382 
383                 }
384             }
385 
386             { // check batch weight
387                 String message = checkWeightRedundant(weightUnit, batch);
388 
389                 if (message != null) {
390                     buildLabelAndLogMessage(message, batchPrefix, weightUnit, batch);
391                     removeWeight = true;
392                 }
393             }
394 
395             if (removeWeight) {
396 
397                 // must clean the batch weight
398                 batch.setWeight(null);
399                 result.add(batch);
400             }
401         }
402 
403     }
404 
405     protected String checkSampleCategoryWeightRedundant(WeightUnit weightUnit, SpeciesBatch parentBatch, SpeciesBatch batch) {
406 
407         Preconditions.checkNotNull(parentBatch);
408 
409         String result = null;
410 
411         // check the sample weight is different than his parent one
412         Float sampleCategoryWeight = batch.getSampleCategoryWeight();
413         Float parentSampleCategoryWeight = parentBatch.getSampleCategoryWeight();
414 
415         if (sampleCategoryWeight != null && parentSampleCategoryWeight != null && WeightUnit.KG.isEquals(parentSampleCategoryWeight, sampleCategoryWeight)) {
416 
417             result = t("tutti.service.operations.cleanWeights.error.redundant.sampleCategoryWeight",
418                        weightUnit.fromEntity(sampleCategoryWeight) + weightUnit.getShortLabel(),
419                        getCategoryLabel(batch),
420                        getCategoryLabel(parentBatch)
421             );
422 
423         }
424         return result;
425     }
426 
427 
428     protected String checkFrequencyWeightRedundant(WeightUnit weightUnit,
429                                                    SpeciesBatch batch,
430                                                    FrequencyFunction frequencyFunction) {
431         Preconditions.checkState(batch.isChildBatchsEmpty());
432 
433         String result = null;
434 
435         // on a leaf, check if weight is not = to sum of frequencies weight
436         Float weight = batch.getWeight();
437         if (weight != null) {
438 
439             // check if there is some frequencies
440             List<SpeciesBatchFrequency> frequencies = frequencyFunction.apply(batch);
441             Float frequenciesWeigth = persistenceService.countFrequenciesWeight(frequencies, true);
442 
443             if (frequenciesWeigth != null && WeightUnit.KG.isEquals(weight, frequenciesWeigth)) {
444                 result = t("tutti.service.operations.cleanWeights.error.redundant.frequencyWeight",
445                            weightUnit.fromEntity(weight) + weightUnit.getShortLabel(),
446                            getCategoryLabel(batch));
447             }
448         }
449 
450         return result;
451     }
452 
453     protected String checkWeightRedundant(WeightUnit weightUnit, SpeciesBatch batch) {
454 
455         Preconditions.checkState(batch.isChildBatchsEmpty());
456 
457         String result = null;
458 
459         // on a leaf, check if weight is not = to the finest category weight
460         Float weight = batch.getWeight();
461         Float sampleCategoryWeight = batch.getSampleCategoryWeight();
462         if (weight != null && sampleCategoryWeight != null && WeightUnit.KG.isEquals(weight, sampleCategoryWeight)) {
463 
464             result = t("tutti.service.operations.cleanWeights.error.redundant.weight",
465                        weightUnit.fromEntity(weight) + weightUnit.getShortLabel(),
466                        getCategoryLabel(batch));
467         }
468 
469         return result;
470     }
471 
472     protected String getBatchLabel(String type, WeightUnit weightUnit, SpeciesBatch batch) {
473         StringBuilder sb = new StringBuilder("[ " + type);
474 
475         String species = decoratorService.getDecoratorByType(Species.class).toString(batch.getSpecies());
476         sb.append(" ").append(species);
477 
478         List<String> categories = new ArrayList<>();
479         SpeciesBatch b = batch;
480         while (b.getParentBatch() != null) {
481             String categoryValue = decoratorService.getDecorator(b.getSampleCategoryValue())
482                                                    .toString(b.getSampleCategoryValue());
483             categories.add(0, categoryValue + " / " + weightUnit.fromEntity(b.getSampleCategoryWeight()));
484             b = b.getParentBatch();
485         }
486 
487         for (String category : categories) {
488             sb.append(" - ").append(category);
489         }
490         sb.append(" ]");
491         return sb.toString();
492     }
493 
494     protected String getCategoryLabel(SpeciesBatch batch) {
495         SampleCategoryModelEntry category = sampleCategoryModel.getCategoryById(batch.getSampleCategoryId());
496         return category.getLabel();
497     }
498 
499 
500     protected String buildLabelAndLogMessage(String message, String batchPrefix, WeightUnit weightUnit, SpeciesBatch b) {
501         String label = getBatchLabel(batchPrefix, weightUnit, b);
502         if (log.isInfoEnabled()) {
503             log.info(label + " " + message);
504         }
505         return label;
506     }
507 
508     protected void addMessageIfNotExist(String message, String batchPrefix, WeightUnit weightUnit, SpeciesBatch b, Multimap<String, String> result) {
509 
510         if (message != null) {
511 
512             String label = buildLabelAndLogMessage(message, batchPrefix, weightUnit, b);
513             if (!result.containsEntry(label, message)) {
514                 result.put(label, message);
515             }
516         }
517 
518     }
519 
520 }