Project

General

Profile

Download (27.4 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

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

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

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

    
77
    public static final Logger logger = Logger.getLogger(DistributionAggregation.class);
78

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

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

    
94
    private final Map<NamedArea, Set<NamedArea>> subAreaMap = new HashMap<>();
95

    
96
// ******************* CONSTRUCTOR *********************************/
97

    
98
    public DistributionAggregation() {}
99

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

    
105
// ********************* METHODS *********************************/
106

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

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

    
114
        makeStatusOrder();
115

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

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

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

    
136
    @Override
137
    protected void initTransaction() {
138
    }
139

    
140
    List<NamedArea> superAreaList;
141

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

    
159

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

    
165
// ********************* METHODS *****************************************/
166

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

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

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

    
195
        if (newStatus == null || newStatus.status == null) {
196
            return accumulatedStatus;
197
        }
198
        if (accumulatedStatus == null || accumulatedStatus.status == null) {
199
            return newStatus;
200
        }
201

    
202
        Integer indexAcc = statusOrder.indexOf(accumulatedStatus.status);
203
        Integer indexNew = statusOrder.indexOf(newStatus.status);
204

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

    
229
    @Override
230
    protected void addAggregationResultToDescription(TaxonDescription targetDescription,
231
            ResultHolder resultHolder) {
232

    
233
        Map<NamedArea, StatusAndSources> accumulatedStatusMap = ((DistributionResultHolder)resultHolder).accumulatedStatusMap;
234

    
235
        Set<Distribution> toDelete = new HashSet<>();
236
        if (getConfig().isDoClearExistingDescription()){
237
            clearDescription(targetDescription);
238
        }else{
239
            toDelete = new HashSet<>();
240
        }
241
        for (NamedArea area : accumulatedStatusMap.keySet()) {
242
            PresenceAbsenceTerm status = accumulatedStatusMap.get(area).status;
243
            Distribution distribution = findDistributionForArea(targetDescription, area);
244
            //old: if we want to reuse distribution only with exact same status
245
//          Distribution distribution = findDistributionForAreaAndStatus(aggregationDescription, area, status);
246

    
247
            if(distribution == null) {
248
                // create a new distribution element
249
                distribution = Distribution.NewInstance(area, status);
250
                targetDescription.addElement(distribution);
251
            }else{
252
                distribution.setStatus(status);
253
                toDelete.remove(distribution);  //we keep the distribution for reuse
254
            }
255
            replaceSources(distribution, accumulatedStatusMap.get(area).sources);
256
//            addSourcesDeduplicated(distribution.getSources(), accumulatedStatusMap.get(area).sources);
257
        }
258
        for(Distribution toDeleteDist: toDelete){
259
            targetDescription.removeElement(toDeleteDist);
260
        }
261
    }
262

    
263
    /**
264
     * Removes all description elements of type {@link Distribution} from the
265
     * (aggregation) description.
266
     */
267
    private void clearDescription(TaxonDescription aggregationDescription) {
268
        int deleteCount = 0;
269
        Set<DescriptionElementBase> deleteCandidates = new HashSet<>();
270
        for (DescriptionElementBase descriptionElement : aggregationDescription.getElements()) {
271
            if(descriptionElement.isInstanceOf(Distribution.class)) {
272
                deleteCandidates.add(descriptionElement);
273
            }
274
        }
275
        aggregationDescription.addType(DescriptionType.AGGREGATED_DISTRIBUTION);
276
        if(deleteCandidates.size() > 0){
277
            for(DescriptionElementBase descriptionElement : deleteCandidates) {
278
                aggregationDescription.removeElement(descriptionElement);
279
                getDescriptionService().deleteDescriptionElement(descriptionElement);
280
                descriptionElement = null;
281
                deleteCount++;
282
            }
283
            getDescriptionService().saveOrUpdate(aggregationDescription);
284
            logger.debug("\t" + deleteCount +" distributions cleared");
285
        }
286
    }
287

    
288
    @Override
289
    protected void aggregateWithinSingleTaxon(Taxon taxon,
290
            ResultHolder  resultHolder,
291
            Set<TaxonDescription> excludedDescriptions) {
292

    
293
        Map<NamedArea, StatusAndSources> accumulatedStatusMap =
294
                ((DistributionResultHolder)resultHolder).accumulatedStatusMap;
295

    
296
        if(logger.isDebugEnabled()){
297
            logger.debug("accumulateByArea() - taxon :" + taxonToString(taxon));
298
        }
299

    
300
        Set<TaxonDescription> descriptions = descriptionsFor(taxon, excludedDescriptions);
301
        Set<Distribution> distributions = distributionsFor(descriptions);
302

    
303
        // Step through superAreas for accumulation of subAreas
304
        for (NamedArea superArea : superAreaList){
305

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

    
330

    
331
            if (accumulatedStatusAndSources != null) {
332
                StatusAndSources preferedStatus = choosePreferredOrMerge(accumulatedStatusMap.get(superArea), accumulatedStatusAndSources, null, aggregationSourceMode);
333
                accumulatedStatusMap.put(superArea, preferedStatus);
334
            }
335

    
336
        } // next super area ....
337
    }
338

    
339
    private class DistributionResultHolder extends ResultHolder{
340
        Map<NamedArea, StatusAndSources> accumulatedStatusMap = new HashMap<>();
341
    }
342

    
343
    @Override
344
    protected ResultHolder createResultHolder() {
345
        return new DistributionResultHolder();
346
    }
347

    
348
    protected class StatusAndSources {
349

    
350
        private final PresenceAbsenceTerm status;
351
        private final Set<DescriptionElementSource> sources = new HashSet<>();
352

    
353
        public StatusAndSources(PresenceAbsenceTerm status, DescriptionElementBase deb, AggregationSourceMode aggregationSourceMode) {
354
            this.status = status;
355
            if (aggregationSourceMode == AggregationSourceMode.NONE){
356
                return;
357
            }else if (aggregationSourceMode == AggregationSourceMode.DESCRIPTION){
358
                sources.add(DescriptionElementSource.NewAggregationInstance(deb.getInDescription()));
359
            }else if (aggregationSourceMode == AggregationSourceMode.TAXON){
360
                if (deb.getInDescription().isInstanceOf(TaxonDescription.class)){
361
                    TaxonDescription td = CdmBase.deproxy(deb.getInDescription(), TaxonDescription.class);
362
                    sources.add(DescriptionElementSource.NewAggregationInstance(td.getTaxon()));
363
                }else{
364
                    logger.warn("Description is not of type TaxonDescription. Adding source not possible");
365
                }
366
            }else if (aggregationSourceMode == AggregationSourceMode.ALL || aggregationSourceMode == AggregationSourceMode.ALL_SAMEVALUE){
367
                addSourcesDeduplicated(this.sources, deb.getSources());
368
            }else{
369
                throw new RuntimeException("Unhandled source aggregation mode: " + aggregationSourceMode);
370
            }
371
        }
372

    
373
        public void addSources(Set<DescriptionElementSource> sources) {
374
            addSourcesDeduplicated(this.sources, sources);
375
        }
376

    
377
        @Override
378
        public String toString() {
379
            return "StatusAndSources [status=" + status + ", sources=" + sources.size() + "]";
380
        }
381
    }
382

    
383
    @Override
384
    protected void aggregateToParentTaxon(TaxonNode taxonNode,
385
            ResultHolder  resultHolder,
386
            Set<TaxonDescription> excludedDescriptions) {
387

    
388
        Map<NamedArea, StatusAndSources> accumulatedStatusMap =
389
                ((DistributionResultHolder)resultHolder).accumulatedStatusMap;
390

    
391
        Taxon taxon = CdmBase.deproxy(taxonNode.getTaxon());
392
        if(logger.isDebugEnabled()){
393
            logger.debug("accumulateByRank() [" + /*rank.getLabel() +*/ "] - taxon :" + taxonToString(taxon));
394
        }
395

    
396
        if(!taxonNode.getChildNodes().isEmpty()) {
397

    
398
            LinkedList<Taxon> childStack = new LinkedList<>();
399
            for (TaxonNode node : taxonNode.getChildNodes()){
400
                if (node == null){
401
                    continue;  //just in case if sortindex is broken
402
                }
403
                Taxon child = CdmBase.deproxy(node.getTaxon());
404
                //TODO maybe we should also use child catching from taxon node filter
405
                //     we could e.g. clone the filter and set the parent as subtree filter
406
                //     and this way get all children via service layer, this may improve also
407
                //     memory usage
408
                if (getConfig().getTaxonNodeFilter().isIncludeUnpublished()||
409
                        taxon.isPublish()){
410
                    childStack.add(child);
411
                }
412
            }
413

    
414
            while(childStack.size() > 0){
415

    
416
                Taxon childTaxon = childStack.pop();
417
                getSession().setReadOnly(childTaxon, true);
418
                if(logger.isTraceEnabled()){
419
                    logger.trace("                   subtaxon :" + taxonToString(childTaxon));
420
                }
421

    
422
                Set<Distribution> distributions = distributionsFor(descriptionsFor(childTaxon, excludedDescriptions));
423
                for(Distribution distribution : distributions) {
424

    
425
                    PresenceAbsenceTerm status = distribution.getStatus();
426
                    if (status == null || getByRankIgnoreStatusList().contains(status)
427
                            || (getConfig().isIgnoreAbsentStatusByRank() && status.isAbsenceTerm())){
428
                        continue;
429
                    }
430

    
431
                    NamedArea area = distribution.getArea();
432
                    AggregationSourceMode aggregationSourceMode = getConfig().getToParentSourceMode();
433

    
434
                    StatusAndSources childStatusAndSources = new StatusAndSources(status, distribution, aggregationSourceMode);
435
                    StatusAndSources preferedStatus = choosePreferredOrMerge(accumulatedStatusMap.get(area),
436
                            childStatusAndSources, null, aggregationSourceMode );
437
                    accumulatedStatusMap.put(area, preferedStatus);
438
                }
439

    
440
                // evict all initialized entities of the childTaxon
441
                // TODO consider using cascade="evict" in the model classes
442
    //                            for( TaxonDescription description : ((Taxon)childTaxonBase).getDescriptions()) {
443
    //                                for (DescriptionElementBase deb : description.getElements()) {
444
    //                                    getSession().evict(deb);
445
    //                                }
446
    //                                getSession().evict(description); // this causes in some cases the taxon object to be detached from the session
447
    //                            }
448
    //            getSession().evict(childTaxon); // no longer needed, save heap
449
            }
450
        }
451
    }
452

    
453
    private Distribution findDistributionForArea(TaxonDescription description, NamedArea area) {
454
        for(DescriptionElementBase item : description.getElements()) {
455
            if(!(item.isInstanceOf(Distribution.class))) {
456
                continue;
457
            }
458
            Distribution distribution = CdmBase.deproxy(item, Distribution.class);
459
            if(distribution.getArea().equals(area)) {
460
                return distribution;
461
            }
462
        }
463
        return null;
464
    }
465

    
466
    /**
467
     * Old: For if we want to reuse distributions only for the exact same status or
468
     * if we aggregate for each status separately. Otherwise use {@link #findDistributionForArea(TaxonDescription, NamedArea)}
469
     */
470
    private Distribution findDistributionForAreaAndStatus(TaxonDescription description, NamedArea area, PresenceAbsenceTerm status) {
471
        for(DescriptionElementBase item : description.getElements()) {
472
            if(!(item.isInstanceOf(Distribution.class))) {
473
                continue;
474
            }
475
            Distribution distribution = CdmBase.deproxy(item, Distribution.class);
476
            if(distribution.getArea().equals(area) && distribution.getStatus().equals(status)) {
477
                return distribution;
478
            }
479
        }
480
        return null;
481
    }
482

    
483
    private void flush() {
484
        logger.debug("flushing session ...");
485
        getSession().flush();
486
        try {
487
            logger.debug("flushing to indexes ...");
488
            Search.getFullTextSession(getSession()).flushToIndexes();
489
        } catch (HibernateException e) {
490
            /* IGNORE - Hibernate Search Event listeners not configured ... */
491
            if(!e.getMessage().startsWith("Hibernate Search Event listeners not configured")){
492
                throw e;
493
            }
494
        }
495
    }
496

    
497
    private void flushAndClear() {
498
       flush();
499
       logger.debug("clearing session ...");
500
       getSession().clear();
501
    }
502

    
503
    @Override
504
    protected TaxonDescription createNewDescription(Taxon taxon) {
505
        String title = taxon.getTitleCache();
506
        logger.debug("creating new description for " + title);
507
        TaxonDescription description = TaxonDescription.NewInstance(taxon);
508
        description.addType(DescriptionType.AGGREGATED_DISTRIBUTION);
509
        setDescriptionTitle(description, taxon);
510
        return description;
511
    }
512

    
513
    @Override
514
    protected boolean hasDescriptionType(TaxonDescription description) {
515
        return description.isAggregatedDistribution();
516
    }
517

    
518
    @Override
519
    protected void setDescriptionTitle(TaxonDescription description, Taxon taxon) {
520
        String title = taxon.getName() != null? taxon.getName().getTitleCache() : taxon.getTitleCache();
521
        description.setTitleCache("Aggregated distribution for " + title, true);
522
        return;
523
    }
524

    
525
    private Set<NamedArea> getSubAreasFor(NamedArea superArea) {
526

    
527
        if(!subAreaMap.containsKey(superArea)) {
528
            if(logger.isDebugEnabled()){
529
                logger.debug("loading included areas for " + superArea.getLabel());
530
            }
531
            subAreaMap.put(superArea, superArea.getIncludes());
532
        }
533
        return subAreaMap.get(superArea);
534
    }
535

    
536
    private Set<TaxonDescription> descriptionsFor(Taxon taxon, Set<TaxonDescription> excludedDescriptions) {
537
        Set<TaxonDescription> result = new HashSet<>();
538
        for(TaxonDescription description: taxon.getDescriptions()) {
539
//          readOnlyIfInSession(description); //not needed for tests anymore
540
            if (excludedDescriptions == null || !excludedDescriptions.contains(description)){
541
                result.add(description);
542
            }
543
        }
544
        return result;
545
    }
546

    
547
    private Set<Distribution> distributionsFor(Set<TaxonDescription> descriptions) {
548
        Set<Distribution> result = new HashSet<>();
549
        for(TaxonDescription description: descriptions) {
550
            for(DescriptionElementBase deb : description.getElements()) {
551
                if(deb.isInstanceOf(Distribution.class)) {
552
//                    readOnlyIfInSession(deb); //not needed for tests anymore
553
                    result.add(CdmBase.deproxy(deb, Distribution.class));
554
                }
555
            }
556
        }
557
        return result;
558
    }
559

    
560
    /**
561
     * This method avoids problems when running the {@link DistributionAggregationTest}.
562
     * For some unknown reason entities are not in the PersitenceContext even if they are
563
     * loaded by a service method. Setting these entities to read-only would raise a
564
     * TransientObjectException("Instance was not associated with this persistence context")
565
     *
566
     * @param entity
567
     */
568
    private void readOnlyIfInSession(CdmBase entity) {
569
        if(getSession().contains(entity)) {
570
            getSession().setReadOnly(entity, true);
571
        }
572
    }
573

    
574

    
575
    private String termToString(OrderedTermBase<?> term) {
576
        if(logger.isTraceEnabled()) {
577
            return term.getLabel() + " [" + term.getIdInVocabulary() + "]";
578
        } else {
579
            return term.getIdInVocabulary();
580
        }
581
    }
582

    
583
    /**
584
     * Sets the priorities for presence and absence terms, the priorities are stored in extensions.
585
     * This method will start a new transaction and commits it after the work is done.
586
     */
587
    private void makeStatusOrder() {
588

    
589
        TransactionStatus txStatus = startTransaction(false);
590

    
591
        @SuppressWarnings("rawtypes")
592
        TermCollection<PresenceAbsenceTerm, TermNode> stOrder = getConfig().getStatusOrder();
593
        if (stOrder == null){
594
            stOrder = defaultStatusOrder();
595
        }
596
        if (stOrder.isInstanceOf(TermTree.class)){
597
            statusOrder = CdmBase.deproxy(stOrder, TermTree.class).asTermList();
598
        }else if (stOrder.isInstanceOf(OrderedTermVocabulary.class)){
599
            statusOrder = new ArrayList<>(CdmBase.deproxy(stOrder, OrderedTermVocabulary.class).getOrderedTerms());
600
        }else{
601
            throw new RuntimeException("TermCollection type for status order not supported: " + statusOrder.getClass().getSimpleName());
602
        }
603

    
604
        commitTransaction(txStatus);
605
    }
606

    
607
    private OrderedTermVocabulary<PresenceAbsenceTerm> defaultStatusOrder() {
608
        @SuppressWarnings("unchecked")
609
        OrderedTermVocabulary<PresenceAbsenceTerm> voc = (OrderedTermVocabulary<PresenceAbsenceTerm>)getRepository().getVocabularyService().find(VocabularyEnum.PresenceAbsenceTerm.getUuid());
610
        return voc;
611
    }
612

    
613
    private void replaceSources(Distribution distribution, Set<DescriptionElementSource> newSources) {
614
        Set<DescriptionElementSource> toDeleteSources = new HashSet<>(distribution.getSources());
615
        for(DescriptionElementSource newSource : newSources) {
616
            boolean contained = false;
617
            for(DescriptionElementSource existingSource: distribution.getSources()) {
618
                if(existingSource.equalsByShallowCompare(newSource)) {
619
                    contained = true;
620
                    toDeleteSources.remove(existingSource);
621
                    break;
622
                }
623
            }
624
            if(!contained) {
625
                try {
626
                    distribution.addSource(newSource.clone());
627
                } catch (CloneNotSupportedException e) {
628
                    // should never happen
629
                    throw new RuntimeException(e);
630
                }
631
            }
632
        }
633
        for (DescriptionElementSource toDeleteSource : toDeleteSources){
634
            distribution.removeSource(toDeleteSource);
635
        }
636
    }
637

    
638

    
639
}
(7-7/12)