Project

General

Profile

Download (28.1 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2013 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9
package eu.etaxonomy.cdm.api.service.description;
10

    
11
import java.util.ArrayList;
12
import java.util.Arrays;
13
import java.util.HashMap;
14
import java.util.HashSet;
15
import java.util.LinkedList;
16
import java.util.List;
17
import java.util.Map;
18
import java.util.Set;
19
import java.util.UUID;
20
import java.util.stream.Collectors;
21

    
22
import org.apache.log4j.Logger;
23
import org.hibernate.HibernateException;
24
import org.hibernate.search.Search;
25
import org.springframework.transaction.TransactionStatus;
26

    
27
import eu.etaxonomy.cdm.common.CdmUtils;
28
import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
29
import eu.etaxonomy.cdm.model.common.CdmBase;
30
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
31
import eu.etaxonomy.cdm.model.description.DescriptionElementSource;
32
import eu.etaxonomy.cdm.model.description.DescriptionType;
33
import eu.etaxonomy.cdm.model.description.Distribution;
34
import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
35
import eu.etaxonomy.cdm.model.description.TaxonDescription;
36
import eu.etaxonomy.cdm.model.location.NamedArea;
37
import eu.etaxonomy.cdm.model.taxon.Taxon;
38
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
39
import eu.etaxonomy.cdm.model.term.OrderedTermBase;
40
import eu.etaxonomy.cdm.model.term.OrderedTermVocabulary;
41
import eu.etaxonomy.cdm.model.term.TermCollection;
42
import eu.etaxonomy.cdm.model.term.TermNode;
43
import eu.etaxonomy.cdm.model.term.TermTree;
44
import eu.etaxonomy.cdm.model.term.VocabularyEnum;
45

    
46
/**
47
 *
48
 * <h2>GENERAL NOTES </h2>
49
 * <em>TODO: These notes are directly taken from original Transmission Engine Occurrence
50
 * version 14 written in Visual Basic and still need to be
51
 * adapted to the java version of the transmission engine!</em>
52
 *
53
 * <h3>summaryStatus</h3>
54
 *
55
 *   Each distribution information has a summaryStatus, this is an summary of the status codes
56
 *   as stored in the fields of emOccurrence native, introduced, cultivated, ...
57
 *   The summaryStatus seems to be equivalent to  the CDM DistributionStatus
58
 *
59
 * <h3>map generation</h3>
60
 *
61
 *   When generating maps from the accumulated distribution information some special cases have to be handled:
62
 * <ol>
63
 *   <li>if an entered or imported status information exists for the same area for which calculated (accumulated)
64
 *       data is available, the calculated data has to be given preference over other data.
65
 *   </li>
66
 *   <li>If there is an area with a sub area and both areas have the same calculated status only the subarea
67
 *       status should be shown in the map, whereas the super area should be ignored.
68
 *   </li>
69
 * </ol>
70
 *
71
 * @author Anton Güntsch (author of original Transmission Engine Occurrence version 14 written in Visual Basic)
72
 * @author Andreas Kohlbecker (2013, porting Transmission Engine Occurrence to Java)
73
 * @author a.mueller (refactoring and merge with Structured Description Aggregation)
74
 * @since Feb 22, 2013
75
 */
76
public class DistributionAggregation
77
            extends DescriptionAggregationBase<DistributionAggregation,DistributionAggregationConfiguration>{
78

    
79
    public static final Logger logger = Logger.getLogger(DistributionAggregation.class);
80

    
81
    protected static final List<String> TAXONDESCRIPTION_INIT_STRATEGY = Arrays.asList(new String [] {
82
            "description.elements.area",
83
            "description.elements.status",
84
            "description.elements.sources.citation.authorship",
85
//            "description.elements.sources.nameUsedInSource",
86
//            "description.elements.multilanguageText",
87
//            "name.status.type",
88
    });
89

    
90
    /**
91
     * A map which contains the status terms as key and the priority as value
92
     * The map will contain both, the PresenceTerms and the AbsenceTerms
93
     */
94
    private List<PresenceAbsenceTerm> statusOrder = null;
95

    
96
    private final Map<NamedArea, Set<NamedArea>> subAreaMap = new HashMap<>();
97

    
98
// ******************* CONSTRUCTOR *********************************/
99

    
100
    public DistributionAggregation() {}
101

    
102
    @Override
103
    protected String pluralDataType(){
104
        return "distributions";
105
    }
106

    
107
// ********************* METHODS *********************************/
108

    
109
    @Override
110
    protected void preAggregate(IProgressMonitor monitor) {
111
        monitor.subTask("make status order");
112

    
113
        // take start time for performance testing
114
        double start = System.currentTimeMillis();
115

    
116
        makeStatusOrder();
117

    
118
        double end1 = System.currentTimeMillis();
119
        logger.info("Time elapsed for making status order : " + (end1 - start) / (1000) + "s");
120

    
121
        makeSuperAreas();
122
        double end2 = System.currentTimeMillis();
123
        logger.info("Time elapsed for making super areas : " + (end2 - end1) / (1000) + "s");
124
    }
125

    
126
    @Override
127
    protected void verifyConfiguration(IProgressMonitor monitor){
128
        if (!AggregationSourceMode.list(AggregationMode.ToParent, AggregationType.Distribution)
129
                .contains(getConfig().getToParentSourceMode())){
130
            throw new AggregationException("Unsupported source mode for to-parent aggregation: " + getConfig().getToParentSourceMode());
131
        }
132
        if (!AggregationSourceMode.list(AggregationMode.WithinTaxon, AggregationType.Distribution)
133
                .contains(getConfig().getWithinTaxonSourceMode())){
134
            throw new AggregationException("Unsupported source mode for within-taxon aggregation: " + getConfig().getToParentSourceMode());
135
        }
136
    }
137

    
138
    @Override
139
    protected void initTransaction() {
140
    }
141

    
142
    List<NamedArea> superAreaList;
143

    
144
    private void makeSuperAreas() {
145
        TransactionStatus tx = startTransaction(true);
146
        if (getConfig().getSuperAreas()!= null){
147
            Set<UUID> superAreaUuids = new HashSet<>(getConfig().getSuperAreas());
148
            superAreaList = getTermService().find(NamedArea.class, superAreaUuids);
149
            for (NamedArea superArea : superAreaList){
150
                Set<NamedArea> subAreas = getSubAreasFor(superArea);
151
                for(NamedArea subArea : subAreas){
152
                    if (logger.isTraceEnabled()) {
153
                        logger.trace("Initialize " + subArea.getTitleCache());
154
                    }
155
                }
156
            }
157
        }
158
        commitTransaction(tx);
159
    }
160

    
161

    
162
    @Override
163
    protected List<String> descriptionInitStrategy() {
164
        return TAXONDESCRIPTION_INIT_STRATEGY;
165
    }
166

    
167
// ********************* METHODS *****************************************/
168

    
169
    private List<PresenceAbsenceTerm> getByAreaIgnoreStatusList() {
170
        return getConfig().getByAreaIgnoreStatusList();
171
    }
172

    
173
    private List<PresenceAbsenceTerm> getByRankIgnoreStatusList() {
174
        return getConfig().getByRankIgnoreStatusList();
175
    }
176

    
177
    /**
178
     * Compares the PresenceAbsenceTermBase terms contained in <code>a.status</code> and <code>b.status</code> after
179
     * the priority as stored in the statusPriorityMap. The StatusAndSources object with
180
     * the higher priority is returned. In the case of <code>a == b</code> the sources of b will be added to the sources
181
     * of a.
182
     *
183
     * If either a or b or the status are null b or a is returned.
184
     *
185
     * @see initializeStatusPriorityMap()
186
     *
187
     * @param accumulatedStatus
188
     * @param newStatus
189
     * @param additionalSourcesForWinningNewStatus Not in Use!
190
     *  In the case when <code>newStatus</code> is preferred over <code>accumulatedStatus</code> these Set of sources will be added to the sources of <code>b</code>
191
     * @param aggregationSourceMode
192
     * @return
193
     */
194
    private Distribution choosePreferredOrMerge(Distribution accumulatedStatus, Distribution newStatus,
195
            Set<DescriptionElementSource> additionalSourcesForWinningNewStatus, AggregationSourceMode aggregationSourceMode){
196

    
197
        if (newStatus == null || newStatus.getStatus() == null) {
198
            return accumulatedStatus;
199
        }
200
        if (accumulatedStatus == null || accumulatedStatus.getStatus() == null) {
201
            return newStatus;
202
        }
203

    
204
        Integer indexAcc = statusOrder.indexOf(accumulatedStatus.getStatus());
205
        Integer indexNew = statusOrder.indexOf(newStatus.getStatus());
206

    
207
        if (indexNew == -1) {
208
            logger.warn("No priority found in map for " + newStatus.getStatus().getLabel());
209
            return accumulatedStatus;
210
        }
211
        if (indexAcc == -1) {
212
            logger.warn("No priority found in map for " + accumulatedStatus.getStatus().getLabel());
213
            return newStatus;
214
        }
215
        if(indexAcc < indexNew){
216
            if(additionalSourcesForWinningNewStatus != null) {
217
                addSourcesToDescriptionElement(newStatus, additionalSourcesForWinningNewStatus);
218
            }
219
            if (aggregationSourceMode == AggregationSourceMode.ALL){
220
                addSourcesToDescriptionElement(newStatus, accumulatedStatus.getSources());
221
            }
222
            return newStatus;
223
        } else {
224
            if (indexAcc == indexNew || aggregationSourceMode == AggregationSourceMode.ALL){
225
                addSourcesToDescriptionElement(accumulatedStatus, newStatus.getSources());
226
            }
227
            return accumulatedStatus;
228
        }
229
    }
230

    
231
    private void addSourcesToDescriptionElement(Distribution aggregatingDistribution,
232
            Set<DescriptionElementSource> additionalSources) {
233
        addSourcesDeduplicated(aggregatingDistribution, additionalSources);
234
    }
235

    
236
    @Override
237
    protected boolean mergeAggregationResultIntoTargetDescription(TaxonDescription targetDescription,
238
            ResultHolder resultHolder) {
239

    
240
        boolean updated = false;
241

    
242
        Class<? extends DescriptionElementBase> debClass = Distribution.class;
243
        Map<NamedArea, Distribution> accumulatedStatusMap = ((DistributionResultHolder)resultHolder).accumulatedStatusMap;
244

    
245
//        mergeDescriptionElements(targetDescription, accumulatedStatusMap, debClass);
246

    
247
        Set<DescriptionElementBase> elementsToRemove = new HashSet<>(
248
                targetDescription.getElements().stream()
249
                    .filter(el->el.isInstanceOf(debClass))
250
                    .collect(Collectors.toSet()));
251

    
252
        for (NamedArea area : accumulatedStatusMap.keySet()) {
253
            Distribution newElement = accumulatedStatusMap.get(area);
254
            Distribution targetDistribution = findDistributionToStayForArea(targetDescription, area);
255
            //old: if we want to reuse distribution only with exact same status
256
//          Distribution distribution = findDistributionForAreaAndStatus(aggregationDescription, area, status);
257

    
258
            if(targetDistribution == null) {
259
                // create a new distribution element
260
                targetDistribution = newElement;
261
                targetDescription.addElement(targetDistribution);
262
                updated = true;
263
            }else{
264
                updated |= mergeDescriptionElement(targetDistribution, newElement);
265
                elementsToRemove.remove(targetDistribution);  //we keep the distribution for reuse
266
            }
267
            updated |= mergeSourcesForDescriptionElements(targetDistribution, accumulatedStatusMap.get(area).getSources());
268
        }
269

    
270
        updated |= this.handleDescriptionElementsToRemove(targetDescription, elementsToRemove);
271
        return updated;
272
    }
273

    
274
    @Override
275
    protected <S extends DescriptionElementBase> boolean mergeDescriptionElement(S targetElement,
276
            S newElement) {
277
        boolean updated = false;
278
        if (!(targetElement instanceof Distribution)){
279
            throw new AggregationException("Unexpected class: " + targetElement.getClass().getName());
280
        }else{
281
            Distribution targetDistribution = (Distribution)targetElement;
282
            Distribution newDistribution = (Distribution)newElement;
283
            if (!CdmUtils.nullSafeEqual(targetDistribution.getStatus(), newDistribution.getStatus())){
284
                targetDistribution.setStatus(newDistribution.getStatus());
285
                updated = true;
286
            }
287
        }
288
        return updated;
289
    }
290

    
291
    @Override
292
    protected boolean isRelevantDescriptionElement(DescriptionElementBase deb){
293
        return deb.isInstanceOf(Distribution.class);
294
    }
295

    
296

    
297
    @Override
298
    protected void aggregateWithinSingleTaxon(Taxon taxon,
299
            ResultHolder  resultHolder,
300
            Set<TaxonDescription> excludedDescriptions) {
301

    
302
        AggregationSourceMode aggregationSourceMode = getConfig().getWithinTaxonSourceMode();
303
        Map<NamedArea, Distribution> accumulatedStatusMap =
304
                ((DistributionResultHolder)resultHolder).accumulatedStatusMap;
305

    
306
        if(logger.isDebugEnabled()){
307
            logger.debug("accumulateByArea() - taxon :" + taxonToString(taxon));
308
        }
309

    
310
        Set<TaxonDescription> descriptions = descriptionsFor(taxon, excludedDescriptions);
311
        Set<Distribution> existingDistributions = distributionsFor(descriptions);
312

    
313
        // Step through superAreas for accumulation of subAreas
314
        for (NamedArea superArea : superAreaList){
315

    
316
            // accumulate all sub area status
317
            Distribution accumulatedStatusAndSources = null;
318
            // TODO consider using the TermHierarchyLookup (only in local branch a.kohlbecker)
319
            Set<NamedArea> subAreas = getSubAreasFor(superArea);
320
            for(NamedArea subArea : subAreas){
321
                if(logger.isTraceEnabled()){logger.trace("accumulateByArea() - \t\t" + termToString(subArea));}
322
                // step through all distributions for the given subArea
323
                for(Distribution distribution : existingDistributions){
324
                    //TODO AM is the status handling here correct? The mapping to CDM handled
325
                    if(subArea.equals(distribution.getArea()) && distribution.getStatus() != null) {
326
                        PresenceAbsenceTerm status = distribution.getStatus();
327
                        if(logger.isTraceEnabled()){logger.trace("accumulateByArea() - \t\t" + termToString(subArea) + ": " + termToString(status));}
328
                        // skip all having a status value in the ignore list
329
                        if (status == null || getByAreaIgnoreStatusList().contains(status)
330
                                || (getConfig().isIgnoreAbsentStatusByArea() && status.isAbsenceTerm())){
331
                            continue;
332
                        }
333
                        Distribution subAreaStatusAndSources = newStatusAndSources(superArea, status, distribution, aggregationSourceMode);
334
                        accumulatedStatusAndSources = choosePreferredOrMerge(accumulatedStatusAndSources, subAreaStatusAndSources, null, aggregationSourceMode);
335
                    }
336
                }
337
            } // next sub area
338

    
339

    
340
            if (accumulatedStatusAndSources != null) {
341
                Distribution preferedStatus = choosePreferredOrMerge(accumulatedStatusMap.get(superArea), accumulatedStatusAndSources, null, aggregationSourceMode);
342
                accumulatedStatusMap.put(superArea, preferedStatus);
343
            }
344

    
345
        } // next super area ....
346
    }
347

    
348
    private Distribution newStatusAndSources(NamedArea area, PresenceAbsenceTerm status, Distribution distribution, AggregationSourceMode aggregationSourceMode){
349
        Distribution result = Distribution.NewInstance(area, status);
350
        if (aggregationSourceMode == AggregationSourceMode.NONE){
351
            return result;
352
        }else if (aggregationSourceMode == AggregationSourceMode.DESCRIPTION){
353
            result.addSource(DescriptionElementSource.NewAggregationInstance(distribution.getInDescription()));
354
        }else if (aggregationSourceMode == AggregationSourceMode.TAXON){
355
            if (distribution.getInDescription().isInstanceOf(TaxonDescription.class)){
356
                TaxonDescription td = CdmBase.deproxy(distribution.getInDescription(), TaxonDescription.class);
357
                result.addSource(DescriptionElementSource.NewAggregationInstance(td.getTaxon()));
358
            }else{
359
                logger.warn("Description is not of type TaxonDescription. Adding source not possible");
360
            }
361
        }else if (aggregationSourceMode == AggregationSourceMode.ALL || aggregationSourceMode == AggregationSourceMode.ALL_SAMEVALUE){
362
            addSourcesDeduplicated(result, distribution.getSources());
363
        }else{
364
            throw new RuntimeException("Unhandled source aggregation mode: " + aggregationSourceMode);
365
        }
366
        return result;
367
    }
368

    
369
    private class DistributionResultHolder extends ResultHolder{
370
        Map<NamedArea, Distribution> accumulatedStatusMap = new HashMap<>();
371
    }
372

    
373
    @Override
374
    protected ResultHolder createResultHolder() {
375
        return new DistributionResultHolder();
376
    }
377

    
378
//    protected class StatusAndSources {
379
//
380
//        private final PresenceAbsenceTerm status;
381
//        private final Set<DescriptionElementSource> sources = new HashSet<>();
382
//
383
//        public StatusAndSources(PresenceAbsenceTerm status, DescriptionElementBase deb, AggregationSourceMode aggregationSourceMode) {
384
//            this.status = status;
385
//            if (aggregationSourceMode == AggregationSourceMode.NONE){
386
//                return;
387
//            }else if (aggregationSourceMode == AggregationSourceMode.DESCRIPTION){
388
//                sources.add(DescriptionElementSource.NewAggregationInstance(deb.getInDescription()));
389
//            }else if (aggregationSourceMode == AggregationSourceMode.TAXON){
390
//                if (deb.getInDescription().isInstanceOf(TaxonDescription.class)){
391
//                    TaxonDescription td = CdmBase.deproxy(deb.getInDescription(), TaxonDescription.class);
392
//                    sources.add(DescriptionElementSource.NewAggregationInstance(td.getTaxon()));
393
//                }else{
394
//                    logger.warn("Description is not of type TaxonDescription. Adding source not possible");
395
//                }
396
//            }else if (aggregationSourceMode == AggregationSourceMode.ALL || aggregationSourceMode == AggregationSourceMode.ALL_SAMEVALUE){
397
//                addSourcesDeduplicated(this.sources, deb.getSources());
398
//            }else{
399
//                throw new RuntimeException("Unhandled source aggregation mode: " + aggregationSourceMode);
400
//            }
401
//        }
402
//
403
//        public void addSourcesX(Set<DescriptionElementSource> sources) {
404
//            addSourcesDeduplicated(this.sources, sources);
405
//        }
406
//
407
//        public Set<DescriptionElementSource> getSourcesX(){
408
//            return this.sources;
409
//        }
410
//
411
//        @Override
412
//        public String toString() {
413
//            return "StatusAndSources [status=" + status + ", sources=" + sources.size() + "]";
414
//        }
415
//    }
416

    
417
    @Override
418
    protected void aggregateToParentTaxon(TaxonNode taxonNode,
419
            ResultHolder  resultHolder,
420
            Set<TaxonDescription> excludedDescriptions) {
421

    
422
        Map<NamedArea, Distribution> accumulatedStatusMap =
423
                ((DistributionResultHolder)resultHolder).accumulatedStatusMap;
424

    
425
        Taxon taxon = CdmBase.deproxy(taxonNode.getTaxon());
426
        if(logger.isDebugEnabled()){
427
            logger.debug("accumulateByRank() [" + /*rank.getLabel() +*/ "] - taxon :" + taxonToString(taxon));
428
        }
429

    
430
        if(!taxonNode.getChildNodes().isEmpty()) {
431

    
432
            LinkedList<Taxon> childStack = new LinkedList<>();
433
            for (TaxonNode node : taxonNode.getChildNodes()){
434
                if (node == null){
435
                    continue;  //just in case if sortindex is broken
436
                }
437
                Taxon child = CdmBase.deproxy(node.getTaxon());
438
                //TODO maybe we should also use child catching from taxon node filter
439
                //     we could e.g. clone the filter and set the parent as subtree filter
440
                //     and this way get all children via service layer, this may improve also
441
                //     memory usage
442
                if (getConfig().getTaxonNodeFilter().isIncludeUnpublished()||
443
                        taxon.isPublish()){
444
                    childStack.add(child);
445
                }
446
            }
447

    
448
            while(childStack.size() > 0){
449

    
450
                Taxon childTaxon = childStack.pop();
451
                getSession().setReadOnly(childTaxon, true);
452
                if(logger.isTraceEnabled()){
453
                    logger.trace("                   subtaxon :" + taxonToString(childTaxon));
454
                }
455

    
456
                Set<Distribution> distributions = distributionsFor(descriptionsFor(childTaxon, excludedDescriptions));
457
                for(Distribution distribution : distributions) {
458

    
459
                    PresenceAbsenceTerm status = distribution.getStatus();
460
                    if (status == null || getByRankIgnoreStatusList().contains(status)
461
                            || (getConfig().isIgnoreAbsentStatusByRank() && status.isAbsenceTerm())){
462
                        continue;
463
                    }
464

    
465
                    NamedArea area = distribution.getArea();
466
                    AggregationSourceMode aggregationSourceMode = getConfig().getToParentSourceMode();
467

    
468
                    Distribution childStatusAndSources = newStatusAndSources(area, status, distribution, aggregationSourceMode);
469
                    Distribution preferedStatus = choosePreferredOrMerge(accumulatedStatusMap.get(area),
470
                            childStatusAndSources, null, aggregationSourceMode );
471
                    accumulatedStatusMap.put(area, preferedStatus);
472
                }
473

    
474
                // evict all initialized entities of the childTaxon
475
                // TODO consider using cascade="evict" in the model classes
476
    //                            for( TaxonDescription description : ((Taxon)childTaxonBase).getDescriptions()) {
477
    //                                for (DescriptionElementBase deb : description.getElements()) {
478
    //                                    getSession().evict(deb);
479
    //                                }
480
    //                                getSession().evict(description); // this causes in some cases the taxon object to be detached from the session
481
    //                            }
482
    //            getSession().evict(childTaxon); // no longer needed, save heap
483
            }
484
        }
485
    }
486

    
487
    private Distribution findDistributionToStayForArea(TaxonDescription description, NamedArea area) {
488
        for(DescriptionElementBase item : description.getElements()) {
489
            if(!(item.isInstanceOf(Distribution.class))) {
490
                continue;
491
            }
492
            Distribution distribution = CdmBase.deproxy(item, Distribution.class);
493
            if(distribution.getArea().equals(area)) {
494
                return distribution;
495
            }
496
        }
497
        return null;
498
    }
499

    
500
    /**
501
     * Old: For if we want to reuse distributions only for the exact same status or
502
     * if we aggregate for each status separately. Otherwise use {@link #findDistributionForArea(TaxonDescription, NamedArea)}
503
     */
504
    private Distribution findDistributionForAreaAndStatus(TaxonDescription description, NamedArea area, PresenceAbsenceTerm status) {
505
        for(DescriptionElementBase item : description.getElements()) {
506
            if(!(item.isInstanceOf(Distribution.class))) {
507
                continue;
508
            }
509
            Distribution distribution = CdmBase.deproxy(item, Distribution.class);
510
            if(distribution.getArea().equals(area) && distribution.getStatus().equals(status)) {
511
                return distribution;
512
            }
513
        }
514
        return null;
515
    }
516

    
517
    private void flush() {
518
        logger.debug("flushing session ...");
519
        getSession().flush();
520
        try {
521
            logger.debug("flushing to indexes ...");
522
            Search.getFullTextSession(getSession()).flushToIndexes();
523
        } catch (HibernateException e) {
524
            /* IGNORE - Hibernate Search Event listeners not configured ... */
525
            if(!e.getMessage().startsWith("Hibernate Search Event listeners not configured")){
526
                throw e;
527
            }
528
        }
529
    }
530

    
531
    @Override
532
    protected TaxonDescription createNewDescription(Taxon taxon) {
533
        String title = taxon.getTitleCache();
534
        if (logger.isDebugEnabled()){logger.debug("creating new description for " + title);}
535
        TaxonDescription description = TaxonDescription.NewInstance(taxon);
536
        description.addType(DescriptionType.AGGREGATED_DISTRIBUTION);
537
        setDescriptionTitle(description, taxon);
538
        return description;
539
    }
540

    
541
    @Override
542
    protected boolean hasDescriptionType(TaxonDescription description) {
543
        return description.isAggregatedDistribution();
544
    }
545

    
546
    @Override
547
    protected void setDescriptionTitle(TaxonDescription description, Taxon taxon) {
548
        String title = taxon.getName() != null? taxon.getName().getTitleCache() : taxon.getTitleCache();
549
        description.setTitleCache("Aggregated distribution for " + title, true);
550
        return;
551
    }
552

    
553
    private Set<NamedArea> getSubAreasFor(NamedArea superArea) {
554

    
555
        if(!subAreaMap.containsKey(superArea)) {
556
            if(logger.isDebugEnabled()){
557
                logger.debug("loading included areas for " + superArea.getLabel());
558
            }
559
            subAreaMap.put(superArea, superArea.getIncludes());
560
        }
561
        return subAreaMap.get(superArea);
562
    }
563

    
564
    private Set<TaxonDescription> descriptionsFor(Taxon taxon, Set<TaxonDescription> excludedDescriptions) {
565
        Set<TaxonDescription> result = new HashSet<>();
566
        for(TaxonDescription description: taxon.getDescriptions()) {
567
//          readOnlyIfInSession(description); //not needed for tests anymore
568
            if (excludedDescriptions == null || !excludedDescriptions.contains(description)){
569
                result.add(description);
570
            }
571
        }
572
        return result;
573
    }
574

    
575
    private Set<Distribution> distributionsFor(Set<TaxonDescription> descriptions) {
576
        Set<Distribution> result = new HashSet<>();
577
        for(TaxonDescription description: descriptions) {
578
            for(DescriptionElementBase deb : description.getElements()) {
579
                if(deb.isInstanceOf(Distribution.class)) {
580
//                    readOnlyIfInSession(deb); //not needed for tests anymore
581
                    result.add(CdmBase.deproxy(deb, Distribution.class));
582
                }
583
            }
584
        }
585
        return result;
586
    }
587

    
588
    /**
589
     * This method avoids problems when running the {@link DistributionAggregationTest}.
590
     * For some unknown reason entities are not in the PersitenceContext even if they are
591
     * loaded by a service method. Setting these entities to read-only would raise a
592
     * TransientObjectException("Instance was not associated with this persistence context")
593
     *
594
     * @param entity
595
     */
596
    private void readOnlyIfInSession(CdmBase entity) {
597
        if(getSession().contains(entity)) {
598
            getSession().setReadOnly(entity, true);
599
        }
600
    }
601

    
602

    
603
    private String termToString(OrderedTermBase<?> term) {
604
        if(logger.isTraceEnabled()) {
605
            return term.getLabel() + " [" + term.getIdInVocabulary() + "]";
606
        } else {
607
            return term.getIdInVocabulary();
608
        }
609
    }
610

    
611
    /**
612
     * Sets the priorities for presence and absence terms, the priorities are stored in extensions.
613
     * This method will start a new transaction and commits it after the work is done.
614
     */
615
    private void makeStatusOrder() {
616

    
617
        TransactionStatus txStatus = startTransaction(false);
618

    
619
        @SuppressWarnings("rawtypes")
620
        TermCollection<PresenceAbsenceTerm, TermNode> stOrder = getConfig().getStatusOrder();
621
        if (stOrder == null){
622
            stOrder = defaultStatusOrder();
623
        }
624
        if (stOrder.isInstanceOf(TermTree.class)){
625
            statusOrder = CdmBase.deproxy(stOrder, TermTree.class).asTermList();
626
        }else if (stOrder.isInstanceOf(OrderedTermVocabulary.class)){
627
            statusOrder = new ArrayList<>(CdmBase.deproxy(stOrder, OrderedTermVocabulary.class).getOrderedTerms());
628
        }else{
629
            throw new RuntimeException("TermCollection type for status order not supported: " + statusOrder.getClass().getSimpleName());
630
        }
631

    
632
        commitTransaction(txStatus);
633
    }
634

    
635
    private OrderedTermVocabulary<PresenceAbsenceTerm> defaultStatusOrder() {
636
        @SuppressWarnings("unchecked")
637
        OrderedTermVocabulary<PresenceAbsenceTerm> voc = (OrderedTermVocabulary<PresenceAbsenceTerm>)getRepository().getVocabularyService().find(VocabularyEnum.PresenceAbsenceTerm.getUuid());
638
        return voc;
639
    }
640
}
(7-7/12)