ref #10222 add tagged text for synonyms
[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.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;
17 import java.util.Map;
18 import java.util.Set;
19 import java.util.stream.Collectors;
20
21 import org.joda.time.DateTime;
22
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;
77
78 /**
79 * Loads the portal dto from a taxon instance.
80 * Maybe later also supports loading from persistence.
81 *
82 * @author a.mueller
83 * @date 09.01.2023
84 */
85 public class PortalDtoLoader {
86
87 public TaxonPageDto load(Taxon taxon, TaxonPageDtoConfiguration config) {
88 TaxonPageDto result = new TaxonPageDto();
89
90 TaxonName name = taxon.getName();
91
92 //load 1:1
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));
99
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);
107
108 return result;
109 }
110
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);
115 return tags;
116 }
117
118 private void loadKeys(Taxon taxon, TaxonPageDto result, TaxonPageDtoConfiguration config) {
119 ContainerDto<KeyDTO> container =new ContainerDto<>();
120 //TODO
121
122 if (container.getCount() > 0) {
123 result.setKeys(container);
124 }
125 }
126
127 private void loadSpecimens(Taxon taxon, TaxonPageDto result, TaxonPageDtoConfiguration config) {
128 //TODO load specimen from multiple places
129
130 ContainerDto<SpecimenDTO> container = new ContainerDto<TaxonPageDto.SpecimenDTO>();
131
132 List<SpecimenOrObservationBase<?>> specimens = new ArrayList<>();
133 for (TaxonDescription taxonDescription : taxon.getDescriptions()) {
134 if (taxonDescription.isImageGallery()) {
135 continue;
136 }
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);
142 }
143 }
144 }
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);
150 }
151
152 for (SpecimenOrObservationBase<?> specimen : specimens) {
153 SpecimenDTO dto = new SpecimenDTO();
154 loadBaseData(specimen, dto);
155 dto.setLabel(specimen.getTitleCache());
156 container.addItem(dto);
157 }
158 if (container.getCount() > 0 ) {
159 result.setSpecimens(container);
160 }
161
162 }
163
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);
170 }
171 }
172 return result;
173 }
174
175 private void loadMedia(Taxon taxon, TaxonPageDto result, TaxonPageDtoConfiguration config) {
176
177 ContainerDto<MediaDTO> container = new ContainerDto<TaxonPageDto.MediaDTO>();
178
179 List<Media> medias = new ArrayList<>();
180 for (TaxonDescription taxonDescription : taxon.getDescriptions()) {
181 if (!taxonDescription.isImageGallery()) {
182 continue;
183 }
184
185 List<Media> newMedia = taxonDescription.getElements().stream()
186 .filter(el->el.isInstanceOf(TextData.class))
187 .map(el->CdmBase.deproxy(el, TextData.class))
188 .filter(td->true)
189 .flatMap(td->td.getMedia().stream())
190 .collect(Collectors.toList())
191 ;
192 medias.addAll(newMedia);
193 }
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());
215 }
216 //TODO AudioFile etc.
217 }
218 representations.addItem(repDto);
219 }
220 if (representations.getCount() > 0) {
221 dto.setRepresentations(representations);
222 }
223 //TODO load representation data
224 container.addItem(dto);
225 }
226
227 if (container.getCount() > 0) {
228 result.setMedia(container);
229 }
230
231 }
232
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);
238 //classification
239 Classification classification = node.getClassification();
240 if (classification != null) {
241 dto.setClassificationUuid(node.getClassification().getUuid());
242 dto.setClassificationLabel(classification.getName().getText());
243 }
244 //TODO lang/locale
245 Language language = Language.DEFAULT();
246
247 //status
248 TaxonNodeStatus status = node.getStatus();
249 if (status != null) {
250 dto.setStatus(status.getLabel(language));
251 }
252 //statusNote
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();
259 }
260 if (statusNoteStr != null) {
261 dto.setStatusNote(statusNoteStr.getText());
262 }
263 }
264 //agent relations
265 Set<TaxonNodeAgentRelation> agents = node.getAgentRelations();
266 if (!agents.isEmpty()) {
267 for (TaxonNodeAgentRelation rel : agents) {
268 TaxonNodeAgentsRelDTO agentDto = new TaxonNodeAgentsRelDTO();
269 loadBaseData(rel, agentDto);
270
271 //TODO laod
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);
277 }
278 if (rel.getType() != null) {
279 agentDto.setType(rel.getType().getTitleCache());
280 agentDto.setTypeUuid(rel.getType().getUuid());
281 }
282 dto.addAgent(agentDto);
283 }
284 }
285 container.addItem(dto);
286 }
287 if (container.getCount() > 0) {
288 result.setTaxonNodes(container);
289 }
290
291 }
292
293
294 private void loadSynonyms(Taxon taxon, TaxonPageDto result, TaxonPageDtoConfiguration config) {
295 // List<HomotypicalGroup> homotypicGroups = taxon.getHomotypicSynonymyGroups();
296
297 TaxonComparator comparator = new TaxonComparator();
298
299 TaxonName name = taxon.getName();
300
301 //TODO depending on config add/remove accepted name
302
303 //homotypic synonyms
304 List<Synonym> homotypicSynonmys = taxon.getHomotypicSynonymsByHomotypicGroup(comparator);
305 TaxonPageDto.HomotypicGroupDTO homotypicGroupDto = new TaxonPageDto.HomotypicGroupDTO();
306 if (!homotypicSynonmys.isEmpty()) {
307 loadBaseData(name.getHomotypicalGroup(), homotypicGroupDto);
308
309 for (Synonym syn : homotypicSynonmys) {
310 loadSynonymsInGroup(homotypicGroupDto, syn, config);
311 }
312 }
313 //TODO NPE
314 handleTypification(name.getHomotypicalGroup(), homotypicGroupDto, result, config);
315 result.setHomotypicSynonyms(homotypicGroupDto);
316
317 //heterotypic synonyms
318 List<HomotypicalGroup> heteroGroups = taxon.getHeterotypicSynonymyGroups();
319 if (heteroGroups.isEmpty()) {
320 return;
321 }
322 ContainerDto<HomotypicGroupDTO> heteroContainer = new ContainerDto<>();
323 result.setHeterotypicSynonymGroups(heteroContainer);
324
325 for (HomotypicalGroup hg : heteroGroups) {
326 TaxonPageDto.HomotypicGroupDTO hgDto = new TaxonPageDto.HomotypicGroupDTO();
327 loadBaseData(taxon.getName().getHomotypicalGroup(), hgDto);
328 heteroContainer.addItem(hgDto);
329
330 List<Synonym> heteroSyns = taxon.getSynonymsInGroup(hg, comparator);
331 for (Synonym syn : heteroSyns) {
332 loadSynonymsInGroup(hgDto, syn, config);
333 }
334 handleTypification(hg, hgDto, result, config);
335 }
336 }
337
338 private void handleTypification(HomotypicalGroup homotypicalGroup, HomotypicGroupDTO hgDto,
339 TaxonPageDto result, TaxonPageDtoConfiguration config) {
340
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();
347 try {
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);
353
354 } catch (TypeDesignationSetException e) {
355 result.addMessage(new MessagesDto(MessageType.ERROR, "Error when creating type designation information"));
356 }
357 }
358
359 private void loadConceptRelations(Taxon taxon, TaxonPageDto result, TaxonPageDtoConfiguration config) {
360
361 //concept relations
362 ContainerDto<ConceptRelationDTO> conceptRelContainer = new ContainerDto<>();
363 TaxonRelationshipFormatter taxRelFormatter = TaxonRelationshipFormatter.INSTANCE();
364
365 //... MAN
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);
371 }
372
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);
379 }
380
381 //TODO MAN and pp from this taxon
382
383 //... to-relations
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);
389 }
390
391 //... from-relations
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);
397 }
398
399 if (conceptRelContainer.getCount() > 0) {
400 result.setConceptRelations(conceptRelContainer);
401 }
402 }
403
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);
417 }
418
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));
425 //TODO
426 hgDto.addSynonym(synDto);
427 }
428
429 private void loadFacts(Taxon taxon, TaxonPageDto result, TaxonPageDtoConfiguration config) {
430
431 //TODO load feature tree
432 Map<Feature,Set<DescriptionElementBase>> featureMap = new HashMap<>();
433
434 //load facts
435 for (TaxonDescription taxonDescription : taxon.getDescriptions()) {
436 if (taxonDescription.isImageGallery()) {
437 continue;
438 }
439 for (DescriptionElementBase deb : taxonDescription.getElements()) {
440 Feature feature = deb.getFeature();
441 if (featureMap.get(feature) == null) {
442 featureMap.put(feature, new HashSet<>());
443 }
444 featureMap.get(feature).add(deb);
445 }
446 }
447
448 //TODO sort
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());
456 //TODO locale
457 featureDto.setLabel(feature.getTitleCache());
458 features.addItem(featureDto);
459
460 //
461 for (DescriptionElementBase fact : featureMap.get(feature)){
462 handleFact(featureDto, fact);
463 }
464 }
465 }
466 }
467
468 private void handleFact(FeatureDto featureDto, DescriptionElementBase fact) {
469 if (fact.isInstanceOf(TextData.class)) {
470 TextData td = CdmBase.deproxy(fact, TextData.class);
471 //TODO locale
472 Language lang = null;
473 LanguageString ls = td.getPreferredLanguageString(lang);
474 String text = ls == null ? "" : CdmUtils.Nz(ls.getText());
475
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);
482 }else {
483 // TODO
484 }
485
486 }
487
488 /**
489 * Compares an existing last date and the last date of an entity
490 * and returns the resulting last date.
491 */
492 private LocalDateTime getLastUpdated(LocalDateTime existingLastDate, VersionableEntity dateToAddEntity) {
493
494 DateTime dateToAdd = dateToAddEntity.getUpdated() != null ? dateToAddEntity.getUpdated() : dateToAddEntity.getCreated();
495
496 LocalDateTime javaLocalDateTimeOfEntity = dateToAdd == null ? null:
497 LocalDateTime.of(dateToAdd.getYear(), dateToAdd.getMonthOfYear(),
498 dateToAdd.getDayOfMonth(), dateToAdd.getHourOfDay(),
499 dateToAdd.getMinuteOfHour(), dateToAdd.getSecondOfMinute());
500
501 if (existingLastDate == null) {
502 return javaLocalDateTimeOfEntity;
503 }else if (javaLocalDateTimeOfEntity == null || javaLocalDateTimeOfEntity.compareTo(existingLastDate) < 0) {
504 return existingLastDate;
505 }else {
506 return javaLocalDateTimeOfEntity;
507 }
508 }
509
510 private void loadBaseData(CdmBase cdmBase, CdmBaseDto dto) {
511 dto.setId(cdmBase.getId());
512 dto.setUuid(cdmBase.getUuid());
513 }
514
515 }