Project

General

Profile

Download (26.6 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
    @Override
100
    protected String pluralDataType(){
101
        return "distributions";
102
    }
103

    
104
// ********************* METHODS *********************************/
105

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

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

    
113
        makeStatusOrder();
114

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

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

    
123
    @Override
124
    protected void initTransaction() {
125
    }
126

    
127
    List<NamedArea> superAreaList;
128

    
129
    private void makeSuperAreas() {
130
        TransactionStatus tx = startTransaction(true);
131
        if (getConfig().getSuperAreas()!= null){
132
            Set<UUID> superAreaUuids = new HashSet<>(getConfig().getSuperAreas());
133
            superAreaList = getTermService().find(NamedArea.class, superAreaUuids);
134
            for (NamedArea superArea : superAreaList){
135
                Set<NamedArea> subAreas = getSubAreasFor(superArea);
136
                for(NamedArea subArea : subAreas){
137
                    if (logger.isTraceEnabled()) {
138
                        logger.trace("Initialize " + subArea.getTitleCache());
139
                    }
140
                }
141
            }
142
        }
143
        commitTransaction(tx);
144
    }
145

    
146

    
147
    @Override
148
    protected List<String> descriptionInitStrategy() {
149
        return TAXONDESCRIPTION_INIT_STRATEGY;
150
    }
151

    
152
// ********************* METHODS *****************************************/
153

    
154
    private List<PresenceAbsenceTerm> getByAreaIgnoreStatusList() {
155
        return getConfig().getByAreaIgnoreStatusList();
156
    }
157

    
158
    private List<PresenceAbsenceTerm> getByRankIgnoreStatusList() {
159
        return getConfig().getByRankIgnoreStatusList();
160
    }
161

    
162
    /**
163
     * Compares the PresenceAbsenceTermBase terms contained in <code>a.status</code> and <code>b.status</code> after
164
     * the priority as stored in the statusPriorityMap. The StatusAndSources object with
165
     * the higher priority is returned. In the case of <code>a == b</code> the sources of b will be added to the sources
166
     * of a.
167
     *
168
     * If either a or b or the status are null b or a is returned.
169
     *
170
     * @see initializeStatusPriorityMap()
171
     *
172
     * @param accumulatedStatus
173
     * @param newStatus
174
     * @param additionalSourcesForWinningNewStatus Not in Use!
175
     *  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>
176
     * @param aggregationSourceMode
177
     * @return
178
     */
179
    private StatusAndSources choosePreferredOrMerge(StatusAndSources accumulatedStatus, StatusAndSources newStatus,
180
            Set<DescriptionElementSource> additionalSourcesForWinningNewStatus, AggregationSourceMode aggregationSourceMode){
181

    
182
        if (newStatus == null || newStatus.status == null) {
183
            return accumulatedStatus;
184
        }
185
        if (accumulatedStatus == null || accumulatedStatus.status == null) {
186
            return newStatus;
187
        }
188

    
189
        Integer indexAcc = statusOrder.indexOf(accumulatedStatus.status);
190
        Integer indexNew = statusOrder.indexOf(newStatus.status);
191

    
192
        if (indexNew == -1) {
193
            logger.warn("No priority found in map for " + newStatus.status.getLabel());
194
            return accumulatedStatus;
195
        }
196
        if (indexAcc == -1) {
197
            logger.warn("No priority found in map for " + accumulatedStatus.status.getLabel());
198
            return newStatus;
199
        }
200
        if(indexAcc < indexNew){
201
            if(additionalSourcesForWinningNewStatus != null) {
202
                newStatus.addSources(additionalSourcesForWinningNewStatus);
203
            }
204
            if (aggregationSourceMode == AggregationSourceMode.ALL){
205
                newStatus.addSources(accumulatedStatus.sources);
206
            }
207
            return newStatus;
208
        } else {
209
            if (indexAcc == indexNew || aggregationSourceMode == AggregationSourceMode.ALL){
210
                accumulatedStatus.addSources(newStatus.sources);
211
            }
212
            return accumulatedStatus;
213
        }
214
    }
215

    
216
    @Override
217
    protected void addAggregationResultToDescription(TaxonDescription targetDescription,
218
            ResultHolder resultHolder) {
219

    
220
        Map<NamedArea, StatusAndSources> accumulatedStatusMap = ((DistributionResultHolder)resultHolder).accumulatedStatusMap;
221

    
222
        Set<Distribution> toDelete = new HashSet<>();
223
        if (getConfig().isDoClearExistingDescription()){
224
            clearDescription(targetDescription);
225
        }else{
226
            toDelete = new HashSet<>();
227
        }
228
        for (NamedArea area : accumulatedStatusMap.keySet()) {
229
            PresenceAbsenceTerm status = accumulatedStatusMap.get(area).status;
230
            Distribution distribution = findDistributionForArea(targetDescription, area);
231
            //old: if we want to reuse distribution only with exact same status
232
//          Distribution distribution = findDistributionForAreaAndStatus(aggregationDescription, area, status);
233

    
234
            if(distribution == null) {
235
                // create a new distribution element
236
                distribution = Distribution.NewInstance(area, status);
237
                targetDescription.addElement(distribution);
238
            }else{
239
                distribution.setStatus(status);
240
                toDelete.remove(distribution);  //we keep the distribution for reuse
241
            }
242
            replaceSources(distribution.getSources(), accumulatedStatusMap.get(area).sources);
243
//            addSourcesDeduplicated(distribution.getSources(), accumulatedStatusMap.get(area).sources);
244
        }
245
        for(Distribution toDeleteDist: toDelete){
246
            targetDescription.removeElement(toDeleteDist);
247
        }
248
    }
249

    
250
    /**
251
     * Removes all description elements of type {@link Distribution} from the
252
     * (aggregation) description.
253
     */
254
    private void clearDescription(TaxonDescription aggregationDescription) {
255
        int deleteCount = 0;
256
        Set<DescriptionElementBase> deleteCandidates = new HashSet<>();
257
        for (DescriptionElementBase descriptionElement : aggregationDescription.getElements()) {
258
            if(descriptionElement.isInstanceOf(Distribution.class)) {
259
                deleteCandidates.add(descriptionElement);
260
            }
261
        }
262
        aggregationDescription.addType(DescriptionType.AGGREGATED_DISTRIBUTION);
263
        if(deleteCandidates.size() > 0){
264
            for(DescriptionElementBase descriptionElement : deleteCandidates) {
265
                aggregationDescription.removeElement(descriptionElement);
266
                getDescriptionService().deleteDescriptionElement(descriptionElement);
267
                descriptionElement = null;
268
                deleteCount++;
269
            }
270
            getDescriptionService().saveOrUpdate(aggregationDescription);
271
            logger.debug("\t" + deleteCount +" distributions cleared");
272
        }
273
    }
274

    
275
    @Override
276
    protected void aggregateWithinSingleTaxon(Taxon taxon,
277
            ResultHolder  resultHolder,
278
            Set<TaxonDescription> excludedDescriptions) {
279

    
280
        Map<NamedArea, StatusAndSources> accumulatedStatusMap =
281
                ((DistributionResultHolder)resultHolder).accumulatedStatusMap;
282

    
283
        if(logger.isDebugEnabled()){
284
            logger.debug("accumulateByArea() - taxon :" + taxonToString(taxon));
285
        }
286

    
287
        Set<TaxonDescription> descriptions = descriptionsFor(taxon, excludedDescriptions);
288
        Set<Distribution> distributions = distributionsFor(descriptions);
289

    
290
        // Step through superAreas for accumulation of subAreas
291
        for (NamedArea superArea : superAreaList){
292

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

    
321

    
322
            if (accumulatedStatusAndSources != null) {
323
                StatusAndSources preferedStatus = choosePreferredOrMerge(accumulatedStatusMap.get(superArea), accumulatedStatusAndSources, null, aggregationSourceMode);
324
                accumulatedStatusMap.put(superArea, preferedStatus);
325
            }
326

    
327
        } // next super area ....
328
    }
329

    
330
    private class DistributionResultHolder implements ResultHolder{
331
        Map<NamedArea, StatusAndSources> accumulatedStatusMap = new HashMap<>();
332
    }
333

    
334
    @Override
335
    protected ResultHolder createResultHolder() {
336
        return new DistributionResultHolder();
337
    }
338

    
339
    protected class StatusAndSources {
340

    
341
        private final PresenceAbsenceTerm status;
342
        private final Set<DescriptionElementSource> sources = new HashSet<>();
343

    
344
        public StatusAndSources(PresenceAbsenceTerm status, DescriptionElementBase deb, AggregationSourceMode aggregationSourceMode) {
345
            this.status = status;
346
            if (aggregationSourceMode == AggregationSourceMode.NONE){
347
                return;
348
            }else if (aggregationSourceMode == AggregationSourceMode.DESCRIPTION){
349
                sources.add(DescriptionElementSource.NewAggregationInstance(deb.getInDescription()));
350
            }else if (aggregationSourceMode == AggregationSourceMode.TAXON){
351
                if (deb.getInDescription().isInstanceOf(TaxonDescription.class)){
352
                    TaxonDescription td = CdmBase.deproxy(deb.getInDescription(), TaxonDescription.class);
353
                    sources.add(DescriptionElementSource.NewAggregationInstance(td.getTaxon()));
354
                }else{
355
                    logger.warn("Description is not of type TaxonDescription. Adding source not possible");
356
                }
357
            }else if (aggregationSourceMode == AggregationSourceMode.ALL || aggregationSourceMode == AggregationSourceMode.ALL_SAMEVALUE){
358
                addSourcesDeduplicated(this.sources, deb.getSources());
359
            }else{
360
                throw new RuntimeException("Unhandled source aggregation mode: " + aggregationSourceMode);
361
            }
362
        }
363

    
364
        public void addSources(Set<DescriptionElementSource> sources) {
365
            addSourcesDeduplicated(this.sources, sources);
366
        }
367
    }
368

    
369
    @Override
370
    protected void aggregateToParentTaxon(TaxonNode taxonNode,
371
            ResultHolder  resultHolder,
372
            Set<TaxonDescription> excludedDescriptions) {
373

    
374
        Map<NamedArea, StatusAndSources> accumulatedStatusMap =
375
                ((DistributionResultHolder)resultHolder).accumulatedStatusMap;
376

    
377
        Taxon taxon = CdmBase.deproxy(taxonNode.getTaxon());
378
        if(logger.isDebugEnabled()){
379
            logger.debug("accumulateByRank() [" + /*rank.getLabel() +*/ "] - taxon :" + taxonToString(taxon));
380
        }
381

    
382
        if(!taxonNode.getChildNodes().isEmpty()) {
383

    
384
            LinkedList<Taxon> childStack = new LinkedList<>();
385
            for (TaxonNode node : taxonNode.getChildNodes()){
386
                if (node == null){
387
                    continue;  //just in case if sortindex is broken
388
                }
389
                Taxon child = CdmBase.deproxy(node.getTaxon());
390
                //TODO maybe we should also use child catching from taxon node filter
391
                //     we could e.g. clone the filter and set the parent as subtree filter
392
                //     and this way get all children via service layer, this may improve also
393
                //     memory usage
394
                if (getConfig().getTaxonNodeFilter().isIncludeUnpublished()||
395
                        taxon.isPublish()){
396
                    childStack.add(child);
397
                }
398
            }
399

    
400
            while(childStack.size() > 0){
401

    
402
                Taxon childTaxon = childStack.pop();
403
                getSession().setReadOnly(childTaxon, true);
404
                if(logger.isTraceEnabled()){
405
                    logger.trace("                   subtaxon :" + taxonToString(childTaxon));
406
                }
407

    
408
                Set<Distribution> distributions = distributionsFor(descriptionsFor(childTaxon, excludedDescriptions));
409
                for(Distribution distribution : distributions) {
410

    
411
                    PresenceAbsenceTerm status = distribution.getStatus();
412
                    if (status == null || getByRankIgnoreStatusList().contains(status)
413
                            || (getConfig().isIgnoreAbsentStatusByRank() && status.isAbsenceTerm())){
414
                        continue;
415
                    }
416

    
417
                    NamedArea area = distribution.getArea();
418
                    AggregationSourceMode aggregationSourceMode = getConfig().getToParentSourceMode();
419

    
420
                    StatusAndSources childStatusAndSources = new StatusAndSources(status, distribution, aggregationSourceMode);
421
                    StatusAndSources preferedStatus = choosePreferredOrMerge(accumulatedStatusMap.get(area),
422
                            childStatusAndSources, null, aggregationSourceMode );
423
                    accumulatedStatusMap.put(area, preferedStatus);
424
                }
425

    
426
                // evict all initialized entities of the childTaxon
427
                // TODO consider using cascade="evict" in the model classes
428
    //                            for( TaxonDescription description : ((Taxon)childTaxonBase).getDescriptions()) {
429
    //                                for (DescriptionElementBase deb : description.getElements()) {
430
    //                                    getSession().evict(deb);
431
    //                                }
432
    //                                getSession().evict(description); // this causes in some cases the taxon object to be detached from the session
433
    //                            }
434
    //            getSession().evict(childTaxon); // no longer needed, save heap
435
            }
436
        }
437
    }
438

    
439
    private Distribution findDistributionForArea(TaxonDescription description, NamedArea area) {
440
        for(DescriptionElementBase item : description.getElements()) {
441
            if(!(item.isInstanceOf(Distribution.class))) {
442
                continue;
443
            }
444
            Distribution distribution = CdmBase.deproxy(item, Distribution.class);
445
            if(distribution.getArea().equals(area)) {
446
                return distribution;
447
            }
448
        }
449
        return null;
450
    }
451

    
452
    /**
453
     * Old: For if we want to reuse distributions only for the exact same status or
454
     * if we aggregate for each status separately. Otherwise use {@link #findDistributionForArea(TaxonDescription, NamedArea)}
455
     */
456
    private Distribution findDistributionForAreaAndStatus(TaxonDescription description, NamedArea area, PresenceAbsenceTerm status) {
457
        for(DescriptionElementBase item : description.getElements()) {
458
            if(!(item.isInstanceOf(Distribution.class))) {
459
                continue;
460
            }
461
            Distribution distribution = CdmBase.deproxy(item, Distribution.class);
462
            if(distribution.getArea().equals(area) && distribution.getStatus().equals(status)) {
463
                return distribution;
464
            }
465
        }
466
        return null;
467
    }
468

    
469
    private void flush() {
470
        logger.debug("flushing session ...");
471
        getSession().flush();
472
        try {
473
            logger.debug("flushing to indexes ...");
474
            Search.getFullTextSession(getSession()).flushToIndexes();
475
        } catch (HibernateException e) {
476
            /* IGNORE - Hibernate Search Event listeners not configured ... */
477
            if(!e.getMessage().startsWith("Hibernate Search Event listeners not configured")){
478
                throw e;
479
            }
480
        }
481
    }
482

    
483
    private void flushAndClear() {
484
       flush();
485
       logger.debug("clearing session ...");
486
       getSession().clear();
487
    }
488

    
489
    @Override
490
    protected TaxonDescription createNewDescription(Taxon taxon) {
491
        String title = taxon.getTitleCache();
492
        logger.debug("creating new description for " + title);
493
        TaxonDescription description = TaxonDescription.NewInstance(taxon);
494
        description.addType(DescriptionType.AGGREGATED_DISTRIBUTION);
495
        setDescriptionTitle(description, taxon);
496
        return description;
497
    }
498

    
499
    @Override
500
    protected boolean hasDescriptionType(TaxonDescription description) {
501
        return description.isAggregatedDistribution();
502
    }
503

    
504
    @Override
505
    protected void setDescriptionTitle(TaxonDescription description, Taxon taxon) {
506
        String title = taxon.getName() != null? taxon.getName().getTitleCache() : taxon.getTitleCache();
507
        description.setTitleCache("Aggregated distribution for " + title, true);
508
        return;
509
    }
510

    
511
    private Set<NamedArea> getSubAreasFor(NamedArea superArea) {
512

    
513
        if(!subAreaMap.containsKey(superArea)) {
514
            if(logger.isDebugEnabled()){
515
                logger.debug("loading included areas for " + superArea.getLabel());
516
            }
517
            subAreaMap.put(superArea, superArea.getIncludes());
518
        }
519
        return subAreaMap.get(superArea);
520
    }
521

    
522
    private Set<TaxonDescription> descriptionsFor(Taxon taxon, Set<TaxonDescription> excludedDescriptions) {
523
        Set<TaxonDescription> result = new HashSet<>();
524
        for(TaxonDescription description: taxon.getDescriptions()) {
525
//            readOnlyIfInSession(description); //not needed for tests anymore
526
            if (!excludedDescriptions.contains(description)){
527
                result.add(description);
528
            }
529
        }
530
        return result;
531
    }
532

    
533
    private Set<Distribution> distributionsFor(Set<TaxonDescription> descriptions) {
534
        Set<Distribution> result = new HashSet<>();
535
        for(TaxonDescription description: descriptions) {
536
            for(DescriptionElementBase deb : description.getElements()) {
537
                if(deb.isInstanceOf(Distribution.class)) {
538
//                    readOnlyIfInSession(deb); //not needed for tests anymore
539
                    result.add(CdmBase.deproxy(deb, Distribution.class));
540
                }
541
            }
542
        }
543
        return result;
544
    }
545

    
546
    /**
547
     * This method avoids problems when running the {@link DistributionAggregationTest}.
548
     * For some unknown reason entities are not in the PersitenceContext even if they are
549
     * loaded by a service method. Setting these entities to read-only would raise a
550
     * TransientObjectException("Instance was not associated with this persistence context")
551
     *
552
     * @param entity
553
     */
554
    private void readOnlyIfInSession(CdmBase entity) {
555
        if(getSession().contains(entity)) {
556
            getSession().setReadOnly(entity, true);
557
        }
558
    }
559

    
560

    
561
    private String termToString(OrderedTermBase<?> term) {
562
        if(logger.isTraceEnabled()) {
563
            return term.getLabel() + " [" + term.getIdInVocabulary() + "]";
564
        } else {
565
            return term.getIdInVocabulary();
566
        }
567
    }
568

    
569
    /**
570
     * Sets the priorities for presence and absence terms, the priorities are stored in extensions.
571
     * This method will start a new transaction and commits it after the work is done.
572
     */
573
    private void makeStatusOrder() {
574

    
575
        TransactionStatus txStatus = startTransaction(false);
576

    
577
        @SuppressWarnings("rawtypes")
578
        TermCollection<PresenceAbsenceTerm, TermNode> stOrder = getConfig().getStatusOrder();
579
        if (stOrder == null){
580
            stOrder = defaultStatusOrder();
581
        }
582
        if (stOrder.isInstanceOf(TermTree.class)){
583
            statusOrder = CdmBase.deproxy(stOrder, TermTree.class).asTermList();
584
        }else if (stOrder.isInstanceOf(OrderedTermVocabulary.class)){
585
            statusOrder = new ArrayList<>(CdmBase.deproxy(stOrder, OrderedTermVocabulary.class).getOrderedTerms());
586
        }else{
587
            throw new RuntimeException("TermCollection type for status order not supported: " + statusOrder.getClass().getSimpleName());
588
        }
589

    
590
        commitTransaction(txStatus);
591
    }
592

    
593
    private OrderedTermVocabulary<PresenceAbsenceTerm> defaultStatusOrder() {
594
        @SuppressWarnings("unchecked")
595
        OrderedTermVocabulary<PresenceAbsenceTerm> voc = (OrderedTermVocabulary<PresenceAbsenceTerm>)getRepository().getVocabularyService().find(VocabularyEnum.PresenceAbsenceTerm.getUuid());
596
        return voc;
597
    }
598

    
599
    private void replaceSources(Set<DescriptionElementSource> oldSources, Set<DescriptionElementSource> newSources) {
600
        Set<DescriptionElementSource> toDeleteSources = new HashSet<>(oldSources);
601
        for(DescriptionElementSource newSource : newSources) {
602
            boolean contained = false;
603
            for(DescriptionElementSource existingSource: oldSources) {
604
                if(existingSource.equalsByShallowCompare(newSource)) {
605
                    contained = true;
606
                    toDeleteSources.remove(existingSource);
607
                    break;
608
                }
609
            }
610
            if(!contained) {
611
                try {
612
                    oldSources.add(newSource.clone());
613
                } catch (CloneNotSupportedException e) {
614
                    // should never happen
615
                    throw new RuntimeException(e);
616
                }
617
            }
618
        }
619
        for (DescriptionElementSource toDeleteSource : toDeleteSources){
620
            oldSources.remove(toDeleteSource);
621
        }
622
    }
623

    
624

    
625
}
(6-6/11)