ref #10222: improve SourceDto handling
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / portal / PortalDtoLoader.java
1 /**
2 * Copyright (C) 2023 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.portal;
10
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;
19 import java.util.Map;
20 import java.util.Set;
21 import java.util.UUID;
22 import java.util.stream.Collectors;
23
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;
28
29 import com.fasterxml.jackson.core.JsonProcessingException;
30
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;
130
131 /**
132 * Loads the portal dto from a taxon instance.
133 * Maybe later also supports loading from persistence.
134 *
135 * @author a.mueller
136 * @date 09.01.2023
137 */
138 public class PortalDtoLoader {
139
140 private static final Logger logger = LogManager.getLogger();
141
142 private ICdmRepository repository;
143
144 public PortalDtoLoader(ICdmRepository repository) {
145 this.repository = repository;
146 }
147
148 public TaxonPageDto load(Taxon taxon, TaxonPageDtoConfiguration config) {
149 TaxonPageDto result = new TaxonPageDto();
150
151 TaxonName name = taxon.getName();
152
153 //load 1:1
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));
160
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);
168
169 return result;
170 }
171
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);
176 return tags;
177 }
178
179 private void loadKeys(Taxon taxon, TaxonPageDto result, TaxonPageDtoConfiguration config) {
180 ContainerDto<KeyDTO> container = new ContainerDto<>();
181 //TODO
182
183 if (container.getCount() > 0) {
184 result.setKeys(container);
185 }
186 }
187
188 private void loadSpecimens(Taxon taxon, TaxonPageDto result, TaxonPageDtoConfiguration config) {
189 //TODO load specimen from multiple places
190
191 ContainerDto<SpecimenDTO> container = new ContainerDto<>();
192
193 List<SpecimenOrObservationBase<?>> specimens = new ArrayList<>();
194 for (TaxonDescription taxonDescription : taxon.getDescriptions()) {
195 if (taxonDescription.isImageGallery()) {
196 continue;
197 }
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);
203 }
204 }
205 }
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);
211 }
212
213 for (SpecimenOrObservationBase<?> specimen : specimens) {
214 SpecimenDTO dto = new SpecimenDTO();
215 loadBaseData(specimen, dto);
216 dto.setLabel(specimen.getTitleCache());
217 container.addItem(dto);
218 }
219 if (container.getCount() > 0 ) {
220 result.setSpecimens(container);
221 }
222 }
223
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);
230 }
231 }
232 return result;
233 }
234
235 private void loadMedia(Taxon taxon, TaxonPageDto result, TaxonPageDtoConfiguration config) {
236
237 ContainerDto<MediaDTO> container = new ContainerDto<TaxonPageDto.MediaDTO>();
238
239 List<Media> medias = new ArrayList<>();
240 for (TaxonDescription taxonDescription : taxon.getDescriptions()) {
241 if (!taxonDescription.isImageGallery()) {
242 continue;
243 }
244
245 List<Media> newMedia = taxonDescription.getElements().stream()
246 .filter(el->el.isInstanceOf(TextData.class))
247 .map(el->CdmBase.deproxy(el, TextData.class))
248 .filter(td->true)
249 .flatMap(td->td.getMedia().stream())
250 .collect(Collectors.toList())
251 ;
252 medias.addAll(newMedia);
253 }
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());
275 }
276 //TODO AudioFile etc.
277 }
278 representations.addItem(repDto);
279 }
280 if (representations.getCount() > 0) {
281 dto.setRepresentations(representations);
282 }
283 //TODO load representation data
284 container.addItem(dto);
285 }
286
287 if (container.getCount() > 0) {
288 result.setMedia(container);
289 }
290
291 }
292
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);
298 //classification
299 Classification classification = node.getClassification();
300 if (classification != null) {
301 dto.setClassificationUuid(node.getClassification().getUuid());
302 dto.setClassificationLabel(classification.getName().getText());
303 }
304 //TODO lang/locale
305 Language language = Language.DEFAULT();
306
307 //status
308 TaxonNodeStatus status = node.getStatus();
309 if (status != null) {
310 dto.setStatus(status.getLabel(language));
311 }
312 //statusNote
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();
319 }
320 if (statusNoteStr != null) {
321 dto.setStatusNote(statusNoteStr.getText());
322 }
323 }
324 //agent relations
325 Set<TaxonNodeAgentRelation> agents = node.getAgentRelations();
326 if (!agents.isEmpty()) {
327 for (TaxonNodeAgentRelation rel : agents) {
328 TaxonNodeAgentsRelDTO agentDto = new TaxonNodeAgentsRelDTO();
329 loadBaseData(rel, agentDto);
330
331 //TODO laod
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);
337 }
338 if (rel.getType() != null) {
339 agentDto.setType(rel.getType().getTitleCache());
340 agentDto.setTypeUuid(rel.getType().getUuid());
341 }
342 dto.addAgent(agentDto);
343 }
344 }
345 container.addItem(dto);
346 }
347 if (container.getCount() > 0) {
348 result.setTaxonNodes(container);
349 }
350
351 }
352
353
354 private void loadSynonyms(Taxon taxon, TaxonPageDto result, TaxonPageDtoConfiguration config) {
355 // List<HomotypicalGroup> homotypicGroups = taxon.getHomotypicSynonymyGroups();
356
357 TaxonComparator comparator = new TaxonComparator();
358
359 TaxonName name = taxon.getName();
360
361 //TODO depending on config add/remove accepted name
362
363 //TODO check publish flag
364
365 //homotypic synonyms
366 List<Synonym> homotypicSynonmys = taxon.getHomotypicSynonymsByHomotypicGroup(comparator);
367 TaxonPageDto.HomotypicGroupDTO homotypicGroupDto = new TaxonPageDto.HomotypicGroupDTO();
368 if (!homotypicSynonmys.isEmpty()) {
369 loadBaseData(name.getHomotypicalGroup(), homotypicGroupDto);
370
371 for (Synonym syn : homotypicSynonmys) {
372 loadSynonymsInGroup(homotypicGroupDto, syn, config);
373 }
374 }
375 //TODO NPE
376 handleTypification(name.getHomotypicalGroup(), homotypicGroupDto, result, config);
377 result.setHomotypicSynonyms(homotypicGroupDto);
378
379 //heterotypic synonyms
380 List<HomotypicalGroup> heteroGroups = taxon.getHeterotypicSynonymyGroups();
381 if (heteroGroups.isEmpty()) {
382 return;
383 }
384 ContainerDto<HomotypicGroupDTO> heteroContainer = new ContainerDto<>();
385 result.setHeterotypicSynonymGroups(heteroContainer);
386
387 for (HomotypicalGroup hg : heteroGroups) {
388 TaxonPageDto.HomotypicGroupDTO hgDto = new TaxonPageDto.HomotypicGroupDTO();
389 loadBaseData(taxon.getName().getHomotypicalGroup(), hgDto);
390 heteroContainer.addItem(hgDto);
391
392 List<Synonym> heteroSyns = taxon.getSynonymsInGroup(hg, comparator);
393 for (Synonym syn : heteroSyns) {
394 loadSynonymsInGroup(hgDto, syn, config);
395 }
396 handleTypification(hg, hgDto, result, config);
397 }
398 }
399
400 private void handleTypification(HomotypicalGroup homotypicalGroup, HomotypicGroupDTO hgDto,
401 TaxonPageDto result, TaxonPageDtoConfiguration config) {
402
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();
409 try {
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);
416
417 } catch (TypeDesignationSetException e) {
418 result.addMessage(new MessagesDto(MessageType.ERROR, "Error when creating type designation information"));
419 }
420 }
421
422 private void loadConceptRelations(Taxon taxon, TaxonPageDto result, TaxonPageDtoConfiguration config) {
423
424 //concept relations
425 ContainerDto<ConceptRelationDTO> conceptRelContainer = new ContainerDto<>();
426 TaxonRelationshipFormatter taxRelFormatter = TaxonRelationshipFormatter.INSTANCE();
427
428 //... MAN
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);
434 }
435
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);
442 }
443
444 //TODO MAN and pp from this taxon
445
446 //... to-relations
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);
452 }
453
454 //... from-relations
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);
460 }
461
462 if (conceptRelContainer.getCount() > 0) {
463 result.setConceptRelations(conceptRelContainer);
464 }
465 }
466
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);
480 }
481
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));
488 //TODO
489 hgDto.addSynonym(synDto);
490 }
491
492 private void loadFacts(Taxon taxon, TaxonPageDto taxonPageDto, TaxonPageDtoConfiguration config) {
493
494 //compute the feature that do exist for this taxon
495 Map<UUID, Feature> existingFeatureUuids = getExistingFeatureUuids(taxon);
496
497 //evaluate feature tree if it exists
498 TreeNode<Feature, UUID> filteredRootNode;
499 if (config.getFeatureTree() != null) {
500
501 //TODO class cast
502 TermTree<Feature> featureTree = repository.getTermTreeService().find(config.getFeatureTree());
503 filteredRootNode = filterFeatureNode(featureTree.getRoot(), existingFeatureUuids.keySet());
504 } else {
505 filteredRootNode = createDefaultFeatureNode(taxon);
506 }
507
508 //load facts per feature
509 Map<UUID,Set<DescriptionElementBase>> featureMap = loadfeatureMap(taxon);
510
511 //load final result
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);
516 }
517 taxonPageDto.setFactualData(features);
518 }
519 }
520
521 private void handleFeatureNode(Taxon taxon, TaxonPageDtoConfiguration config,
522 Map<UUID, Set<DescriptionElementBase>> featureMap, ContainerDto<FeatureDto> features,
523 TreeNode<Feature, UUID> node) {
524
525 Feature feature = node.getData();
526 //TODO locale
527 FeatureDto featureDto = new FeatureDto(feature.getUuid(), feature.getId(), feature.getLabel());
528 features.addItem(featureDto);
529
530 List<Distribution> distributions = new ArrayList<>();
531 //
532 for (DescriptionElementBase fact : featureMap.get(feature.getUuid())){
533 if (fact.isInstanceOf(Distribution.class)) {
534 distributions.add(CdmBase.deproxy(fact, Distribution.class));
535 }else {
536 handleFact(featureDto, fact);
537 }
538 }
539
540 handleDistributions(config, featureDto, taxon, distributions);
541
542 //children
543 ContainerDto<FeatureDto> childFeatures = new ContainerDto<>();
544 for (TreeNode<Feature,UUID> child : node.getChildren()) {
545 handleFeatureNode(taxon, config, featureMap, childFeatures, child);
546 }
547 if (childFeatures.getCount() > 0) {
548 featureDto.setSubFeatures(childFeatures);
549 }
550 }
551
552 private Map<UUID, Set<DescriptionElementBase>> loadfeatureMap(Taxon taxon) {
553 Map<UUID, Set<DescriptionElementBase>> featureMap = new HashMap<>();
554
555 //... load facts
556 for (TaxonDescription taxonDescription : taxon.getDescriptions()) {
557 if (taxonDescription.isImageGallery()) {
558 continue;
559 }
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<>());
564 }
565 featureMap.get(feature.getUuid()).add(deb);
566 }
567 }
568 return featureMap;
569 }
570
571 private TreeNode<Feature, UUID> createDefaultFeatureNode(Taxon taxon) {
572 TreeNode<Feature, UUID> root = new TreeNode<>();
573 Set<Feature> requiredFeatures = new HashSet<>();
574
575 for (TaxonDescription taxonDescription : taxon.getDescriptions()) {
576 if (taxonDescription.isImageGallery()) {
577 continue;
578 }
579 for (DescriptionElementBase deb : taxonDescription.getElements()) {
580 Feature feature = deb.getFeature();
581 if (feature != null) { //null should not happen
582 requiredFeatures.add(feature);
583 }
584 }
585 }
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)));
589 return root;
590 }
591
592 /**
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>
597 */
598 private TreeNode<Feature, UUID> filterFeatureNode(TermNode<Feature> featureNode,
599 Set<UUID> existingFeatureUuids) {
600
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);
605 if (child != null) {
606 requiredChildNodes.add(child);
607 }
608 }
609
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);
619 }
620 //add child data
621 requiredChildNodes.stream().forEachOrdered(c->result.addChild(c));
622 return result;
623 }else {
624 return null;
625 }
626 }
627
628 /**
629 * Computes the (unsorted) set of features for which facts exist
630 * for the given taxon.
631 */
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()) {
636 continue;
637 }
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);
642 }
643 }
644 }
645 return result;
646 }
647
648 private void handleDistributions(TaxonPageDtoConfiguration config, FeatureDto featureDto,
649 Taxon taxon, List<Distribution> distributions) {
650
651 if (distributions.isEmpty()) {
652 return;
653 }
654 DistributionInfoConfiguration distributionConfig = config.getDistributionInfoConfiguration();
655
656 CondensedDistributionConfiguration condensedConfig = distributionConfig.getCondensedDistrConfig();
657 String statusColorsString = distributionConfig.getStatusColorsString();
658
659 IDistributionService distributionService = repository.getDistributionService();
660
661 //copied from DescriptionListController
662
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
666
667 DistributionInfoDto dto;
668
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());
674 }
675
676 List<String> initStrategy = null;
677
678 Map<PresenceAbsenceTerm, Color> distributionStatusColors;
679 try {
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;
686 }
687
688 dto = distributionService.composeDistributionInfoFor(distributionConfig, taxon.getUuid(),
689 fallbackAsParent,
690 distributionStatusColors, LocaleContext.getLanguages(),
691 initStrategy);
692
693 featureDto.addFact(dto);
694 }
695
696 private void handleFact(FeatureDto featureDto, DescriptionElementBase fact) {
697 //TODO locale
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());
703
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);
711 //TODO
712 }else if (fact.isInstanceOf(CommonTaxonName.class)) {
713 CommonTaxonName ctn = CdmBase.deproxy(fact, CommonTaxonName.class);
714 CommonNameDto dto = new CommonNameDto();
715 featureDto.addFact(dto);
716
717 Language lang = ctn.getLanguage();
718 if (lang != null) {
719 String langLabel = getTermLabel(lang, localeLang);
720 dto.setLanguage(langLabel);
721 dto.setLanguageUuid(lang.getUuid());
722 }else {
723 //TODO
724 dto.setLanguage("-");
725 }
726 //area
727 NamedArea area = ctn.getArea();
728 if (area != null) {
729 String areaLabel = getTermLabel(area, localeLang);
730 dto.setArea(areaLabel);
731 dto.setAreaUUID(area.getUuid());
732 }
733 dto.setName(ctn.getName());
734 loadBaseData(ctn, dto);
735 //TODO sort all common names
736
737 } else if (fact.isInstanceOf(IndividualsAssociation.class)) {
738 IndividualsAssociation ia = CdmBase.deproxy(fact, IndividualsAssociation.class);
739 IndividualsAssociationDto dto = new IndividualsAssociationDto ();
740
741 LanguageString description = MultilanguageTextHelper.getPreferredLanguageString(ia.getDescription(), Arrays.asList(localeLang));
742 if (description != null) {
743 dto.setDescritpion(description.getText());
744 }
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());
750 }
751
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 ();
757
758 LanguageString description = MultilanguageTextHelper.getPreferredLanguageString(
759 ti.getDescription(), Arrays.asList(localeLang));
760 if (description != null) {
761 dto.setDescritpion(description.getText());
762 }
763 Taxon taxon = ti.getTaxon2();
764 if (taxon != null) {
765 //TODO what to use here??
766 dto.setTaxon(taxon.cacheStrategy().getTaggedTitle(taxon));
767 dto.setTaxonUuid(taxon.getUuid());
768 }
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);
780 //TODO
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);
791 //TODO
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);
802 //TODO
803 loadBaseData(td, factDto);
804 }else {
805 // TODO
806 logger.warn("DescriptionElement type not yet handled: " + fact.getClass().getSimpleName());
807 }
808
809 }
810
811 private String getTermLabel(TermBase term, Language localeLang) {
812 if (term == null) {
813 return null;
814 }
815 Representation rep = term.getPreferredRepresentation(localeLang);
816 String label = rep == null ? null : rep.getLabel();
817 label = label == null ? term.getLabel() : label;
818 return label;
819 }
820
821 /**
822 * Compares an existing last date and the last date of an entity
823 * and returns the resulting last date.
824 */
825 private LocalDateTime getLastUpdated(LocalDateTime existingLastDate, VersionableEntity dateToAddEntity) {
826
827 DateTime dateToAdd = dateToAddEntity.getUpdated() != null ? dateToAddEntity.getUpdated() : dateToAddEntity.getCreated();
828
829 LocalDateTime javaLocalDateTimeOfEntity = dateToAdd == null ? null:
830 LocalDateTime.of(dateToAdd.getYear(), dateToAdd.getMonthOfYear(),
831 dateToAdd.getDayOfMonth(), dateToAdd.getHourOfDay(),
832 dateToAdd.getMinuteOfHour(), dateToAdd.getSecondOfMinute());
833
834 if (existingLastDate == null) {
835 return javaLocalDateTimeOfEntity;
836 }else if (javaLocalDateTimeOfEntity == null || javaLocalDateTimeOfEntity.compareTo(existingLastDate) < 0) {
837 return existingLastDate;
838 }else {
839 return javaLocalDateTimeOfEntity;
840 }
841 }
842
843 private void loadBaseData(CdmBase cdmBase, CdmBaseDto dto) {
844 dto.setId(cdmBase.getId());
845 dto.setUuid(cdmBase.getUuid());
846
847 loadAnnotatable(cdmBase, dto);
848 loadSources(cdmBase, dto);
849 //loadIdentifiable(cdmBase, dto);
850 }
851
852 private void loadSources(CdmBase cdmBase, CdmBaseDto dto) {
853 if (dto instanceof SingleSourcedDto && cdmBase.isInstanceOf(SingleSourcedEntityBase.class)) {
854 //TODO other sourced
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);
869 }
870 }
871 }
872
873 private void loadSource(OriginalSourceBase source, SourceDto sourceDto) {
874
875 //base data
876 loadBaseData(source, sourceDto);
877
878 ICdmBase linkedObject = source.getCitation();
879 if (linkedObject == null) {
880 //cdmsource
881 linkedObject = source.getCdmSource();
882 }
883
884 //citation uuid & doi
885 if (source.getCitation()!= null) {
886 sourceDto.setDoi(source.getCitation().getDoiString());
887 }
888
889 //label
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);
896
897 if (source.isInstanceOf(NamedSourceBase.class)) {
898 NamedSourceBase ns = CdmBase.deproxy(source, NamedSourceBase.class);
899
900 //nameUsedInSource
901 TaxonName name = ns.getNameUsedInSource();
902 if (name != null) {
903 List<TaggedText> taggedName = name.cacheStrategy().getTaggedTitle(name);
904 //TODO nom status?
905 sourceDto.setNameInSource(taggedName);
906 }
907
908 //specimen uuid
909 if (source.isInstanceOf(DescriptionElementSource.class)) {
910 DescriptionElementSource des = CdmBase.deproxy(source, DescriptionElementSource.class);
911 if (linkedObject == null) {
912 linkedObject = des.getSpecimen();
913 }
914 }
915 }
916
917 sourceDto.setLinkedUuid(getUuid(linkedObject));
918 String linkedObjectStr = linkedObject == null ? null : CdmBase.deproxy(linkedObject).getClass().getSimpleName();
919 sourceDto.setLinkedClass(linkedObjectStr);
920 }
921
922 private UUID getUuid(ICdmBase cdmBase) {
923 return cdmBase == null ? null : cdmBase.getUuid();
924 }
925
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;
930 //annotation
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())) {
936
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
945 }
946 }
947
948 //marker
949 for (Marker marker : annotatable.getMarkers()) {
950 if (marker.getMarkerType() != null
951 //TODO markertype filter
952 // && marker.getMarkerType().getUuid().equals(AnnotationType.uuidEditorial)
953 ){
954
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());
961 //TODO locale
962 markerDto.setType(marker.getMarkerType().getTitleCache());
963 }
964 markerDto.setValue(marker.getValue());
965 }
966 }
967 }
968 }
969 }