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
.time
.LocalDateTime
;
12 import java
.util
.ArrayList
;
13 import java
.util
.Arrays
;
14 import java
.util
.HashMap
;
15 import java
.util
.HashSet
;
16 import java
.util
.List
;
19 import java
.util
.stream
.Collectors
;
21 import org
.joda
.time
.DateTime
;
23 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.CdmBaseDto
;
24 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.ContainerDto
;
25 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.FactDto
;
26 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.FeatureDto
;
27 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.MessagesDto
;
28 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.MessagesDto
.MessageType
;
29 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonBaseDto
;
30 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonPageDto
;
31 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonPageDto
.ConceptRelationDTO
;
32 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonPageDto
.HomotypicGroupDTO
;
33 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonPageDto
.KeyDTO
;
34 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonPageDto
.MediaDTO
;
35 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonPageDto
.MediaRepresentationDTO
;
36 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonPageDto
.SpecimenDTO
;
37 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonPageDto
.TaxonNodeAgentsRelDTO
;
38 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.TaxonPageDto
.TaxonNodeDTO
;
39 import eu
.etaxonomy
.cdm
.api
.dto
.portal
.config
.TaxonPageDtoConfiguration
;
40 import eu
.etaxonomy
.cdm
.api
.service
.exception
.TypeDesignationSetException
;
41 import eu
.etaxonomy
.cdm
.api
.service
.name
.TypeDesignationSetContainer
;
42 import eu
.etaxonomy
.cdm
.api
.service
.name
.TypeDesignationSetFormatter
;
43 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
44 import eu
.etaxonomy
.cdm
.compare
.taxon
.TaxonComparator
;
45 import eu
.etaxonomy
.cdm
.format
.common
.TypedLabel
;
46 import eu
.etaxonomy
.cdm
.format
.taxon
.TaxonRelationshipFormatter
;
47 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
48 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
49 import eu
.etaxonomy
.cdm
.model
.common
.LanguageString
;
50 import eu
.etaxonomy
.cdm
.model
.common
.VersionableEntity
;
51 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementBase
;
52 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
53 import eu
.etaxonomy
.cdm
.model
.description
.IndividualsAssociation
;
54 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
55 import eu
.etaxonomy
.cdm
.model
.description
.TextData
;
56 import eu
.etaxonomy
.cdm
.model
.media
.ImageFile
;
57 import eu
.etaxonomy
.cdm
.model
.media
.Media
;
58 import eu
.etaxonomy
.cdm
.model
.media
.MediaRepresentation
;
59 import eu
.etaxonomy
.cdm
.model
.media
.MediaRepresentationPart
;
60 import eu
.etaxonomy
.cdm
.model
.name
.HomotypicalGroup
;
61 import eu
.etaxonomy
.cdm
.model
.name
.SpecimenTypeDesignation
;
62 import eu
.etaxonomy
.cdm
.model
.name
.TaxonName
;
63 import eu
.etaxonomy
.cdm
.model
.name
.TypeDesignationBase
;
64 import eu
.etaxonomy
.cdm
.model
.occurrence
.DerivedUnit
;
65 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
66 import eu
.etaxonomy
.cdm
.model
.taxon
.Classification
;
67 import eu
.etaxonomy
.cdm
.model
.taxon
.Synonym
;
68 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
69 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
70 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
71 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNodeAgentRelation
;
72 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNodeStatus
;
73 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonRelationship
;
74 import eu
.etaxonomy
.cdm
.strategy
.cache
.TaggedCacheHelper
;
75 import eu
.etaxonomy
.cdm
.strategy
.cache
.TaggedText
;
76 import eu
.etaxonomy
.cdm
.strategy
.cache
.taxon
.TaxonBaseDefaultCacheStrategy
;
79 * Loads the portal dto from a taxon instance.
80 * Maybe later also supports loading from persistence.
85 public class PortalDtoLoader
{
87 public TaxonPageDto
load(Taxon taxon
, TaxonPageDtoConfiguration config
) {
88 TaxonPageDto result
= new TaxonPageDto();
90 TaxonName name
= taxon
.getName();
93 loadBaseData(taxon
, result
);
94 result
.setLastUpdated(getLastUpdated(null, taxon
));
95 result
.setNameLabel(name
!= null? name
.getTitleCache() : "");
96 result
.setTaxonLabel(CdmUtils
.Nz(taxon
.getTitleCache()));
97 // result.setTypedTaxonLabel(getTypedTaxonLabel(taxon, config));
98 result
.setTaggedTaxon(getTaggedTaxon(taxon
, config
));
100 loadTaxonNodes(taxon
, result
, config
);
101 loadSynonyms(taxon
, result
, config
);
102 loadConceptRelations(taxon
, result
, config
);
103 loadFacts(taxon
, result
, config
);
104 loadMedia(taxon
, result
, config
);
105 loadSpecimens(taxon
, result
, config
);
106 loadKeys(taxon
, result
, config
);
111 private List
<TaggedText
> getTaggedTaxon(TaxonBase
<?
> taxon
, TaxonPageDtoConfiguration config
) {
112 // List<TypedLabel> result = new ArrayList<>();
113 TaxonBaseDefaultCacheStrategy
<TaxonBase
<?
>> formatter
= new TaxonBaseDefaultCacheStrategy
<>();
114 List
<TaggedText
> tags
= formatter
.getTaggedTitle(taxon
);
118 private void loadKeys(Taxon taxon
, TaxonPageDto result
, TaxonPageDtoConfiguration config
) {
119 ContainerDto
<KeyDTO
> container
=new ContainerDto
<>();
122 if (container
.getCount() > 0) {
123 result
.setKeys(container
);
127 private void loadSpecimens(Taxon taxon
, TaxonPageDto result
, TaxonPageDtoConfiguration config
) {
128 //TODO load specimen from multiple places
130 ContainerDto
<SpecimenDTO
> container
= new ContainerDto
<TaxonPageDto
.SpecimenDTO
>();
132 List
<SpecimenOrObservationBase
<?
>> specimens
= new ArrayList
<>();
133 for (TaxonDescription taxonDescription
: taxon
.getDescriptions()) {
134 if (taxonDescription
.isImageGallery()) {
137 for (DescriptionElementBase el
: taxonDescription
.getElements()) {
138 if (el
.isInstanceOf(IndividualsAssociation
.class)) {
139 IndividualsAssociation indAss
= CdmBase
.deproxy(el
, IndividualsAssociation
.class);
140 SpecimenOrObservationBase
<?
> specimen
= indAss
.getAssociatedSpecimenOrObservation();
141 specimens
.add(specimen
);
145 List
<SpecimenOrObservationBase
<?
>> typeSpecimens
= loadTypeSpecimen(taxon
.getName(), config
);
146 specimens
.addAll(typeSpecimens
);
147 for (TaxonName syn
: taxon
.getSynonymNames()) {
148 typeSpecimens
= loadTypeSpecimen(syn
, config
);
149 specimens
.addAll(typeSpecimens
);
152 for (SpecimenOrObservationBase
<?
> specimen
: specimens
) {
153 SpecimenDTO dto
= new SpecimenDTO();
154 loadBaseData(specimen
, dto
);
155 dto
.setLabel(specimen
.getTitleCache());
156 container
.addItem(dto
);
158 if (container
.getCount() > 0 ) {
159 result
.setSpecimens(container
);
164 private List
<SpecimenOrObservationBase
<?
>> loadTypeSpecimen(TaxonName name
, TaxonPageDtoConfiguration config
) {
165 List
<SpecimenOrObservationBase
<?
>> result
= new ArrayList
<>();
166 for (SpecimenTypeDesignation desig
: name
.getSpecimenTypeDesignations()){
167 DerivedUnit specimen
= desig
.getTypeSpecimen();
168 if (specimen
!= null) {
169 result
.add(specimen
);
175 private void loadMedia(Taxon taxon
, TaxonPageDto result
, TaxonPageDtoConfiguration config
) {
177 ContainerDto
<MediaDTO
> container
= new ContainerDto
<TaxonPageDto
.MediaDTO
>();
179 List
<Media
> medias
= new ArrayList
<>();
180 for (TaxonDescription taxonDescription
: taxon
.getDescriptions()) {
181 if (!taxonDescription
.isImageGallery()) {
185 List
<Media
> newMedia
= taxonDescription
.getElements().stream()
186 .filter(el
->el
.isInstanceOf(TextData
.class))
187 .map(el
->CdmBase
.deproxy(el
, TextData
.class))
189 .flatMap(td
->td
.getMedia().stream())
190 .collect(Collectors
.toList())
192 medias
.addAll(newMedia
);
194 //TODO collect media from elsewhere
195 for (Media media
: medias
) {
196 MediaDTO dto
= new TaxonPageDto
.MediaDTO();
197 loadBaseData(media
, dto
);
198 dto
.setLabel(media
.getTitleCache());
199 ContainerDto
<MediaRepresentationDTO
> representations
= new ContainerDto
<>();
200 for (MediaRepresentation rep
: media
.getRepresentations()) {
201 MediaRepresentationDTO repDto
= new MediaRepresentationDTO();
202 loadBaseData(rep
, dto
);
203 repDto
.setMimeType(rep
.getMimeType());
204 repDto
.setSuffix(rep
.getSuffix());
205 if (!rep
.getParts().isEmpty()) {
206 //TODO handle message if n(parts) > 1
207 MediaRepresentationPart part
= rep
.getParts().get(0);
208 repDto
.setUri(part
.getUri());
209 repDto
.setClazz(part
.getClass());
210 repDto
.setSize(part
.getSize());
211 if (part
.isInstanceOf(ImageFile
.class)) {
212 ImageFile image
= CdmBase
.deproxy(part
, ImageFile
.class);
213 repDto
.setHeight(image
.getHeight());
214 repDto
.setWidth(image
.getWidth());
216 //TODO AudioFile etc.
218 representations
.addItem(repDto
);
220 if (representations
.getCount() > 0) {
221 dto
.setRepresentations(representations
);
223 //TODO load representation data
224 container
.addItem(dto
);
227 if (container
.getCount() > 0) {
228 result
.setMedia(container
);
233 private void loadTaxonNodes(Taxon taxon
, TaxonPageDto result
, TaxonPageDtoConfiguration config
) {
234 ContainerDto
<TaxonNodeDTO
> container
= new ContainerDto
<TaxonPageDto
.TaxonNodeDTO
>();
235 for (TaxonNode node
: taxon
.getTaxonNodes()) {
236 TaxonNodeDTO dto
= new TaxonNodeDTO();
237 loadBaseData(node
, dto
);
239 Classification classification
= node
.getClassification();
240 if (classification
!= null) {
241 dto
.setClassificationUuid(node
.getClassification().getUuid());
242 dto
.setClassificationLabel(classification
.getName().getText());
245 Language language
= Language
.DEFAULT();
248 TaxonNodeStatus status
= node
.getStatus();
249 if (status
!= null) {
250 dto
.setStatus(status
.getLabel(language
));
253 Map
<Language
, LanguageString
> statusNote
= node
.getStatusNote();
254 if (statusNote
!= null) {
255 //TODO handle fallback lang
256 LanguageString statusNoteStr
= statusNote
.get(language
);
257 if (statusNoteStr
== null && statusNote
.size() > 0) {
258 statusNoteStr
= statusNote
.entrySet().iterator().next().getValue();
260 if (statusNoteStr
!= null) {
261 dto
.setStatusNote(statusNoteStr
.getText());
265 Set
<TaxonNodeAgentRelation
> agents
= node
.getAgentRelations();
266 if (!agents
.isEmpty()) {
267 for (TaxonNodeAgentRelation rel
: agents
) {
268 TaxonNodeAgentsRelDTO agentDto
= new TaxonNodeAgentsRelDTO();
269 loadBaseData(rel
, agentDto
);
272 if (rel
.getAgent() != null) {
273 agentDto
.setAgent(rel
.getAgent().getFullTitle());
274 agentDto
.setAgentUuid(rel
.getAgent().getUuid());
275 //TODO compute preferred external link
276 agentDto
.setAgentLink(null);
278 if (rel
.getType() != null) {
279 agentDto
.setType(rel
.getType().getTitleCache());
280 agentDto
.setTypeUuid(rel
.getType().getUuid());
282 dto
.addAgent(agentDto
);
285 container
.addItem(dto
);
287 if (container
.getCount() > 0) {
288 result
.setTaxonNodes(container
);
294 private void loadSynonyms(Taxon taxon
, TaxonPageDto result
, TaxonPageDtoConfiguration config
) {
295 // List<HomotypicalGroup> homotypicGroups = taxon.getHomotypicSynonymyGroups();
297 TaxonComparator comparator
= new TaxonComparator();
299 TaxonName name
= taxon
.getName();
301 //TODO depending on config add/remove accepted name
304 List
<Synonym
> homotypicSynonmys
= taxon
.getHomotypicSynonymsByHomotypicGroup(comparator
);
305 TaxonPageDto
.HomotypicGroupDTO homotypicGroupDto
= new TaxonPageDto
.HomotypicGroupDTO();
306 if (!homotypicSynonmys
.isEmpty()) {
307 loadBaseData(name
.getHomotypicalGroup(), homotypicGroupDto
);
309 for (Synonym syn
: homotypicSynonmys
) {
310 loadSynonymsInGroup(homotypicGroupDto
, syn
, config
);
314 handleTypification(name
.getHomotypicalGroup(), homotypicGroupDto
, result
, config
);
315 result
.setHomotypicSynonyms(homotypicGroupDto
);
317 //heterotypic synonyms
318 List
<HomotypicalGroup
> heteroGroups
= taxon
.getHeterotypicSynonymyGroups();
319 if (heteroGroups
.isEmpty()) {
322 ContainerDto
<HomotypicGroupDTO
> heteroContainer
= new ContainerDto
<>();
323 result
.setHeterotypicSynonymGroups(heteroContainer
);
325 for (HomotypicalGroup hg
: heteroGroups
) {
326 TaxonPageDto
.HomotypicGroupDTO hgDto
= new TaxonPageDto
.HomotypicGroupDTO();
327 loadBaseData(taxon
.getName().getHomotypicalGroup(), hgDto
);
328 heteroContainer
.addItem(hgDto
);
330 List
<Synonym
> heteroSyns
= taxon
.getSynonymsInGroup(hg
, comparator
);
331 for (Synonym syn
: heteroSyns
) {
332 loadSynonymsInGroup(hgDto
, syn
, config
);
334 handleTypification(hg
, hgDto
, result
, config
);
338 private void handleTypification(HomotypicalGroup homotypicalGroup
, HomotypicGroupDTO hgDto
,
339 TaxonPageDto result
, TaxonPageDtoConfiguration config
) {
341 boolean withCitation
= true;
342 boolean withStartingTypeLabel
= true;
343 boolean withNameIfAvailable
= false;
344 TypeDesignationSetFormatter formatter
= new TypeDesignationSetFormatter(
345 withCitation
, withStartingTypeLabel
, withNameIfAvailable
);
346 Set
<TypeDesignationBase
<?
>> desigs
= homotypicalGroup
.getTypeDesignations();
348 TypeDesignationSetContainer manager
= TypeDesignationSetContainer
.NewDefaultInstance((Set
)desigs
);
349 List
<TaggedText
> tags
= formatter
.toTaggedText(manager
);
350 String label
= TaggedCacheHelper
.createString(tags
);
351 hgDto
.setTypes(label
);
352 hgDto
.setTypedTypes(null);
354 } catch (TypeDesignationSetException e
) {
355 result
.addMessage(new MessagesDto(MessageType
.ERROR
, "Error when creating type designation information"));
359 private void loadConceptRelations(Taxon taxon
, TaxonPageDto result
, TaxonPageDtoConfiguration config
) {
362 ContainerDto
<ConceptRelationDTO
> conceptRelContainer
= new ContainerDto
<>();
363 TaxonRelationshipFormatter taxRelFormatter
= TaxonRelationshipFormatter
.INSTANCE();
366 Set
<TaxonRelationship
> misappliedRels
= taxon
.getMisappliedNameRelations();
367 for (TaxonRelationship rel
: misappliedRels
) {
368 boolean inverse
= true;
369 boolean withoutName
= false;
370 loadConceptRelation(taxRelFormatter
, rel
, conceptRelContainer
, inverse
, withoutName
);
373 //... pro parte Synonyms
374 Set
<TaxonRelationship
> proParteRels
= taxon
.getProParteAndPartialSynonymRelations();
375 for (TaxonRelationship rel
: proParteRels
) {
376 boolean inverse
= true;
377 boolean withoutName
= false;
378 loadConceptRelation(taxRelFormatter
, rel
, conceptRelContainer
, inverse
, withoutName
);
381 //TODO MAN and pp from this taxon
384 Set
<TaxonRelationship
> toRels
= taxon
.getRelationsToThisTaxon();
385 for (TaxonRelationship rel
: toRels
) {
386 boolean inverse
= true;
387 boolean withoutName
= false;
388 loadConceptRelation(taxRelFormatter
, rel
, conceptRelContainer
, inverse
, withoutName
);
392 Set
<TaxonRelationship
> fromRels
= taxon
.getRelationsFromThisTaxon();
393 for (TaxonRelationship rel
: fromRels
) {
394 boolean inverse
= false;
395 boolean withoutName
= false;
396 loadConceptRelation(taxRelFormatter
, rel
, conceptRelContainer
, inverse
, withoutName
);
399 if (conceptRelContainer
.getCount() > 0) {
400 result
.setConceptRelations(conceptRelContainer
);
404 private void loadConceptRelation(TaxonRelationshipFormatter taxRelFormatter
, TaxonRelationship rel
, ContainerDto
<ConceptRelationDTO
> conceptRelContainer
, boolean inverse
,
405 boolean withoutName
) {
406 List
<Language
> languages
= Arrays
.asList(new Language
[] {Language
.DEFAULT()}); // TODO config.locales;
407 List
<TaggedText
> tags
= taxRelFormatter
.getTaggedText(rel
, inverse
, languages
, withoutName
);
408 String relLabel
= TaggedCacheHelper
.createString(tags
);
409 ConceptRelationDTO dto
= new TaxonPageDto
.ConceptRelationDTO();
410 loadBaseData(rel
, dto
);
411 Taxon relTaxon
= inverse ? rel
.getFromTaxon() : rel
.getToTaxon();
412 dto
.setRelTaxonId(relTaxon
.getId());
413 dto
.setRelTaxonUuid(relTaxon
.getUuid());
414 dto
.setRelTaxonLabel(relTaxon
.getTitleCache());
415 dto
.setLabel(relLabel
);
416 conceptRelContainer
.addItem(dto
);
419 private void loadSynonymsInGroup(TaxonPageDto
.HomotypicGroupDTO hgDto
, Synonym syn
, TaxonPageDtoConfiguration config
) {
420 TaxonBaseDto synDto
= new TaxonBaseDto();
421 loadBaseData(syn
, synDto
);
422 synDto
.setNameLabel(syn
.getName().getTitleCache());
423 synDto
.setTaxonLabel(syn
.getTitleCache());
424 synDto
.setTaggedTaxon(getTaggedTaxon(syn
, config
));
426 hgDto
.addSynonym(synDto
);
429 private void loadFacts(Taxon taxon
, TaxonPageDto result
, TaxonPageDtoConfiguration config
) {
431 //TODO load feature tree
432 Map
<Feature
,Set
<DescriptionElementBase
>> featureMap
= new HashMap
<>();
435 for (TaxonDescription taxonDescription
: taxon
.getDescriptions()) {
436 if (taxonDescription
.isImageGallery()) {
439 for (DescriptionElementBase deb
: taxonDescription
.getElements()) {
440 Feature feature
= deb
.getFeature();
441 if (featureMap
.get(feature
) == null) {
442 featureMap
.put(feature
, new HashSet
<>());
444 featureMap
.get(feature
).add(deb
);
449 if (!featureMap
.isEmpty()) {
450 ContainerDto
<FeatureDto
> features
= new ContainerDto
<>();
451 result
.setFactualData(features
);
452 for (Feature feature
: featureMap
.keySet()) {
453 FeatureDto featureDto
= new FeatureDto();
454 featureDto
.setId(feature
.getId());
455 featureDto
.setUuid(feature
.getUuid());
457 featureDto
.setLabel(feature
.getTitleCache());
458 features
.addItem(featureDto
);
461 for (DescriptionElementBase fact
: featureMap
.get(feature
)){
462 handleFact(featureDto
, fact
);
468 private void handleFact(FeatureDto featureDto
, DescriptionElementBase fact
) {
469 if (fact
.isInstanceOf(TextData
.class)) {
470 TextData td
= CdmBase
.deproxy(fact
, TextData
.class);
472 Language lang
= null;
473 LanguageString ls
= td
.getPreferredLanguageString(lang
);
474 String text
= ls
== null ?
"" : CdmUtils
.Nz(ls
.getText());
476 FactDto factDto
= new FactDto();
477 featureDto
.getFacts().add(factDto
);
478 //TODO do we really need type information for textdata here?
479 TypedLabel typedLabel
= new TypedLabel(text
);
480 typedLabel
.setClassAndId(td
);
481 factDto
.getTypedLabel().add(typedLabel
);
489 * Compares an existing last date and the last date of an entity
490 * and returns the resulting last date.
492 private LocalDateTime
getLastUpdated(LocalDateTime existingLastDate
, VersionableEntity dateToAddEntity
) {
494 DateTime dateToAdd
= dateToAddEntity
.getUpdated() != null ? dateToAddEntity
.getUpdated() : dateToAddEntity
.getCreated();
496 LocalDateTime javaLocalDateTimeOfEntity
= dateToAdd
== null ?
null:
497 LocalDateTime
.of(dateToAdd
.getYear(), dateToAdd
.getMonthOfYear(),
498 dateToAdd
.getDayOfMonth(), dateToAdd
.getHourOfDay(),
499 dateToAdd
.getMinuteOfHour(), dateToAdd
.getSecondOfMinute());
501 if (existingLastDate
== null) {
502 return javaLocalDateTimeOfEntity
;
503 }else if (javaLocalDateTimeOfEntity
== null || javaLocalDateTimeOfEntity
.compareTo(existingLastDate
) < 0) {
504 return existingLastDate
;
506 return javaLocalDateTimeOfEntity
;
510 private void loadBaseData(CdmBase cdmBase
, CdmBaseDto dto
) {
511 dto
.setId(cdmBase
.getId());
512 dto
.setUuid(cdmBase
.getUuid());