View Javadoc
1   package fr.ifremer.tutti.service.sampling;
2   
3   /*
4    * #%L
5    * Tutti :: Service
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2012 - 2016 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU General Public License as
13   * published by the Free Software Foundation, either version 3 of the
14   * License, or (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 General Public
22   * License along with this program.  If not, see
23   * <http://www.gnu.org/licenses/gpl-3.0.html>.
24   * #L%
25   */
26  
27  import com.google.common.base.MoreObjects;
28  import com.google.common.collect.HashMultimap;
29  import com.google.common.collect.Multimap;
30  import fr.ifremer.tutti.persistence.entities.TuttiEntities;
31  import fr.ifremer.tutti.persistence.entities.data.FishingOperation;
32  import fr.ifremer.tutti.persistence.entities.data.IndividualObservationBatch;
33  import fr.ifremer.tutti.persistence.entities.protocol.CalcifiedPiecesSamplingDefinition;
34  import fr.ifremer.tutti.persistence.entities.protocol.TuttiProtocol;
35  import fr.ifremer.tutti.persistence.entities.protocol.Zone;
36  import fr.ifremer.tutti.persistence.entities.protocol.Zones;
37  import fr.ifremer.tutti.persistence.entities.referential.Caracteristic;
38  import fr.ifremer.tutti.persistence.entities.referential.CaracteristicQualitativeValue;
39  import fr.ifremer.tutti.persistence.entities.referential.Species;
40  import fr.ifremer.tutti.persistence.entities.referential.TuttiLocation;
41  import fr.ifremer.tutti.service.cruise.CruiseCacheAble;
42  import java.util.Collection;
43  import java.util.HashMap;
44  import java.util.List;
45  import java.util.Map;
46  import java.util.Objects;
47  import java.util.Optional;
48  import java.util.stream.IntStream;
49  import org.apache.commons.logging.Log;
50  import org.apache.commons.logging.LogFactory;
51  
52  import static fr.ifremer.tutti.service.sampling.CruiseSamplingInternalCache.addPrefixKey;
53  
54  /**
55   * @author Kevin Morin (Code Lutin)
56   * @since 4.5
57   */
58  public class CruiseSamplingCache implements CruiseCacheAble {
59  
60      /** Logger. */
61      private static final Log log = LogFactory.getLog(CruiseSamplingCache.class);
62  
63      /**
64       * Le cache des échantillons au niveau de la campagne.
65       */
66      private final CruiseSamplingInternalCache cruiseCache = new CruiseSamplingInternalCache();
67      /**
68       * Le cache des échantillons au niveau de chaque zone.
69       */
70      private final CruiseSamplingInternalCache zoneCache = new CruiseSamplingInternalCache();
71      /**
72       * Le cache des échantillons au niveau de chaque opération de pêche.
73       */
74      private final CruiseSamplingInternalCache fishingOperationCache = new CruiseSamplingInternalCache();
75      /**
76       * Le dictionnaire des identifiants de strates ou sous-strates pour chaque zone.
77       */
78      private final Multimap<Zone, Integer> locationIdsPerZone;
79      /**
80       * Le dictionnaire des définitions de l'algorithme de prélèvement des piècess calcifiées indexés par identifiant de taxon.
81       */
82      private final Multimap<Integer, CalcifiedPiecesSamplingDefinition> cpsDefinitionsBySpecies = HashMultimap.create();
83      /**
84       * La caractéristique de maturité pour chaque espèce
85       */
86      private final Map<Integer, Caracteristic> maturityCaracteristicBySpecies = new HashMap<>();
87      /**
88       * Les états matures pour chaque caractéristique de maturité
89       */
90      private final Multimap<String, String> matureStatesByMaturityCracteristic = HashMultimap.create();
91      /**
92       * La caractéristique qui définie le sexe dans une observation individuelle.
93       */
94      private final Caracteristic sexCaracteristic;
95      /**
96       * Les valeurs qualitatives de la caractéristique de Sexe indexées par leur identifiant.
97       */
98      private final Map<Integer, CaracteristicQualitativeValue> sexQualitativeValues;
99      /**
100      * Un drapeau pour indiquer qu'on est en train de construire le cache et donc ne déclancher aucun listener.
101      */
102     private boolean loading;
103 
104     public CruiseSamplingCache(TuttiProtocol protocol, Caracteristic sexCaracteristic, Collection<Caracteristic> maturityCaracteristics) {
105         this.sexCaracteristic = sexCaracteristic;
106         this.sexQualitativeValues = TuttiEntities.splitByIdAsInt(sexCaracteristic.getQualitativeValue());
107 
108         locationIdsPerZone = HashMultimap.create();
109         protocol.getZone().forEach(zone -> locationIdsPerZone.putAll(zone, Zones.getAllLocationIds(zone)));
110 
111         Map<String, Caracteristic> maturityCaracteristicsById = TuttiEntities.splitById(maturityCaracteristics);
112 
113         protocol.getSpecies().forEach(speciesProtocol -> {
114             cpsDefinitionsBySpecies.putAll(speciesProtocol.getSpeciesReferenceTaxonId(),
115                                            speciesProtocol.getCalcifiedPiecesSamplingDefinition());
116             maturityCaracteristicBySpecies.put(speciesProtocol.getSpeciesReferenceTaxonId(),
117                                                maturityCaracteristicsById.get(speciesProtocol.getMaturityPmfmId()));
118         });
119 
120         // poussin 20170104 benthos doit se comporter comme species, il faut sans doute aussi le protocole
121         // du benthos, pour pouvoir afficher les stats pour le benthos
122         protocol.getBenthos().forEach(speciesProtocol -> {
123             cpsDefinitionsBySpecies.putAll(speciesProtocol.getSpeciesReferenceTaxonId(),
124                                            speciesProtocol.getCalcifiedPiecesSamplingDefinition());
125             maturityCaracteristicBySpecies.put(speciesProtocol.getSpeciesReferenceTaxonId(),
126                                                maturityCaracteristicsById.get(speciesProtocol.getMaturityPmfmId()));
127         });
128 
129         protocol.getMaturityCaracteristics().forEach(mc -> matureStatesByMaturityCracteristic.putAll(mc.getId(), mc.getMatureStateIds()));
130     }
131 
132     public boolean isLoading() {
133         return loading;
134     }
135 
136     public void setLoading(boolean loading) {
137         this.loading = loading;
138     }
139 
140     @Override
141     public void addFishingOperation(FishingOperation fishingOperation, Collection<IndividualObservationBatch> individualObservations) {
142 
143         addIndividualObservations(fishingOperation, individualObservations);
144 
145     }
146 
147     @Override
148     public void addIndividualObservations(FishingOperation fishingOperation, Collection<IndividualObservationBatch> individualObservations) {
149 
150         Objects.requireNonNull(fishingOperation);
151         Objects.requireNonNull(individualObservations);
152 
153         Optional<Zone> optionalZone = tryFindZone(fishingOperation);
154         if (!optionalZone.isPresent()) {
155 
156             // pas de zone définie pour cette opération de pêche, on n'enregistre rien ici.
157             return;
158 
159         }
160 
161         Zone zone = optionalZone.get();
162 
163         setLoading(true);
164 
165         try {
166 
167             for (IndividualObservationBatch individualObservationBatch : individualObservations) {
168 
169                 Optional<CalcifiedPiecesSamplingDefinition> optionalCalcifiedPiecesSamplingDefinition =
170                         tryToFindCalcifiedPiecesSamplingDefinition(individualObservationBatch);
171 
172                 if (!optionalCalcifiedPiecesSamplingDefinition.isPresent()) {
173 
174                     // pas dans l'algorithme, one ne tient pas compte de cette observation
175                     continue;
176                 }
177 
178                 Species species = individualObservationBatch.getSpecies();
179                 Objects.requireNonNull(species);
180 
181                 // l'observation a forcement une taille
182                 Float lengthStep = individualObservationBatch.getSize();
183                 Objects.requireNonNull(lengthStep);
184 
185                 Boolean maturity = getMaturity(individualObservationBatch);
186 
187                 // don't forget to compute lengthStep with Caracteristic https://forge.codelutin.com/issues/8337
188                 int lengthStepInMm = individualObservationBatch.getLengthStepCaracteristic().getLengthStepInMm(lengthStep);
189 
190                 CaracteristicQualitativeValue gender = individualObservationBatch.getCaracteristics().getQualitativeValue(sexCaracteristic);
191 
192                 IndividualObservationSamplingContext individualObservationSamplingContext =
193                         createContext(fishingOperation.getIdAsInt(),
194                                       species,
195                                       zone,
196                                       optionalCalcifiedPiecesSamplingDefinition.get(),
197                                       lengthStepInMm,
198                                       maturity,
199                                       gender);
200 
201                 addIndividualObservation(individualObservationSamplingContext, individualObservationBatch.getSamplingCode() != null);
202 
203             }
204 
205         } finally {
206 
207             setLoading(false);
208 
209         }
210 
211     }
212 
213     @Override
214     public void removeIndividualObservations(FishingOperation fishingOperation, Collection<IndividualObservationBatch> individualObservations) {
215 
216         Objects.requireNonNull(fishingOperation);
217         Objects.requireNonNull(individualObservations);
218 
219         Optional<Zone> optionalZone = tryFindZone(fishingOperation);
220         if (!optionalZone.isPresent()) {
221 
222             // pas de zone définie pour cette opération de pêche, rien à supprimer alors.
223             return;
224 
225         }
226 
227         Zone zone = optionalZone.get();
228 
229         setLoading(true);
230 
231         try {
232 
233             for (IndividualObservationBatch individualObservationBatch : individualObservations) {
234 
235                 Optional<CalcifiedPiecesSamplingDefinition> optionalCalcifiedPiecesSamplingDefinition =
236                         tryToFindCalcifiedPiecesSamplingDefinition(individualObservationBatch);
237 
238                 if (!optionalCalcifiedPiecesSamplingDefinition.isPresent()) {
239 
240                     // pas dans l'algorithme, on ne tient pas compte de cette observation
241                     continue;
242                 }
243 
244                 Species species = individualObservationBatch.getSpecies();
245                 Objects.requireNonNull(species);
246 
247                 Boolean maturity = getMaturity(individualObservationBatch);
248 
249                 Objects.requireNonNull(individualObservationBatch.getSize());
250                 float lengthStep = individualObservationBatch.getSize();
251 
252                 // don't forget to compute lengthStep with Caracteristic https://forge.codelutin.com/issues/8304
253 //                int lengthStepInMm = Numbers.convertToMm(lengthStep, individualObservationBatch.getLengthStepCaracteristic().getUnit());
254                 int lengthStepInMm = individualObservationBatch.getLengthStepCaracteristic().getLengthStepInMm(lengthStep);
255 
256 
257                 CaracteristicQualitativeValue gender = individualObservationBatch.getCaracteristics().getQualitativeValue(sexCaracteristic);
258 
259                 IndividualObservationSamplingContext individualObservationSamplingContext =
260                         createContext(fishingOperation.getIdAsInt(),
261                                       species,
262                                       zone,
263                                       optionalCalcifiedPiecesSamplingDefinition.get(),
264                                       lengthStepInMm,
265                                       maturity,
266                                       gender);
267 
268                 removeIndividualObservation(individualObservationSamplingContext, individualObservationBatch.getSamplingCode() != null);
269 
270             }
271 
272         } finally {
273 
274             setLoading(false);
275 
276         }
277 
278     }
279 
280     @Override
281     public void removeFishingOperation(FishingOperation fishingOperation, Collection<IndividualObservationBatch> individualObservations) {
282 
283         Objects.requireNonNull(fishingOperation);
284         Objects.requireNonNull(individualObservations);
285 
286         Optional<Zone> optionalZone = tryFindZone(fishingOperation);
287         if (!optionalZone.isPresent()) {
288 
289             // pas de zone définie pour cette opération de pêche, rien à supprimer alors.
290             return;
291 
292         }
293 
294         Zone zone = optionalZone.get();
295 
296 
297         String fishingOperationId = fishingOperation.getId();
298 
299         if (log.isInfoEnabled()) {
300             log.info("Removing fishing operation: " + fishingOperation + " from " + this);
301         }
302 
303         // suppression de toutes les entrées du cache des opérations (et récupération des clefs)
304 
305         String keyPrefix = CruiseSamplingInternalCache.addPrefixKey(fishingOperationId, "");
306         int keyPrefixLength = keyPrefix.length();
307 
308         fishingOperationCache.getKeys()
309                              .stream()
310                              .filter(key -> key.startsWith(keyPrefix))
311                              .forEach(fishingOperationSamplingKey -> {
312 
313                                  CruiseSamplingInternalCache.SamplingData samplingData = fishingOperationCache.getSamplingData(fishingOperationSamplingKey);
314 
315                                  if (log.isInfoEnabled()) {
316                                      log.info("Found " + fishingOperationSamplingKey + " to remove from fishing operation cache (" + samplingData + ").");
317                                  }
318 
319                                  int individualObservationCount = samplingData.getIndividualObservationCount();
320                                  int samplingCount = samplingData.getSamplingCount();
321 
322                                  String cruiseSamplingKey = fishingOperationSamplingKey.substring(keyPrefixLength);
323                                  cruiseCache.remove(cruiseSamplingKey, individualObservationCount, samplingCount);
324 
325                                  String zoneSamplingKey = createZoneSamplingKey(cruiseSamplingKey, zone);
326                                  zoneCache.remove(zoneSamplingKey, individualObservationCount, samplingCount);
327 
328                                  fishingOperationCache.remove(fishingOperationSamplingKey, individualObservationCount, samplingCount);
329 
330                              });
331 
332         printInfos("After removing " + fishingOperation);
333 
334         cleanEmptyEntries();
335 
336         printInfos("After cleanEmptyEntries");
337 
338         if (log.isInfoEnabled()) {
339             log.info(fishingOperation + " removed from fishingOperationCache: " + fishingOperationCache.size());
340             log.info(fishingOperation + " removed from cruiseCache: " + cruiseCache.size());
341             log.info(fishingOperation + " removed from zoneCache: " + zoneCache.size());
342             log.info(fishingOperation + " removed from " + this);
343         }
344 
345     }
346 
347     @Override
348     public void close() {
349 
350         if (log.isInfoEnabled()) {
351             log.info("Closing cruise sampling cache.");
352         }
353         cruiseCache.close();
354         zoneCache.close();
355         fishingOperationCache.close();
356         locationIdsPerZone.clear();
357         cpsDefinitionsBySpecies.clear();
358         maturityCaracteristicBySpecies.clear();
359         matureStatesByMaturityCracteristic.clear();
360     }
361 
362     @Override
363     public String toString() {
364         return MoreObjects.toStringHelper(this)
365                           .add("cruiseCache", cruiseCache.size())
366                           .add("zoneCache", zoneCache.size())
367                           .add("fishingOperationCache", fishingOperationCache.size())
368                           .toString();
369     }
370 
371     public IndividualObservationSamplingStatus getIndividualObservationSamplingStatus(IndividualObservationSamplingCacheRequest request) throws SizeNotDefinedOnIndividualObservationException, ZoneNotDefinedOnFishingOperationException, CalcifiedPiecesSamplingAlgorithmEntryNotFoundException {
372 
373         Objects.requireNonNull(request);
374 
375         Species species = request.getSpecies();
376         Objects.requireNonNull(species);
377 
378         Integer lengthStep = request.getLengthClass();
379         if (lengthStep == null) {
380 
381             throw new SizeNotDefinedOnIndividualObservationException(request);
382         }
383 
384         FishingOperation fishingOperation = request.getFishingOperation();
385         Objects.requireNonNull(fishingOperation);
386 
387         Optional<Zone> optionalZone = tryFindZone(fishingOperation);
388         if (!optionalZone.isPresent()) {
389 
390             throw new ZoneNotDefinedOnFishingOperationException(request);
391         }
392 
393         CaracteristicQualitativeValue gender = request.getGender();
394         Boolean maturity = getMaturity(request);
395 
396         Optional<CalcifiedPiecesSamplingDefinition> optionalCalcifiedPiecesSamplingDefinition =
397                 tryToFindCalcifiedPiecesSamplingDefinition(species, lengthStep, maturity, gender);
398 
399         if (!optionalCalcifiedPiecesSamplingDefinition.isPresent()) {
400 
401             throw new CalcifiedPiecesSamplingAlgorithmEntryNotFoundException(request);
402         }
403 
404         IndividualObservationSamplingContext context = createContext(fishingOperation.getIdAsInt(),
405                                                                      species,
406                                                                      optionalZone.get(),
407                                                                      optionalCalcifiedPiecesSamplingDefinition.get(),
408                                                                      lengthStep,
409                                                                      maturity,
410                                                                      gender);
411 
412         String cruiseSamplingKey = context.getCruiseSamplingKey();
413         CruiseSamplingInternalCache.SamplingData cruiseSamplingData = cruiseCache.getOrCreateSamplingData(cruiseSamplingKey);
414 
415         String zoneSamplingKey = context.getZoneSamplingKey();
416         CruiseSamplingInternalCache.SamplingData zoneSamplingData = zoneCache.getOrCreateSamplingData(zoneSamplingKey);
417 
418         String fishingOperationSamplingKey = context.getFishingOperationSamplingKey();
419         CruiseSamplingInternalCache.SamplingData fishingOperationSamplingData = fishingOperationCache.getOrCreateSamplingData(fishingOperationSamplingKey);
420 
421         // on demande à calculer needSampling uniquement s'il n'y a pas de code de prélèvement sur l'observation
422         boolean computeSampling = !request.withSamplingCode();
423 
424         return new IndividualObservationSamplingStatus(context, computeSampling, cruiseSamplingData, zoneSamplingData, fishingOperationSamplingData);
425 
426     }
427 
428     public void addIndividualObservation(IndividualObservationSamplingCacheRequest request) {
429 
430         IndividualObservationSamplingContext individualObservationSamplingContext = getIndividualObservationSamplingContext(request);
431 
432         if (individualObservationSamplingContext != null) {
433 
434             addIndividualObservation(individualObservationSamplingContext, request.withSamplingCode());
435 
436         }
437 
438     }
439 
440     public void removeIndividualObservation(IndividualObservationSamplingCacheRequest request) {
441 
442         IndividualObservationSamplingContext individualObservationSamplingContext = getIndividualObservationSamplingContext(request);
443 
444         if (individualObservationSamplingContext != null) {
445 
446             removeIndividualObservation(individualObservationSamplingContext, request.withSamplingCode());
447 
448         }
449 
450     }
451 
452     public void addSampling(IndividualObservationSamplingCacheRequest request) {
453 
454         IndividualObservationSamplingContext individualObservationSamplingContext = getIndividualObservationSamplingContext(request);
455 
456         if (individualObservationSamplingContext != null) {
457 
458             addSampling(individualObservationSamplingContext);
459 
460         }
461 
462     }
463 
464     public void removeSampling(IndividualObservationSamplingCacheRequest request) {
465 
466         IndividualObservationSamplingContext individualObservationSamplingContext = getIndividualObservationSamplingContext(request);
467 
468         if (individualObservationSamplingContext != null) {
469 
470             removeSampling(individualObservationSamplingContext);
471 
472         }
473 
474     }
475 
476     public boolean isZoneChanged(FishingOperation operation1, FishingOperation operation2) {
477 
478         Optional<Zone> optionalZone1 = tryFindZone(operation1);
479         Optional<Zone> optionalZone2 = tryFindZone(operation2);
480         return !Objects.equals(optionalZone1, optionalZone2);
481 
482     }
483 
484     public List<CacheExtractedKey> getSamplingNumbers(Map<String, Species> speciesById) {
485         List<CacheExtractedKey> result = cruiseCache.getSamplingNumbers(speciesById, sexQualitativeValues);
486         result.forEach(key -> {
487             Optional<CalcifiedPiecesSamplingDefinition> cpsDef = tryToFindCalcifiedPiecesSamplingDefinition(key.getSpecies(), key.getLengthStep(), key.getMaturity(), key.getSex());
488             if (cpsDef.isPresent()) {
489                 key.setMaxByLengthStep(cpsDef.get().getMaxByLenghtStep());
490             }
491         });
492         return result;
493     }
494 
495     public Optional<Zone> tryFindZone(FishingOperation operation) {
496         Optional<Zone> result;
497         if (operation.getSubStrata() != null) {
498             result = tryFindZone(operation.getSubStrata());
499         } else if (operation.getStrata() != null) {
500             result = tryFindZone(operation.getStrata());
501         } else {
502             result = Optional.empty();
503         }
504         return result;
505     }
506 
507     public boolean isSpeciesDefined(Species species) {
508         return cpsDefinitionsBySpecies.containsKey(species.getReferenceTaxonId());
509     }
510 
511     private Optional<Zone> tryFindZone(TuttiLocation location) {
512 
513         // FIXME poussin 20160608 si on est obligé de parcourir pour chacun la
514         // collection, c'est que ce n'est pas le bon format de stockage.
515         // Modifier pour que la recherche soit faite sans parcours
516         // il faudrait plutot Map[locationId -> Zone]
517         // ce code est appeler de très nombreuse fois
518         Integer locationId = location.getIdAsInt();
519         return locationIdsPerZone.keySet()
520                                  .stream()
521                                  .filter(zone -> locationIdsPerZone.containsEntry(zone, locationId))
522                                  .findFirst();
523 
524     }
525 
526     private Boolean getMaturity(IndividualObservationSamplingCacheRequest samplingCacheRequest) {
527         Boolean maturity = samplingCacheRequest.getForcedMaturity();
528         if (maturity == null && samplingCacheRequest.withMaturity()) {
529             Caracteristic maturityCaracteristic = maturityCaracteristicBySpecies.get(samplingCacheRequest.getSpecies().getReferenceTaxonId());
530             if (maturityCaracteristic != null) {
531                 CaracteristicQualitativeValue maturityQualitativeValue = samplingCacheRequest.getMaturity();
532                 maturity = matureStatesByMaturityCracteristic.containsEntry(maturityCaracteristic.getId(), maturityQualitativeValue.getId());
533             }
534         }
535 
536         return maturity;
537     }
538 
539     /**
540      * Retourne si la classe utilisé est une classe mature (true) ou non (false)
541      * 
542      * @param individualObservationBatch
543      * @return
544      */
545     private Boolean getMaturity(IndividualObservationBatch individualObservationBatch) {
546         Boolean maturity = null;
547         Caracteristic maturityCaracteristic = maturityCaracteristicBySpecies.get(individualObservationBatch.getSpecies().getReferenceTaxonId());
548         // if a maturity caracteristic is defined in the protocol for this species
549         if (maturityCaracteristic != null) {
550             CaracteristicQualitativeValue qualitativeValue = individualObservationBatch.getCaracteristics().getQualitativeValue(maturityCaracteristic);
551             // it the maturity is set
552             if (qualitativeValue != null) {
553                 maturity = matureStatesByMaturityCracteristic.containsEntry(maturityCaracteristic.getId(), qualitativeValue.getId());
554             }
555         }
556         return maturity;
557     }
558 
559     private String createCruiseSamplingKey(Species species,
560                                            CaracteristicQualitativeValue gender,
561                                            Boolean maturity,
562                                            int lengthStep) {
563         return CruiseSamplingInternalCache.createSamplingKey(species, gender, maturity, lengthStep);
564     }
565 
566     private String createZoneSamplingKey(String cruiseSamplingKey, Zone zone) {
567         return addPrefixKey((zone == null ? "" : zone.getId()), cruiseSamplingKey);
568     }
569 
570     private String createFishingOperationSamplingKey(String cruiseSamplingKey, int fishingOperationId) {
571         return addPrefixKey(fishingOperationId, cruiseSamplingKey);
572     }
573 
574     private void addIndividualObservation(IndividualObservationSamplingContext individualObservationSamplingContext, boolean addSampling) {
575 
576         Objects.requireNonNull(individualObservationSamplingContext);
577 
578         String cruiseSamplingKey = individualObservationSamplingContext.getCruiseSamplingKey();
579         CruiseSamplingInternalCache.SamplingData cruiseSamplingData = cruiseCache.addOneIndividualObservation(cruiseSamplingKey);
580 
581         String zoneSamplingKey = individualObservationSamplingContext.getZoneSamplingKey();
582         CruiseSamplingInternalCache.SamplingData zoneSamplingData = zoneCache.addOneIndividualObservation(zoneSamplingKey);
583 
584         String fishingOperationSamplingKey = individualObservationSamplingContext.getFishingOperationSamplingKey();
585         CruiseSamplingInternalCache.SamplingData fishingOperationSamplingData = fishingOperationCache.addOneIndividualObservation(fishingOperationSamplingKey);
586 
587         if (log.isInfoEnabled()) {
588             log.info(getLogMessage("add individual observation ",
589                                    cruiseSamplingKey,
590                                    zoneSamplingKey,
591                                    fishingOperationSamplingKey,
592                                    cruiseSamplingData,
593                                    zoneSamplingData,
594                                    fishingOperationSamplingData));
595         }
596 
597         if (addSampling) {
598 
599             addSampling(individualObservationSamplingContext);
600 
601         }
602 
603     }
604 
605     private void removeIndividualObservation(IndividualObservationSamplingContext individualObservationSamplingContext, boolean removeSampling) {
606 
607         Objects.requireNonNull(individualObservationSamplingContext);
608 
609         String cruiseSamplingKey = individualObservationSamplingContext.getCruiseSamplingKey();
610         CruiseSamplingInternalCache.SamplingData cruiseSamplingData = cruiseCache.removeOneIndividualObservation(cruiseSamplingKey);
611 
612         String zoneSamplingKey = individualObservationSamplingContext.getZoneSamplingKey();
613         CruiseSamplingInternalCache.SamplingData zoneSamplingData = zoneCache.removeOneIndividualObservation(zoneSamplingKey);
614 
615         String fishingOperationSamplingKey = individualObservationSamplingContext.getFishingOperationSamplingKey();
616         CruiseSamplingInternalCache.SamplingData fishingOperationSamplingData = fishingOperationCache.removeOneIndividualObservation(fishingOperationSamplingKey);
617 
618         if (log.isInfoEnabled()) {
619             log.info(getLogMessage("remove individual observation ",
620                                    cruiseSamplingKey,
621                                    zoneSamplingKey,
622                                    fishingOperationSamplingKey,
623                                    cruiseSamplingData,
624                                    zoneSamplingData,
625                                    fishingOperationSamplingData));
626         }
627 
628         if (removeSampling) {
629 
630             removeSampling(individualObservationSamplingContext);
631 
632         }
633 
634     }
635 
636     private void addSampling(IndividualObservationSamplingContext individualObservationSamplingContext) {
637 
638         Objects.requireNonNull(individualObservationSamplingContext);
639 
640         String cruiseSamplingKey = individualObservationSamplingContext.getCruiseSamplingKey();
641         CruiseSamplingInternalCache.SamplingData cruiseSamplingData = cruiseCache.addOneSampling(cruiseSamplingKey);
642 
643         String zoneSamplingKey = individualObservationSamplingContext.getZoneSamplingKey();
644         CruiseSamplingInternalCache.SamplingData zoneSamplingData = zoneCache.addOneSampling(zoneSamplingKey);
645 
646         String fishingOperationSamplingKey = individualObservationSamplingContext.getFishingOperationSamplingKey();
647         CruiseSamplingInternalCache.SamplingData fishingOperationSamplingData = fishingOperationCache.addOneSampling(fishingOperationSamplingKey);
648 
649         if (log.isInfoEnabled()) {
650             log.info(getLogMessage("add sampling ",
651                                    cruiseSamplingKey,
652                                    zoneSamplingKey,
653                                    fishingOperationSamplingKey,
654                                    cruiseSamplingData,
655                                    zoneSamplingData,
656                                    fishingOperationSamplingData));
657         }
658 
659     }
660 
661     private void removeSampling(IndividualObservationSamplingContext individualObservationSamplingContext) {
662 
663         Objects.requireNonNull(individualObservationSamplingContext);
664 
665         String cruiseSamplingKey = individualObservationSamplingContext.getCruiseSamplingKey();
666         CruiseSamplingInternalCache.SamplingData cruiseSamplingData = cruiseCache.removeOneSampling(cruiseSamplingKey);
667 
668         String zoneSamplingKey = individualObservationSamplingContext.getZoneSamplingKey();
669         CruiseSamplingInternalCache.SamplingData zoneSamplingData = zoneCache.removeOneSampling(zoneSamplingKey);
670 
671         String fishingOperationSamplingKey = individualObservationSamplingContext.getFishingOperationSamplingKey();
672         CruiseSamplingInternalCache.SamplingData fishingOperationSamplingData = fishingOperationCache.removeOneSampling(fishingOperationSamplingKey);
673 
674         if (log.isInfoEnabled()) {
675             log.info(getLogMessage("remove sampling ",
676                                    cruiseSamplingKey,
677                                    zoneSamplingKey,
678                                    fishingOperationSamplingKey,
679                                    cruiseSamplingData,
680                                    zoneSamplingData,
681                                    fishingOperationSamplingData));
682         }
683 
684     }
685 
686     private IndividualObservationSamplingContext getIndividualObservationSamplingContext(IndividualObservationSamplingCacheRequest request) {
687 
688         Objects.requireNonNull(request);
689 
690         FishingOperation fishingOperation = request.getFishingOperation();
691         Objects.requireNonNull(fishingOperation);
692 
693         Optional<Zone> optionalZone = tryFindZone(fishingOperation);
694         if (!optionalZone.isPresent()) {
695 
696             if (log.isInfoEnabled()) {
697                 log.info("Do not record sampling in cache, fishing operation has no matching zone.");
698             }
699             return null;
700         }
701 
702         Integer lengthStep = request.getLengthClass();
703         Objects.requireNonNull(lengthStep);
704 
705         Species species = request.getSpecies();
706         Objects.requireNonNull(species);
707 
708         Boolean maturity = getMaturity(request);
709         CaracteristicQualitativeValue gender = request.getGender();
710 
711         Optional<CalcifiedPiecesSamplingDefinition> optionalCalcifiedPiecesSamplingDefinition =
712                 tryToFindCalcifiedPiecesSamplingDefinition(species, lengthStep, maturity, gender);
713 
714         if (!optionalCalcifiedPiecesSamplingDefinition.isPresent()) {
715 
716             if (log.isInfoEnabled()) {
717                 log.info("Do not record sampling in cache, no definition matched.");
718             }
719             return null;
720         }
721 
722         return createContext(fishingOperation.getIdAsInt(),
723                              species,
724                              optionalZone.get(),
725                              optionalCalcifiedPiecesSamplingDefinition.get(),
726                              lengthStep,
727                              maturity,
728                              gender);
729 
730     }
731 
732     private IndividualObservationSamplingContext createContext(int fishingOperationId,
733                                                                Species species,
734                                                                Zone zone,
735                                                                CalcifiedPiecesSamplingDefinition calcifiedPiecesSamplingDefinition,
736                                                                Integer lengthStep,
737                                                                Boolean maturity,
738                                                                CaracteristicQualitativeValue gender) {
739 
740         String cruiseSamplingKey = createCruiseSamplingKey(species, gender, maturity, lengthStep);
741         String zoneSamplingKey = createZoneSamplingKey(cruiseSamplingKey, zone);
742         String fishingOperationSamplingKey = createFishingOperationSamplingKey(cruiseSamplingKey, fishingOperationId);
743 
744         return new IndividualObservationSamplingContext(species,
745                                                         lengthStep,
746                                                         maturity,
747                                                         gender,
748                                                         calcifiedPiecesSamplingDefinition,
749                                                         zone,
750                                                         cruiseSamplingKey,
751                                                         zoneSamplingKey,
752                                                         fishingOperationSamplingKey);
753 
754     }
755 
756     private Optional<CalcifiedPiecesSamplingDefinition> tryToFindCalcifiedPiecesSamplingDefinition(IndividualObservationBatch individualObservationBatch) {
757 
758         Species species = individualObservationBatch.getSpecies();
759         Objects.requireNonNull(species);
760 
761         Float lengthStep = individualObservationBatch.getSize();
762 
763         Optional<CalcifiedPiecesSamplingDefinition> result;
764         if (lengthStep == null) {
765 
766             // on ne cherche pas sur une observation sans taille
767             result = Optional.empty();
768 
769         } else {
770 
771             Boolean maturity = getMaturity(individualObservationBatch);
772 
773             // lengthStepInMm must be compute and not lengthS converted in mm (like bug #8304 and #8337)
774             int lengthStepInMm = individualObservationBatch.getLengthStepCaracteristic().getLengthStepInMm(lengthStep);
775             // OLD: int lengthStepInMm = Numbers.convertToMm(lengthStep, individualObservationBatch.getLengthStepCaracteristic().getUnit());
776 
777             CaracteristicQualitativeValue gender = individualObservationBatch.getCaracteristics().getQualitativeValue(sexCaracteristic);
778 
779             result = tryToFindCalcifiedPiecesSamplingDefinition(species, lengthStepInMm, maturity, gender);
780 
781         }
782 
783         return result;
784 
785     }
786 
787     private Optional<CalcifiedPiecesSamplingDefinition> tryToFindCalcifiedPiecesSamplingDefinition(Species species,
788                                                                                                    int lengthStep,
789                                                                                                    Boolean maturity,
790                                                                                                    CaracteristicQualitativeValue gender) {
791 
792         Collection<CalcifiedPiecesSamplingDefinition> cpsDefinitions = cpsDefinitionsBySpecies.get(species.getReferenceTaxonId());
793 
794         CalcifiedPiecesSamplingDefinition result = null;
795 
796         if (cpsDefinitions == null) {
797 
798             if (log.isInfoEnabled()) {
799                 log.info(species + " not found in any calcified pieces sampling definitions");
800             }
801 
802         } else {
803 
804             // poussin 20171204 si maturity dans la definition est null
805             // cela veut dire qu'il faut faire les sommes pour les statistiques
806             // sur toutes les maturites. Elle ne peut etre null que si dans le
807             // protocole on a pas demander a classifier par maturite, et donc
808             // dans les definitions toutes les maturites doivent etre null
809             // il ne peut pas existe des maturite null mixee avec true ou false
810             // conclusion si dans la definition maturite est null, elle convient
811             // pour n'importe quelle maturite
812             Optional<CalcifiedPiecesSamplingDefinition> optionalDefinition =
813                     cpsDefinitions.stream()
814                                   .filter(cpsDef -> (cpsDef.getMaturity() == null || Objects.equals(cpsDef.getMaturity(), maturity))
815                                           && lengthStep >= cpsDef.getMinSize()
816                                           && (cpsDef.getMaxSize() == null || lengthStep <= cpsDef.getMaxSize()))
817                                   .findFirst();
818 
819             if (!optionalDefinition.isPresent()) {
820 
821                 if (log.isInfoEnabled()) {
822                     log.info(species + " - maturity " + maturity + " length step " + lengthStep + " not found in calcified pieces sampling definitions");
823                 }
824 
825             } else {
826 
827                 result = optionalDefinition.get();
828 
829                 if (result.getSamplingInterval() == 0) {
830 
831                     if (log.isInfoEnabled()) {
832                         log.info("Can't use definition with no sampling interval: " + result);
833                     }
834                     result = null;
835 
836                 } else if ((result.isSex() && gender == null)) {
837 
838                     if (log.isInfoEnabled()) {
839                         log.info("Can't use definition (sex is required, but none was given): " + result);
840                     }
841                     result = null;
842 
843                 } else {
844 
845                     if (log.isInfoEnabled()) {
846                         log.info("Found matching definition: " + result);
847                     }
848 
849                 }
850 
851             }
852 
853         }
854 
855         return Optional.ofNullable(result);
856 
857     }
858 
859     private String getLogMessage(String prefix,
860                                  String cruiseSamplingKey,
861                                  String zoneSamplingKey,
862                                  String fishingOperationSamplingKey,
863                                  CruiseSamplingInternalCache.SamplingData cruiseSamplingData,
864                                  CruiseSamplingInternalCache.SamplingData zoneSamplingData,
865                                  CruiseSamplingInternalCache.SamplingData fishingOperationSamplingData) {
866 
867         int maxSize = IntStream
868                 .builder()
869                 .add(cruiseSamplingKey.length())
870                 .add(zoneSamplingKey.length())
871                 .add(fishingOperationSamplingKey.length())
872                 .build()
873                 .max()
874                 .orElseGet(() -> 0);
875 
876         return prefix
877                 + "\n[cruise            " + (String.format("%1$" + maxSize + "s", cruiseSamplingKey).replaceAll(" ", ".")) + "] → " + cruiseSamplingData
878                 + "\n[zone              " + (String.format("%1$" + maxSize + "s", zoneSamplingKey).replaceAll(" ", ".")) + "] → " + zoneSamplingData
879                 + "\n[fishing operation" + (String.format("%1$" + maxSize + "s", fishingOperationSamplingKey).replaceAll(" ", ".")) + "] → " + fishingOperationSamplingData;
880 
881     }
882 
883     private void cleanEmptyEntries() {
884         cruiseCache.cleanEmptyEntries();
885         zoneCache.cleanEmptyEntries();
886         fishingOperationCache.cleanEmptyEntries();
887     }
888 
889     public void printInfos(String message) {
890 
891         if (log.isInfoEnabled()) {
892 
893             String cruiseCacheInfos = cruiseCache.toStringVerbose();
894             String zoneCacheInfos = zoneCache.toStringVerbose();
895             String fishingOperationCacheInfos = fishingOperationCache.toStringVerbose();
896 
897             StringBuilder stringBuilder = new StringBuilder(message);
898             stringBuilder.append("\nCruise            cache: ").append(cruiseCacheInfos);
899             stringBuilder.append("\nZone              cache: ").append(zoneCacheInfos);
900             stringBuilder.append("\nFishing operation cache: ").append(fishingOperationCacheInfos);
901 
902             if (log.isInfoEnabled()) {
903                 log.info(stringBuilder.toString());
904             }
905         }
906     }
907 
908 }