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
|
}
|