2 * Copyright (C) 2023 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
9 package eu
.etaxonomy
.cdm
.api
.service
.portal
;
11 import java
.awt
.Color
;
12 import java
.time
.LocalDateTime
;
13 import java
.util
.ArrayList
;
14 import java
.util
.Arrays
;
15 import java
.util
.Collections
;
16 import java
.util
.HashMap
;
17 import java
.util
.HashSet
;
18 import java
.util
.List
;
21 import java
.util
.UUID
;
22 import java
.util
.stream
.Collectors
;
24 import org
.apache
.commons
.lang3
.StringUtils
;
25 import org
.apache
.logging
.log4j
.LogManager
;
26 import org
.apache
.logging
.log4j
.Logger
;
27 import org
.joda
.time
.DateTime
;
29 import com
.fasterxml
.jackson
.core
.JsonProcessingException
;
31 import eu
.etaxonomy
.cdm
.api
.application
.ICdmRepository
;
32 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.AnnotatableDto
;
33 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.AnnotationDto
;
34 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.CdmBaseDto
;
35 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.CommonNameDto
;
36 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.ContainerDto
;
37 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.DistributionInfoDto
;
38 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.FactDto
;
39 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.FeatureDto
;
40 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.IndividualsAssociationDto
;
41 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.MarkerDto
;
42 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.MessagesDto
;
43 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.MessagesDto
.MessageType
;
44 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.SingleSourcedDto
;
45 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.SourceDto
;
46 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.SourcedDto
;
47 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonBaseDto
;
48 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonInteractionDto
;
49 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonPageDto
;
50 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonPageDto
.ConceptRelationDTO
;
51 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonPageDto
.HomotypicGroupDTO
;
52 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonPageDto
.KeyDTO
;
53 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonPageDto
.MediaDTO
;
54 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonPageDto
.MediaRepresentationDTO
;
55 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonPageDto
.SpecimenDTO
;
56 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonPageDto
.TaxonNodeAgentsRelDTO
;
57 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonPageDto
.TaxonNodeDTO
;
58 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.config
.DistributionInfoConfiguration
;
59 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.config
.TaxonPageDtoConfiguration
;
60 import eu
.etaxonomy
.cdm
.api
.service
.exception
.TypeDesignationSetException
;
61 import eu
.etaxonomy
.cdm
.api
.service
.geo
.DistributionServiceUtilities
;
62 import eu
.etaxonomy
.cdm
.api
.service
.geo
.IDistributionService
;
63 import eu
.etaxonomy
.cdm
.api
.service
.l10n
.LocaleContext
;
64 import eu
.etaxonomy
.cdm
.api
.service
.name
.TypeDesignationSetContainer
;
65 import eu
.etaxonomy
.cdm
.api
.service
.name
.TypeDesignationSetFormatter
;
66 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
67 import eu
.etaxonomy
.cdm
.common
.TreeNode
;
68 import eu
.etaxonomy
.cdm
.compare
.taxon
.TaxonComparator
;
69 import eu
.etaxonomy
.cdm
.format
.common
.TypedLabel
;
70 import eu
.etaxonomy
.cdm
.format
.description
.CategoricalDataFormatter
;
71 import eu
.etaxonomy
.cdm
.format
.description
.QuantitativeDataFormatter
;
72 import eu
.etaxonomy
.cdm
.format
.description
.distribution
.CondensedDistributionConfiguration
;
73 import eu
.etaxonomy
.cdm
.format
.reference
.OriginalSourceFormatter
;
74 import eu
.etaxonomy
.cdm
.format
.taxon
.TaxonRelationshipFormatter
;
75 import eu
.etaxonomy
.cdm
.model
.common
.AnnotatableEntity
;
76 import eu
.etaxonomy
.cdm
.model
.common
.Annotation
;
77 import eu
.etaxonomy
.cdm
.model
.common
.AnnotationType
;
78 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
79 import eu
.etaxonomy
.cdm
.model
.common
.ICdmBase
;
80 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
81 import eu
.etaxonomy
.cdm
.model
.common
.LanguageString
;
82 import eu
.etaxonomy
.cdm
.model
.common
.Marker
;
83 import eu
.etaxonomy
.cdm
.model
.common
.MarkerType
;
84 import eu
.etaxonomy
.cdm
.model
.common
.MultilanguageTextHelper
;
85 import eu
.etaxonomy
.cdm
.model
.common
.SingleSourcedEntityBase
;
86 import eu
.etaxonomy
.cdm
.model
.common
.VersionableEntity
;
87 import eu
.etaxonomy
.cdm
.model
.description
.CategoricalData
;
88 import eu
.etaxonomy
.cdm
.model
.description
.CommonTaxonName
;
89 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementBase
;
90 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementSource
;
91 import eu
.etaxonomy
.cdm
.model
.description
.Distribution
;
92 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
93 import eu
.etaxonomy
.cdm
.model
.description
.IndividualsAssociation
;
94 import eu
.etaxonomy
.cdm
.model
.description
.PresenceAbsenceTerm
;
95 import eu
.etaxonomy
.cdm
.model
.description
.QuantitativeData
;
96 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
97 import eu
.etaxonomy
.cdm
.model
.description
.TaxonInteraction
;
98 import eu
.etaxonomy
.cdm
.model
.description
.TemporalData
;
99 import eu
.etaxonomy
.cdm
.model
.description
.TextData
;
100 import eu
.etaxonomy
.cdm
.model
.location
.NamedArea
;
101 import eu
.etaxonomy
.cdm
.model
.media
.ImageFile
;
102 import eu
.etaxonomy
.cdm
.model
.media
.Media
;
103 import eu
.etaxonomy
.cdm
.model
.media
.MediaRepresentation
;
104 import eu
.etaxonomy
.cdm
.model
.media
.MediaRepresentationPart
;
105 import eu
.etaxonomy
.cdm
.model
.name
.HomotypicalGroup
;
106 import eu
.etaxonomy
.cdm
.model
.name
.SpecimenTypeDesignation
;
107 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
108 import eu
.etaxonomy
.cdm
.model
.name
.TypeDesignationBase
;
109 import eu
.etaxonomy
.cdm
.model
.occurrence
.DerivedUnit
;
110 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
111 import eu
.etaxonomy
.cdm
.model
.reference
.ISourceable
;
112 import eu
.etaxonomy
.cdm
.model
.reference
.NamedSource
;
113 import eu
.etaxonomy
.cdm
.model
.reference
.NamedSourceBase
;
114 import eu
.etaxonomy
.cdm
.model
.reference
.OriginalSourceBase
;
115 import eu
.etaxonomy
.cdm
.model
.taxon
.Classification
;
116 import eu
.etaxonomy
.cdm
.model
.taxon
.Synonym
;
117 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
118 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
119 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
120 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNodeAgentRelation
;
121 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNodeStatus
;
122 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationship
;
123 import eu
.etaxonomy
.cdm
.model
.term
.Representation
;
124 import eu
.etaxonomy
.cdm
.model
.term
.TermBase
;
125 import eu
.etaxonomy
.cdm
.model
.term
.TermNode
;
126 import eu
.etaxonomy
.cdm
.model
.term
.TermTree
;
127 import eu
.etaxonomy
.cdm
.strategy
.cache
.TaggedCacheHelper
;
128 import eu
.etaxonomy
.cdm
.strategy
.cache
.TaggedText
;
129 import eu
.etaxonomy
.cdm
.strategy
.cache
.taxon
.TaxonBaseDefaultCacheStrategy
;
132 * Loads the portal dto from a taxon instance.
133 * Maybe later also supports loading from persistence.
138 public class PortalDtoLoader
{
140 private static final Logger logger
= LogManager
.getLogger();
142 private ICdmRepository repository
;
144 public PortalDtoLoader(ICdmRepository repository
) {
145 this.repository
= repository
;
148 public TaxonPageDto
load(Taxon taxon
, TaxonPageDtoConfiguration config
) {
149 TaxonPageDto result
= new TaxonPageDto();
151 TaxonName name
= taxon
.getName();
154 loadBaseData(taxon
, result
);
155 result
.setLastUpdated(getLastUpdated(null, taxon
));
156 result
.setNameLabel(name
!= null? name
.getTitleCache() : "");
157 result
.setLabel(CdmUtils
.Nz(taxon
.getTitleCache()));
158 // result.setTypedTaxonLabel(getTypedTaxonLabel(taxon, config));
159 result
.setTaggedLabel(getTaggedTaxon(taxon
, config
));
161 loadTaxonNodes(taxon
, result
, config
);
162 loadSynonyms(taxon
, result
, config
);
163 loadConceptRelations(taxon
, result
, config
);
164 loadFacts(taxon
, result
, config
);
165 loadMedia(taxon
, result
, config
);
166 loadSpecimens(taxon
, result
, config
);
167 loadKeys(taxon
, result
, config
);
172 private List
<TaggedText
> getTaggedTaxon(TaxonBase
<?
> taxon
, TaxonPageDtoConfiguration config
) {
173 // List<TypedLabel> result = new ArrayList<>();
174 TaxonBaseDefaultCacheStrategy
<TaxonBase
<?
>> formatter
= new TaxonBaseDefaultCacheStrategy
<>();
175 List
<TaggedText
> tags
= formatter
.getTaggedTitle(taxon
);
179 private void loadKeys(Taxon taxon
, TaxonPageDto result
, TaxonPageDtoConfiguration config
) {
180 ContainerDto
<KeyDTO
> container
= new ContainerDto
<>();
183 if (container
.getCount() > 0) {
184 result
.setKeys(container
);
188 private void loadSpecimens(Taxon taxon
, TaxonPageDto result
, TaxonPageDtoConfiguration config
) {
189 //TODO load specimen from multiple places
191 ContainerDto
<SpecimenDTO
> container
= new ContainerDto
<>();
193 List
<SpecimenOrObservationBase
<?
>> specimens
= new ArrayList
<>();
194 for (TaxonDescription taxonDescription
: taxon
.getDescriptions()) {
195 if (taxonDescription
.isImageGallery()) {
198 for (DescriptionElementBase el
: taxonDescription
.getElements()) {
199 if (el
.isInstanceOf(IndividualsAssociation
.class)) {
200 IndividualsAssociation indAss
= CdmBase
.deproxy(el
, IndividualsAssociation
.class);
201 SpecimenOrObservationBase
<?
> specimen
= indAss
.getAssociatedSpecimenOrObservation();
202 specimens
.add(specimen
);
206 List
<SpecimenOrObservationBase
<?
>> typeSpecimens
= loadTypeSpecimen(taxon
.getName(), config
);
207 specimens
.addAll(typeSpecimens
);
208 for (TaxonName syn
: taxon
.getSynonymNames()) {
209 typeSpecimens
= loadTypeSpecimen(syn
, config
);
210 specimens
.addAll(typeSpecimens
);
213 for (SpecimenOrObservationBase
<?
> specimen
: specimens
) {
214 SpecimenDTO dto
= new SpecimenDTO();
215 loadBaseData(specimen
, dto
);
216 dto
.setLabel(specimen
.getTitleCache());
217 container
.addItem(dto
);
219 if (container
.getCount() > 0 ) {
220 result
.setSpecimens(container
);
224 private List
<SpecimenOrObservationBase
<?
>> loadTypeSpecimen(TaxonName name
, TaxonPageDtoConfiguration config
) {
225 List
<SpecimenOrObservationBase
<?
>> result
= new ArrayList
<>();
226 for (SpecimenTypeDesignation desig
: name
.getSpecimenTypeDesignations()){
227 DerivedUnit specimen
= desig
.getTypeSpecimen();
228 if (specimen
!= null) {
229 result
.add(specimen
);
235 private void loadMedia(Taxon taxon
, TaxonPageDto result
, TaxonPageDtoConfiguration config
) {
237 ContainerDto
<MediaDTO
> container
= new ContainerDto
<TaxonPageDto
.MediaDTO
>();
239 List
<Media
> medias
= new ArrayList
<>();
240 for (TaxonDescription taxonDescription
: taxon
.getDescriptions()) {
241 if (!taxonDescription
.isImageGallery()) {
245 List
<Media
> newMedia
= taxonDescription
.getElements().stream()
246 .filter(el
->el
.isInstanceOf(TextData
.class))
247 .map(el
->CdmBase
.deproxy(el
, TextData
.class))
249 .flatMap(td
->td
.getMedia().stream())
250 .collect(Collectors
.toList())
252 medias
.addAll(newMedia
);
254 //TODO collect media from elsewhere
255 for (Media media
: medias
) {
256 MediaDTO dto
= new TaxonPageDto
.MediaDTO();
257 loadBaseData(media
, dto
);
258 dto
.setLabel(media
.getTitleCache());
259 ContainerDto
<MediaRepresentationDTO
> representations
= new ContainerDto
<>();
260 for (MediaRepresentation rep
: media
.getRepresentations()) {
261 MediaRepresentationDTO repDto
= new MediaRepresentationDTO();
262 loadBaseData(rep
, dto
);
263 repDto
.setMimeType(rep
.getMimeType());
264 repDto
.setSuffix(rep
.getSuffix());
265 if (!rep
.getParts().isEmpty()) {
266 //TODO handle message if n(parts) > 1
267 MediaRepresentationPart part
= rep
.getParts().get(0);
268 repDto
.setUri(part
.getUri());
269 repDto
.setClazz(part
.getClass());
270 repDto
.setSize(part
.getSize());
271 if (part
.isInstanceOf(ImageFile
.class)) {
272 ImageFile image
= CdmBase
.deproxy(part
, ImageFile
.class);
273 repDto
.setHeight(image
.getHeight());
274 repDto
.setWidth(image
.getWidth());
276 //TODO AudioFile etc.
278 representations
.addItem(repDto
);
280 if (representations
.getCount() > 0) {
281 dto
.setRepresentations(representations
);
283 //TODO load representation data
284 container
.addItem(dto
);
287 if (container
.getCount() > 0) {
288 result
.setMedia(container
);
293 private void loadTaxonNodes(Taxon taxon
, TaxonPageDto result
, TaxonPageDtoConfiguration config
) {
294 ContainerDto
<TaxonNodeDTO
> container
= new ContainerDto
<TaxonPageDto
.TaxonNodeDTO
>();
295 for (TaxonNode node
: taxon
.getTaxonNodes()) {
296 TaxonNodeDTO dto
= new TaxonNodeDTO();
297 loadBaseData(node
, dto
);
299 Classification classification
= node
.getClassification();
300 if (classification
!= null) {
301 dto
.setClassificationUuid(node
.getClassification().getUuid());
302 dto
.setClassificationLabel(classification
.getName().getText());
305 Language language
= Language
.DEFAULT();
308 TaxonNodeStatus status
= node
.getStatus();
309 if (status
!= null) {
310 dto
.setStatus(status
.getLabel(language
));
313 Map
<Language
, LanguageString
> statusNote
= node
.getStatusNote();
314 if (statusNote
!= null) {
315 //TODO handle fallback lang
316 LanguageString statusNoteStr
= statusNote
.get(language
);
317 if (statusNoteStr
== null && statusNote
.size() > 0) {
318 statusNoteStr
= statusNote
.entrySet().iterator().next().getValue();
320 if (statusNoteStr
!= null) {
321 dto
.setStatusNote(statusNoteStr
.getText());
325 Set
<TaxonNodeAgentRelation
> agents
= node
.getAgentRelations();
326 if (!agents
.isEmpty()) {
327 for (TaxonNodeAgentRelation rel
: agents
) {
328 TaxonNodeAgentsRelDTO agentDto
= new TaxonNodeAgentsRelDTO();
329 loadBaseData(rel
, agentDto
);
332 if (rel
.getAgent() != null) {
333 agentDto
.setAgent(rel
.getAgent().getFullTitle());
334 agentDto
.setAgentUuid(rel
.getAgent().getUuid());
335 //TODO compute preferred external link
336 agentDto
.setAgentLink(null);
338 if (rel
.getType() != null) {
339 agentDto
.setType(rel
.getType().getTitleCache());
340 agentDto
.setTypeUuid(rel
.getType().getUuid());
342 dto
.addAgent(agentDto
);
345 container
.addItem(dto
);
347 if (container
.getCount() > 0) {
348 result
.setTaxonNodes(container
);
354 private void loadSynonyms(Taxon taxon
, TaxonPageDto result
, TaxonPageDtoConfiguration config
) {
355 // List<HomotypicalGroup> homotypicGroups = taxon.getHomotypicSynonymyGroups();
357 TaxonComparator comparator
= new TaxonComparator();
359 TaxonName name
= taxon
.getName();
361 //TODO depending on config add/remove accepted name
363 //TODO check publish flag
366 List
<Synonym
> homotypicSynonmys
= taxon
.getHomotypicSynonymsByHomotypicGroup(comparator
);
367 TaxonPageDto
.HomotypicGroupDTO homotypicGroupDto
= new TaxonPageDto
.HomotypicGroupDTO();
368 if (!homotypicSynonmys
.isEmpty()) {
369 loadBaseData(name
.getHomotypicalGroup(), homotypicGroupDto
);
371 for (Synonym syn
: homotypicSynonmys
) {
372 loadSynonymsInGroup(homotypicGroupDto
, syn
, config
);
376 handleTypification(name
.getHomotypicalGroup(), homotypicGroupDto
, result
, config
);
377 result
.setHomotypicSynonyms(homotypicGroupDto
);
379 //heterotypic synonyms
380 List
<HomotypicalGroup
> heteroGroups
= taxon
.getHeterotypicSynonymyGroups();
381 if (heteroGroups
.isEmpty()) {
384 ContainerDto
<HomotypicGroupDTO
> heteroContainer
= new ContainerDto
<>();
385 result
.setHeterotypicSynonymGroups(heteroContainer
);
387 for (HomotypicalGroup hg
: heteroGroups
) {
388 TaxonPageDto
.HomotypicGroupDTO hgDto
= new TaxonPageDto
.HomotypicGroupDTO();
389 loadBaseData(taxon
.getName().getHomotypicalGroup(), hgDto
);
390 heteroContainer
.addItem(hgDto
);
392 List
<Synonym
> heteroSyns
= taxon
.getSynonymsInGroup(hg
, comparator
);
393 for (Synonym syn
: heteroSyns
) {
394 loadSynonymsInGroup(hgDto
, syn
, config
);
396 handleTypification(hg
, hgDto
, result
, config
);
400 private void handleTypification(HomotypicalGroup homotypicalGroup
, HomotypicGroupDTO hgDto
,
401 TaxonPageDto result
, TaxonPageDtoConfiguration config
) {
403 boolean withCitation
= true;
404 boolean withStartingTypeLabel
= true;
405 boolean withNameIfAvailable
= false;
406 TypeDesignationSetFormatter formatter
= new TypeDesignationSetFormatter(
407 withCitation
, withStartingTypeLabel
, withNameIfAvailable
);
408 Set
<TypeDesignationBase
<?
>> desigs
= homotypicalGroup
.getTypeDesignations();
410 TypeDesignationSetContainer manager
= TypeDesignationSetContainer
.NewDefaultInstance((Set
)desigs
);
411 List
<TaggedText
> tags
= formatter
.toTaggedText(manager
);
412 String label
= TaggedCacheHelper
.createString(tags
);
413 hgDto
.setTypes(label
);
414 hgDto
.setTaggedTypes(tags
);
415 // hgDto.setTypedTypes(null);
417 } catch (TypeDesignationSetException e
) {
418 result
.addMessage(new MessagesDto(MessageType
.ERROR
, "Error when creating type designation information"));
422 private void loadConceptRelations(Taxon taxon
, TaxonPageDto result
, TaxonPageDtoConfiguration config
) {
425 ContainerDto
<ConceptRelationDTO
> conceptRelContainer
= new ContainerDto
<>();
426 TaxonRelationshipFormatter taxRelFormatter
= TaxonRelationshipFormatter
.INSTANCE();
429 Set
<TaxonRelationship
> misappliedRels
= taxon
.getMisappliedNameRelations();
430 for (TaxonRelationship rel
: misappliedRels
) {
431 boolean inverse
= true;
432 boolean withoutName
= false;
433 loadConceptRelation(taxRelFormatter
, rel
, conceptRelContainer
, inverse
, withoutName
);
436 //... pro parte Synonyms
437 Set
<TaxonRelationship
> proParteRels
= taxon
.getProParteAndPartialSynonymRelations();
438 for (TaxonRelationship rel
: proParteRels
) {
439 boolean inverse
= true;
440 boolean withoutName
= false;
441 loadConceptRelation(taxRelFormatter
, rel
, conceptRelContainer
, inverse
, withoutName
);
444 //TODO MAN and pp from this taxon
447 Set
<TaxonRelationship
> toRels
= taxon
.getRelationsToThisTaxon();
448 for (TaxonRelationship rel
: toRels
) {
449 boolean inverse
= true;
450 boolean withoutName
= false;
451 loadConceptRelation(taxRelFormatter
, rel
, conceptRelContainer
, inverse
, withoutName
);
455 Set
<TaxonRelationship
> fromRels
= taxon
.getRelationsFromThisTaxon();
456 for (TaxonRelationship rel
: fromRels
) {
457 boolean inverse
= false;
458 boolean withoutName
= false;
459 loadConceptRelation(taxRelFormatter
, rel
, conceptRelContainer
, inverse
, withoutName
);
462 if (conceptRelContainer
.getCount() > 0) {
463 result
.setConceptRelations(conceptRelContainer
);
467 private void loadConceptRelation(TaxonRelationshipFormatter taxRelFormatter
, TaxonRelationship rel
, ContainerDto
<ConceptRelationDTO
> conceptRelContainer
, boolean inverse
,
468 boolean withoutName
) {
469 List
<Language
> languages
= Arrays
.asList(new Language
[] {Language
.DEFAULT()}); // TODO config.locales;
470 List
<TaggedText
> tags
= taxRelFormatter
.getTaggedText(rel
, inverse
, languages
, withoutName
);
471 String relLabel
= TaggedCacheHelper
.createString(tags
);
472 ConceptRelationDTO dto
= new TaxonPageDto
.ConceptRelationDTO();
473 loadBaseData(rel
, dto
);
474 Taxon relTaxon
= inverse ? rel
.getFromTaxon() : rel
.getToTaxon();
475 dto
.setRelTaxonId(relTaxon
.getId());
476 dto
.setRelTaxonUuid(relTaxon
.getUuid());
477 dto
.setRelTaxonLabel(relTaxon
.getTitleCache());
478 dto
.setLabel(relLabel
);
479 conceptRelContainer
.addItem(dto
);
482 private void loadSynonymsInGroup(TaxonPageDto
.HomotypicGroupDTO hgDto
, Synonym syn
, TaxonPageDtoConfiguration config
) {
483 TaxonBaseDto synDto
= new TaxonBaseDto();
484 loadBaseData(syn
, synDto
);
485 synDto
.setNameLabel(syn
.getName().getTitleCache());
486 synDto
.setLabel(syn
.getTitleCache());
487 synDto
.setTaggedLabel(getTaggedTaxon(syn
, config
));
489 hgDto
.addSynonym(synDto
);
492 private void loadFacts(Taxon taxon
, TaxonPageDto taxonPageDto
, TaxonPageDtoConfiguration config
) {
494 //compute the feature that do exist for this taxon
495 Map
<UUID
, Feature
> existingFeatureUuids
= getExistingFeatureUuids(taxon
);
497 //evaluate feature tree if it exists
498 TreeNode
<Feature
, UUID
> filteredRootNode
;
499 if (config
.getFeatureTree() != null) {
502 TermTree
<Feature
> featureTree
= repository
.getTermTreeService().find(config
.getFeatureTree());
503 filteredRootNode
= filterFeatureNode(featureTree
.getRoot(), existingFeatureUuids
.keySet());
505 filteredRootNode
= createDefaultFeatureNode(taxon
);
508 //load facts per feature
509 Map
<UUID
,Set
<DescriptionElementBase
>> featureMap
= loadfeatureMap(taxon
);
512 if (!filteredRootNode
.getChildren().isEmpty()) {
513 ContainerDto
<FeatureDto
> features
= new ContainerDto
<>();
514 for (TreeNode
<Feature
,UUID
> node
: filteredRootNode
.getChildren()) {
515 handleFeatureNode(taxon
, config
, featureMap
, features
, node
);
517 taxonPageDto
.setFactualData(features
);
521 private void handleFeatureNode(Taxon taxon
, TaxonPageDtoConfiguration config
,
522 Map
<UUID
, Set
<DescriptionElementBase
>> featureMap
, ContainerDto
<FeatureDto
> features
,
523 TreeNode
<Feature
, UUID
> node
) {
525 Feature feature
= node
.getData();
527 FeatureDto featureDto
= new FeatureDto(feature
.getUuid(), feature
.getId(), feature
.getLabel());
528 features
.addItem(featureDto
);
530 List
<Distribution
> distributions
= new ArrayList
<>();
532 for (DescriptionElementBase fact
: featureMap
.get(feature
.getUuid())){
533 if (fact
.isInstanceOf(Distribution
.class)) {
534 distributions
.add(CdmBase
.deproxy(fact
, Distribution
.class));
536 handleFact(featureDto
, fact
);
540 handleDistributions(config
, featureDto
, taxon
, distributions
);
543 ContainerDto
<FeatureDto
> childFeatures
= new ContainerDto
<>();
544 for (TreeNode
<Feature
,UUID
> child
: node
.getChildren()) {
545 handleFeatureNode(taxon
, config
, featureMap
, childFeatures
, child
);
547 if (childFeatures
.getCount() > 0) {
548 featureDto
.setSubFeatures(childFeatures
);
552 private Map
<UUID
, Set
<DescriptionElementBase
>> loadfeatureMap(Taxon taxon
) {
553 Map
<UUID
, Set
<DescriptionElementBase
>> featureMap
= new HashMap
<>();
556 for (TaxonDescription taxonDescription
: taxon
.getDescriptions()) {
557 if (taxonDescription
.isImageGallery()) {
560 for (DescriptionElementBase deb
: taxonDescription
.getElements()) {
561 Feature feature
= deb
.getFeature();
562 if (featureMap
.get(feature
.getUuid()) == null) {
563 featureMap
.put(feature
.getUuid(), new HashSet
<>());
565 featureMap
.get(feature
.getUuid()).add(deb
);
571 private TreeNode
<Feature
, UUID
> createDefaultFeatureNode(Taxon taxon
) {
572 TreeNode
<Feature
, UUID
> root
= new TreeNode
<>();
573 Set
<Feature
> requiredFeatures
= new HashSet
<>();
575 for (TaxonDescription taxonDescription
: taxon
.getDescriptions()) {
576 if (taxonDescription
.isImageGallery()) {
579 for (DescriptionElementBase deb
: taxonDescription
.getElements()) {
580 Feature feature
= deb
.getFeature();
581 if (feature
!= null) { //null should not happen
582 requiredFeatures
.add(feature
);
586 List
<Feature
> sortedChildren
= new ArrayList
<>(requiredFeatures
);
587 Collections
.sort(sortedChildren
, (f1
,f2
) -> f1
.getTitleCache().compareTo(f2
.getTitleCache()));
588 sortedChildren
.stream().forEachOrdered(f
->root
.addChild(new TreeNode
<>(f
.getUuid(), f
)));
593 * Recursive call to a feature tree's feature node in order to creates a tree structure
594 * ordered in the same way as the according feature tree but only containing features
595 * that do really exist for the given taxon. If only a child node is required the parent
596 * node/feature is also considered to be required.<BR>
598 private TreeNode
<Feature
, UUID
> filterFeatureNode(TermNode
<Feature
> featureNode
,
599 Set
<UUID
> existingFeatureUuids
) {
601 //first filter children
602 List
<TreeNode
<Feature
, UUID
>> requiredChildNodes
= new ArrayList
<>();
603 for (TermNode
<Feature
> childNode
: featureNode
.getChildNodes()) {
604 TreeNode
<Feature
, UUID
> child
= filterFeatureNode(childNode
, existingFeatureUuids
);
606 requiredChildNodes
.add(child
);
610 //if any child is required or this node is required ....
611 if (!requiredChildNodes
.isEmpty() ||
612 featureNode
.getTerm() != null && existingFeatureUuids
.contains(featureNode
.getTerm().getUuid())) {
613 TreeNode
<Feature
,UUID
> result
= new TreeNode
<>();
614 //add this nodes data
615 Feature feature
= featureNode
.getTerm() == null ?
null : featureNode
.getTerm();
616 if (feature
!= null) {
617 result
.setNodeId(feature
.getUuid());
618 result
.setData(feature
);
621 requiredChildNodes
.stream().forEachOrdered(c
->result
.addChild(c
));
629 * Computes the (unsorted) set of features for which facts exist
630 * for the given taxon.
632 private Map
<UUID
, Feature
> getExistingFeatureUuids(Taxon taxon
) {
633 Map
<UUID
, Feature
> result
= new HashMap
<>();
634 for (TaxonDescription taxonDescription
: taxon
.getDescriptions()) {
635 if (taxonDescription
.isImageGallery()) {
638 for (DescriptionElementBase deb
: taxonDescription
.getElements()) {
639 Feature feature
= deb
.getFeature();
640 if (feature
!= null) { //null should not happen
641 result
.put(feature
.getUuid(), feature
);
648 private void handleDistributions(TaxonPageDtoConfiguration config
, FeatureDto featureDto
,
649 Taxon taxon
, List
<Distribution
> distributions
) {
651 if (distributions
.isEmpty()) {
654 DistributionInfoConfiguration distributionConfig
= config
.getDistributionInfoConfiguration();
656 CondensedDistributionConfiguration condensedConfig
= distributionConfig
.getCondensedDistrConfig();
657 String statusColorsString
= distributionConfig
.getStatusColorsString();
659 IDistributionService distributionService
= repository
.getDistributionService();
661 //copied from DescriptionListController
663 boolean ignoreDistributionStatusUndefined
= true; //workaround until #9500 is fully implemented
664 distributionConfig
.setIgnoreDistributionStatusUndefined(ignoreDistributionStatusUndefined
);
665 boolean fallbackAsParent
= true; //may become a service parameter in future
667 DistributionInfoDto dto
;
669 //hiddenArea markers include markers for fully hidden areas and fallback areas. The later
670 //are hidden markers on areas that have non-hidden subareas (#4408)
671 Set
<MarkerType
> hiddenAreaMarkerTypes
= distributionConfig
.getHiddenAreaMarkerTypeList();
672 if(hiddenAreaMarkerTypes
!= null && !hiddenAreaMarkerTypes
.isEmpty()){
673 condensedConfig
.hiddenAndFallbackAreaMarkers
= hiddenAreaMarkerTypes
.stream().map(mt
->mt
.getUuid()).collect(Collectors
.toSet());
676 List
<String
> initStrategy
= null;
678 Map
<PresenceAbsenceTerm
, Color
> distributionStatusColors
;
680 distributionStatusColors
= DistributionServiceUtilities
.buildStatusColorMap(
681 statusColorsString
, repository
.getTermService(), repository
.getVocabularyService());
682 } catch (JsonProcessingException e
) {
683 logger
.error("JsonProcessingException when reading distribution status colors");
684 //TODO is null allowed?
685 distributionStatusColors
= null;
688 dto
= distributionService
.composeDistributionInfoFor(distributionConfig
, taxon
.getUuid(),
690 distributionStatusColors
, LocaleContext
.getLanguages(),
693 featureDto
.addFact(dto
);
696 private void handleFact(FeatureDto featureDto
, DescriptionElementBase fact
) {
698 Language localeLang
= null;
699 if (fact
.isInstanceOf(TextData
.class)) {
700 TextData td
= CdmBase
.deproxy(fact
, TextData
.class);
701 LanguageString ls
= td
.getPreferredLanguageString(localeLang
);
702 String text
= ls
== null ?
"" : CdmUtils
.Nz(ls
.getText());
704 FactDto factDto
= new FactDto();
705 featureDto
.addFact(factDto
);
706 //TODO do we really need type information for textdata here?
707 TypedLabel typedLabel
= new TypedLabel(text
);
708 typedLabel
.setClassAndId(td
);
709 factDto
.getTypedLabel().add(typedLabel
);
710 loadBaseData(td
, factDto
);
712 }else if (fact
.isInstanceOf(CommonTaxonName
.class)) {
713 CommonTaxonName ctn
= CdmBase
.deproxy(fact
, CommonTaxonName
.class);
714 CommonNameDto dto
= new CommonNameDto();
715 featureDto
.addFact(dto
);
717 Language lang
= ctn
.getLanguage();
719 String langLabel
= getTermLabel(lang
, localeLang
);
720 dto
.setLanguage(langLabel
);
721 dto
.setLanguageUuid(lang
.getUuid());
724 dto
.setLanguage("-");
727 NamedArea area
= ctn
.getArea();
729 String areaLabel
= getTermLabel(area
, localeLang
);
730 dto
.setArea(areaLabel
);
731 dto
.setAreaUUID(area
.getUuid());
733 dto
.setName(ctn
.getName());
734 loadBaseData(ctn
, dto
);
735 //TODO sort all common names
737 } else if (fact
.isInstanceOf(IndividualsAssociation
.class)) {
738 IndividualsAssociation ia
= CdmBase
.deproxy(fact
, IndividualsAssociation
.class);
739 IndividualsAssociationDto dto
= new IndividualsAssociationDto ();
741 LanguageString description
= MultilanguageTextHelper
.getPreferredLanguageString(ia
.getDescription(), Arrays
.asList(localeLang
));
742 if (description
!= null) {
743 dto
.setDescritpion(description
.getText());
745 SpecimenOrObservationBase
<?
> specimen
= ia
.getAssociatedSpecimenOrObservation();
746 if (specimen
!= null) {
747 //TODO what to use here??
748 dto
.setOccurrence(specimen
.getTitleCache());
749 dto
.setOccurrenceUuid(specimen
.getUuid());
752 featureDto
.addFact(dto
);
753 loadBaseData(ia
, dto
);
754 } else if (fact
.isInstanceOf(TaxonInteraction
.class)) {
755 TaxonInteraction ti
= CdmBase
.deproxy(fact
, TaxonInteraction
.class);
756 TaxonInteractionDto dto
= new TaxonInteractionDto ();
758 LanguageString description
= MultilanguageTextHelper
.getPreferredLanguageString(
759 ti
.getDescription(), Arrays
.asList(localeLang
));
760 if (description
!= null) {
761 dto
.setDescritpion(description
.getText());
763 Taxon taxon
= ti
.getTaxon2();
765 //TODO what to use here??
766 dto
.setTaxon(taxon
.cacheStrategy().getTaggedTitle(taxon
));
767 dto
.setTaxonUuid(taxon
.getUuid());
769 featureDto
.addFact(dto
);
770 loadBaseData(ti
, dto
);
771 }else if (fact
.isInstanceOf(CategoricalData
.class)) {
772 CategoricalData cd
= CdmBase
.deproxy(fact
, CategoricalData
.class);
773 FactDto factDto
= new FactDto();
774 featureDto
.addFact(factDto
);
775 //TODO do we really need type information for textdata here?
776 String label
= CategoricalDataFormatter
.NewInstance(null).format(cd
, localeLang
);
777 TypedLabel typedLabel
= new TypedLabel(label
);
778 typedLabel
.setClassAndId(cd
);
779 factDto
.getTypedLabel().add(typedLabel
);
781 loadBaseData(cd
, factDto
);
782 }else if (fact
.isInstanceOf(QuantitativeData
.class)) {
783 QuantitativeData qd
= CdmBase
.deproxy(fact
, QuantitativeData
.class);
784 FactDto factDto
= new FactDto();
785 featureDto
.addFact(factDto
);
786 //TODO do we really need type information for textdata here?
787 String label
= QuantitativeDataFormatter
.NewInstance(null).format(qd
, localeLang
);
788 TypedLabel typedLabel
= new TypedLabel(label
);
789 typedLabel
.setClassAndId(qd
);
790 factDto
.getTypedLabel().add(typedLabel
);
792 loadBaseData(qd
, factDto
);
793 }else if (fact
.isInstanceOf(TemporalData
.class)) {
794 TemporalData td
= CdmBase
.deproxy(fact
, TemporalData
.class);
795 FactDto factDto
= new FactDto();
796 featureDto
.addFact(factDto
);
797 //TODO do we really need type information for textdata here?
798 String label
= td
.toString();
799 TypedLabel typedLabel
= new TypedLabel(label
);
800 typedLabel
.setClassAndId(td
);
801 factDto
.getTypedLabel().add(typedLabel
);
803 loadBaseData(td
, factDto
);
806 logger
.warn("DescriptionElement type not yet handled: " + fact
.getClass().getSimpleName());
811 private String
getTermLabel(TermBase term
, Language localeLang
) {
815 Representation rep
= term
.getPreferredRepresentation(localeLang
);
816 String label
= rep
== null ?
null : rep
.getLabel();
817 label
= label
== null ? term
.getLabel() : label
;
822 * Compares an existing last date and the last date of an entity
823 * and returns the resulting last date.
825 private LocalDateTime
getLastUpdated(LocalDateTime existingLastDate
, VersionableEntity dateToAddEntity
) {
827 DateTime dateToAdd
= dateToAddEntity
.getUpdated() != null ? dateToAddEntity
.getUpdated() : dateToAddEntity
.getCreated();
829 LocalDateTime javaLocalDateTimeOfEntity
= dateToAdd
== null ?
null:
830 LocalDateTime
.of(dateToAdd
.getYear(), dateToAdd
.getMonthOfYear(),
831 dateToAdd
.getDayOfMonth(), dateToAdd
.getHourOfDay(),
832 dateToAdd
.getMinuteOfHour(), dateToAdd
.getSecondOfMinute());
834 if (existingLastDate
== null) {
835 return javaLocalDateTimeOfEntity
;
836 }else if (javaLocalDateTimeOfEntity
== null || javaLocalDateTimeOfEntity
.compareTo(existingLastDate
) < 0) {
837 return existingLastDate
;
839 return javaLocalDateTimeOfEntity
;
843 private void loadBaseData(CdmBase cdmBase
, CdmBaseDto dto
) {
844 dto
.setId(cdmBase
.getId());
845 dto
.setUuid(cdmBase
.getUuid());
847 loadAnnotatable(cdmBase
, dto
);
848 loadSources(cdmBase
, dto
);
849 //loadIdentifiable(cdmBase, dto);
852 private void loadSources(CdmBase cdmBase
, CdmBaseDto dto
) {
853 if (dto
instanceof SingleSourcedDto
&& cdmBase
.isInstanceOf(SingleSourcedEntityBase
.class)) {
855 SingleSourcedEntityBase sourced
= CdmBase
.deproxy(cdmBase
, SingleSourcedEntityBase
.class);
856 SingleSourcedDto sourcedDto
= (SingleSourcedDto
)dto
;
857 NamedSource source
= sourced
.getSource();
858 SourceDto sourceDto
= new SourceDto();
859 loadSource(source
, sourceDto
);
860 sourcedDto
.setSource(sourceDto
);
861 } else if (dto
instanceof SourcedDto
&& cdmBase
instanceof ISourceable
) {
862 @SuppressWarnings("unchecked")
863 ISourceable
<OriginalSourceBase
> sourced
= (ISourceable
<OriginalSourceBase
>)cdmBase
;
864 SourcedDto sourcedDto
= (SourcedDto
)dto
;
865 for (OriginalSourceBase source
: sourced
.getSources()) {
866 SourceDto sourceDto
= new SourceDto();
867 loadSource(source
, sourceDto
);
868 sourcedDto
.addSource(sourceDto
);
873 private void loadSource(OriginalSourceBase source
, SourceDto sourceDto
) {
876 loadBaseData(source
, sourceDto
);
878 ICdmBase linkedObject
= source
.getCitation();
879 if (linkedObject
== null) {
881 linkedObject
= source
.getCdmSource();
884 //citation uuid & doi
885 if (source
.getCitation()!= null) {
886 sourceDto
.setDoi(source
.getCitation().getDoiString());
890 //TODO this creates only the short form and probably also does not use
891 // specimen or cdmSource if necessary
892 String label
= OriginalSourceFormatter
.INSTANCE
.format(source
);
893 TypedLabel typedLabel
= new TypedLabel(source
, label
);
894 sourceDto
.addLabel(typedLabel
);
895 sourceDto
.setType(source
.getType() != null ? source
.getType().toString() : null);
897 if (source
.isInstanceOf(NamedSourceBase
.class)) {
898 NamedSourceBase ns
= CdmBase
.deproxy(source
, NamedSourceBase
.class);
901 TaxonName name
= ns
.getNameUsedInSource();
903 List
<TaggedText
> taggedName
= name
.cacheStrategy().getTaggedTitle(name
);
905 sourceDto
.setNameInSource(taggedName
);
909 if (source
.isInstanceOf(DescriptionElementSource
.class)) {
910 DescriptionElementSource des
= CdmBase
.deproxy(source
, DescriptionElementSource
.class);
911 if (linkedObject
== null) {
912 linkedObject
= des
.getSpecimen();
917 sourceDto
.setLinkedUuid(getUuid(linkedObject
));
918 String linkedObjectStr
= linkedObject
== null ?
null : CdmBase
.deproxy(linkedObject
).getClass().getSimpleName();
919 sourceDto
.setLinkedClass(linkedObjectStr
);
922 private UUID
getUuid(ICdmBase cdmBase
) {
923 return cdmBase
== null ?
null : cdmBase
.getUuid();
926 private void loadAnnotatable(CdmBase cdmBase
, CdmBaseDto dto
) {
927 if (dto
instanceof AnnotatableDto
&& cdmBase
.isInstanceOf(AnnotatableEntity
.class)) {
928 AnnotatableEntity annotatable
= CdmBase
.deproxy(cdmBase
, AnnotatableEntity
.class);
929 AnnotatableDto annotatableDto
= (AnnotatableDto
)dto
;
931 for (Annotation annotation
: annotatable
.getAnnotations()) {
932 if (annotation
.getAnnotationType() != null
933 //TODO annotation type filter
934 && annotation
.getAnnotationType().getUuid().equals(AnnotationType
.uuidEditorial
)
935 && StringUtils
.isNotBlank(annotation
.getText())) {
937 AnnotationDto annotationDto
= new AnnotationDto();
938 annotatableDto
.addAnnotation(annotationDto
);
939 //TODO id needed? but need to adapt dto and container then
940 loadBaseData(annotation
, annotationDto
);
941 annotationDto
.setText(annotation
.getText());
942 UUID uuidAnnotationType
= annotation
.getAnnotationType() == null ?
null :annotation
.getAnnotationType().getUuid();
943 annotationDto
.setTypeUuid(uuidAnnotationType
);
944 //language etc. currently not yet used
949 for (Marker marker
: annotatable
.getMarkers()) {
950 if (marker
.getMarkerType() != null
951 //TODO markertype filter
952 // && marker.getMarkerType().getUuid().equals(AnnotationType.uuidEditorial)
955 MarkerDto markerDto
= new MarkerDto();
956 annotatableDto
.addMarker(markerDto
);
957 //TODO id needed? but need to adapt dto and container then
958 loadBaseData(marker
, markerDto
);
959 if (marker
.getMarkerType() != null) {
960 markerDto
.setTypeUuid(marker
.getMarkerType().getUuid());
962 markerDto
.setType(marker
.getMarkerType().getTitleCache());
964 markerDto
.setValue(marker
.getValue());