Project

General

Profile

Download (55.2 KB) Statistics
| Branch: | Tag: | Revision:
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.DistributionDto;
38
import eu.etaxonomy.cdm.api.dto.portal.DistributionInfoDto;
39
import eu.etaxonomy.cdm.api.dto.portal.DistributionTreeDto;
40
import eu.etaxonomy.cdm.api.dto.portal.FactDto;
41
import eu.etaxonomy.cdm.api.dto.portal.FactDtoBase;
42
import eu.etaxonomy.cdm.api.dto.portal.FeatureDto;
43
import eu.etaxonomy.cdm.api.dto.portal.IFactDto;
44
import eu.etaxonomy.cdm.api.dto.portal.IndividualsAssociationDto;
45
import eu.etaxonomy.cdm.api.dto.portal.MarkerDto;
46
import eu.etaxonomy.cdm.api.dto.portal.MessagesDto;
47
import eu.etaxonomy.cdm.api.dto.portal.NamedAreaDto;
48
import eu.etaxonomy.cdm.api.dto.portal.SingleSourcedDto;
49
import eu.etaxonomy.cdm.api.dto.portal.SourceDto;
50
import eu.etaxonomy.cdm.api.dto.portal.SourcedDto;
51
import eu.etaxonomy.cdm.api.dto.portal.TaxonBaseDto;
52
import eu.etaxonomy.cdm.api.dto.portal.TaxonInteractionDto;
53
import eu.etaxonomy.cdm.api.dto.portal.TaxonPageDto;
54
import eu.etaxonomy.cdm.api.dto.portal.TaxonPageDto.ConceptRelationDTO;
55
import eu.etaxonomy.cdm.api.dto.portal.TaxonPageDto.HomotypicGroupDTO;
56
import eu.etaxonomy.cdm.api.dto.portal.TaxonPageDto.KeyDTO;
57
import eu.etaxonomy.cdm.api.dto.portal.TaxonPageDto.MediaDTO;
58
import eu.etaxonomy.cdm.api.dto.portal.TaxonPageDto.MediaRepresentationDTO;
59
import eu.etaxonomy.cdm.api.dto.portal.TaxonPageDto.NameRelationDTO;
60
import eu.etaxonomy.cdm.api.dto.portal.TaxonPageDto.SpecimenDTO;
61
import eu.etaxonomy.cdm.api.dto.portal.TaxonPageDto.TaxonNodeAgentsRelDTO;
62
import eu.etaxonomy.cdm.api.dto.portal.TaxonPageDto.TaxonNodeDTO;
63
import eu.etaxonomy.cdm.api.dto.portal.config.DistributionInfoConfiguration;
64
import eu.etaxonomy.cdm.api.dto.portal.config.TaxonPageDtoConfiguration;
65
import eu.etaxonomy.cdm.api.service.geo.DistributionServiceUtilities;
66
import eu.etaxonomy.cdm.api.service.geo.IDistributionService;
67
import eu.etaxonomy.cdm.api.service.l10n.LocaleContext;
68
import eu.etaxonomy.cdm.api.service.name.TypeDesignationSetContainer;
69
import eu.etaxonomy.cdm.api.service.name.TypeDesignationSetFormatter;
70
import eu.etaxonomy.cdm.api.service.pager.Pager;
71
import eu.etaxonomy.cdm.common.CdmUtils;
72
import eu.etaxonomy.cdm.common.TreeNode;
73
import eu.etaxonomy.cdm.compare.taxon.TaxonComparator;
74
import eu.etaxonomy.cdm.format.common.TypedLabel;
75
import eu.etaxonomy.cdm.format.description.CategoricalDataFormatter;
76
import eu.etaxonomy.cdm.format.description.QuantitativeDataFormatter;
77
import eu.etaxonomy.cdm.format.description.distribution.CondensedDistributionConfiguration;
78
import eu.etaxonomy.cdm.format.reference.OriginalSourceFormatter;
79
import eu.etaxonomy.cdm.format.taxon.TaxonRelationshipFormatter;
80
import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
81
import eu.etaxonomy.cdm.model.common.Annotation;
82
import eu.etaxonomy.cdm.model.common.AnnotationType;
83
import eu.etaxonomy.cdm.model.common.CdmBase;
84
import eu.etaxonomy.cdm.model.common.ICdmBase;
85
import eu.etaxonomy.cdm.model.common.Language;
86
import eu.etaxonomy.cdm.model.common.LanguageString;
87
import eu.etaxonomy.cdm.model.common.Marker;
88
import eu.etaxonomy.cdm.model.common.MarkerType;
89
import eu.etaxonomy.cdm.model.common.MultilanguageTextHelper;
90
import eu.etaxonomy.cdm.model.common.SingleSourcedEntityBase;
91
import eu.etaxonomy.cdm.model.common.VersionableEntity;
92
import eu.etaxonomy.cdm.model.description.CategoricalData;
93
import eu.etaxonomy.cdm.model.description.CommonTaxonName;
94
import eu.etaxonomy.cdm.model.description.DescriptionBase;
95
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
96
import eu.etaxonomy.cdm.model.description.DescriptionElementSource;
97
import eu.etaxonomy.cdm.model.description.Distribution;
98
import eu.etaxonomy.cdm.model.description.Feature;
99
import eu.etaxonomy.cdm.model.description.IDescribable;
100
import eu.etaxonomy.cdm.model.description.IndividualsAssociation;
101
import eu.etaxonomy.cdm.model.description.PolytomousKey;
102
import eu.etaxonomy.cdm.model.description.PresenceAbsenceTerm;
103
import eu.etaxonomy.cdm.model.description.QuantitativeData;
104
import eu.etaxonomy.cdm.model.description.TaxonDescription;
105
import eu.etaxonomy.cdm.model.description.TaxonInteraction;
106
import eu.etaxonomy.cdm.model.description.TemporalData;
107
import eu.etaxonomy.cdm.model.description.TextData;
108
import eu.etaxonomy.cdm.model.location.NamedArea;
109
import eu.etaxonomy.cdm.model.media.ExternalLink;
110
import eu.etaxonomy.cdm.model.media.ImageFile;
111
import eu.etaxonomy.cdm.model.media.Media;
112
import eu.etaxonomy.cdm.model.media.MediaRepresentation;
113
import eu.etaxonomy.cdm.model.media.MediaRepresentationPart;
114
import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
115
import eu.etaxonomy.cdm.model.name.NameRelationship;
116
import eu.etaxonomy.cdm.model.name.NameRelationshipType;
117
import eu.etaxonomy.cdm.model.name.NomenclaturalSource;
118
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
119
import eu.etaxonomy.cdm.model.name.TaxonName;
120
import eu.etaxonomy.cdm.model.name.TypeDesignationBase;
121
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
122
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
123
import eu.etaxonomy.cdm.model.reference.ISourceable;
124
import eu.etaxonomy.cdm.model.reference.NamedSource;
125
import eu.etaxonomy.cdm.model.reference.NamedSourceBase;
126
import eu.etaxonomy.cdm.model.reference.OriginalSourceBase;
127
import eu.etaxonomy.cdm.model.reference.Reference;
128
import eu.etaxonomy.cdm.model.taxon.Classification;
129
import eu.etaxonomy.cdm.model.taxon.Synonym;
130
import eu.etaxonomy.cdm.model.taxon.Taxon;
131
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
132
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
133
import eu.etaxonomy.cdm.model.taxon.TaxonNodeAgentRelation;
134
import eu.etaxonomy.cdm.model.taxon.TaxonNodeStatus;
135
import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
136
import eu.etaxonomy.cdm.model.term.Representation;
137
import eu.etaxonomy.cdm.model.term.TermBase;
138
import eu.etaxonomy.cdm.model.term.TermNode;
139
import eu.etaxonomy.cdm.model.term.TermTree;
140
import eu.etaxonomy.cdm.strategy.cache.TaggedCacheHelper;
141
import eu.etaxonomy.cdm.strategy.cache.TaggedText;
142
import eu.etaxonomy.cdm.strategy.cache.taxon.TaxonBaseDefaultCacheStrategy;
143

    
144
/**
145
 * Loads the portal dto from a taxon instance.
146
 * Maybe later also supports loading from persistence.
147
 *
148
 * @author a.mueller
149
 * @date 09.01.2023
150
 */
151
public class PortalDtoLoader {
152

    
153
    private static final Logger logger = LogManager.getLogger();
154

    
155
    private ICdmRepository repository;
156

    
157
    public PortalDtoLoader(ICdmRepository repository) {
158
        this.repository = repository;
159
    }
160

    
161
    public TaxonPageDto load(Taxon taxon, TaxonPageDtoConfiguration config) {
162
        TaxonPageDto result = new TaxonPageDto();
163

    
164
        loadAcceptedTaxon(taxon, config, result);
165

    
166
        loadTaxonNodes(taxon, result, config);
167
        loadSynonyms(taxon, result, config);
168
        loadConceptRelations(taxon, result, config);
169
        loadFacts(taxon, result, config);
170
        loadMedia(taxon, result, config);
171
        loadSpecimens(taxon, result, config);
172
        loadKeys(taxon, result, config);
173

    
174
        return result;
175
    }
176

    
177
    private void loadAcceptedTaxon(Taxon taxon, TaxonPageDtoConfiguration config, TaxonPageDto result) {
178
        try {
179
            TaxonName name = taxon.getName();
180

    
181
            //load 1:1
182
            //TODO supplementalData for name
183
            loadBaseData(taxon, result);
184
            result.setLastUpdated(getLastUpdated(null, taxon));
185
            result.setLabel(CdmUtils.Nz(taxon.getTitleCache()));
186
//          result.setTypedTaxonLabel(getTypedTaxonLabel(taxon, config));
187
            result.setTaggedLabel(getTaggedTaxon(taxon, config));
188
            if (name != null) {
189
                handleName(config, result, name, result);
190
            }
191
            if (taxon.getSec() != null) {
192
                result.setSecTitleCache(taxon.getSec().getTitleCache());
193
            }
194
        } catch (Exception e) {
195
            //e.printStackTrace();
196
            result.addMessage(MessagesDto.NewErrorInstance("Error when loading accepted name data.", e));
197
        }
198
    }
199

    
200
    private void handleName(TaxonPageDtoConfiguration config, TaxonBaseDto nameDto, TaxonName name, TaxonPageDto pageDto) {
201
        nameDto.setNameLabel(name.getTitleCache());
202
        handleRelatedNames(name, nameDto, config);
203
        loadProtologues(name, nameDto);
204
        nameDto.setNameUuid(name.getUuid());
205
        nameDto.setNameType(name.getNameType().toString());
206
        loadNameFacts(name, nameDto, config, pageDto);
207
        nameDto.setTaggedName(name.getTaggedFullTitle());
208
    }
209

    
210
    private List<TaggedText> getTaggedTaxon(TaxonBase<?> taxon, TaxonPageDtoConfiguration config) {
211
//        List<TypedLabel> result = new ArrayList<>();
212
        TaxonBaseDefaultCacheStrategy<TaxonBase<?>> formatter = new TaxonBaseDefaultCacheStrategy<>();
213
        List<TaggedText> tags = formatter.getTaggedTitle(taxon);
214
        return tags;
215
    }
216

    
217
    private void loadKeys(Taxon taxon, TaxonPageDto result, TaxonPageDtoConfiguration config) {
218
        try {
219
            ContainerDto<KeyDTO> container = new ContainerDto<>();
220

    
221
            //TODO other key types, but type must not be null, otherwise NPE
222
            Pager<PolytomousKey> keys = repository.getIdentificationKeyService().findKeysConvering(taxon, PolytomousKey.class, null, null, null);
223
            for (PolytomousKey key : keys.getRecords()) {
224
                KeyDTO dto = new KeyDTO();
225
                loadBaseData(key, dto);
226
                dto.setLabel(key.getTitleCache());
227
                dto.setKeyClass(key.getClass().getSimpleName());
228
                container.addItem(dto);
229
            }
230
            if (container.getCount() > 0) {
231
                result.setKeys(container);
232
            }
233
        } catch (Exception e) {
234
            //e.printStackTrace();
235
            result.addMessage(MessagesDto.NewErrorInstance("Error when loading identification key data.", e));
236
        }
237
    }
238

    
239
    private void loadSpecimens(Taxon taxon, TaxonPageDto result, TaxonPageDtoConfiguration config) {
240
        //TODO load specimen from multiple places
241

    
242
        try {
243
            ContainerDto<SpecimenDTO> container = new ContainerDto<>();
244

    
245
            List<SpecimenOrObservationBase<?>> specimens = new ArrayList<>();
246
            for (TaxonDescription taxonDescription : taxon.getDescriptions()) {
247
                if (taxonDescription.isImageGallery()) {
248
                    continue;
249
                }
250
                for (DescriptionElementBase el : taxonDescription.getElements()) {
251
                    if (el.isInstanceOf(IndividualsAssociation.class)) {
252
                        IndividualsAssociation indAss = CdmBase.deproxy(el, IndividualsAssociation.class);
253
                        SpecimenOrObservationBase<?> specimen = indAss.getAssociatedSpecimenOrObservation();
254
                        specimens.add(specimen);
255
                    }
256
                }
257
            }
258
            List<SpecimenOrObservationBase<?>> typeSpecimens = loadTypeSpecimen(taxon.getName(), config);
259
            specimens.addAll(typeSpecimens);
260
            for (TaxonName syn : taxon.getSynonymNames()) {
261
                typeSpecimens = loadTypeSpecimen(syn, config);
262
                specimens.addAll(typeSpecimens);
263
            }
264

    
265
            for (SpecimenOrObservationBase<?> specimen : specimens) {
266
                SpecimenDTO dto = new SpecimenDTO();
267
                loadBaseData(specimen, dto);
268
                dto.setLabel(specimen.getTitleCache());
269
                container.addItem(dto);
270
            }
271
            if (container.getCount() > 0 ) {
272
                result.setSpecimens(container);
273
            }
274
        } catch (Exception e) {
275
            //e.printStackTrace();
276
            result.addMessage(MessagesDto.NewErrorInstance("Error when loading specimen data.", e));
277
        }
278
    }
279

    
280
    private List<SpecimenOrObservationBase<?>> loadTypeSpecimen(TaxonName name, TaxonPageDtoConfiguration config) {
281
        List<SpecimenOrObservationBase<?>> result = new ArrayList<>();
282
        for (SpecimenTypeDesignation desig: name.getSpecimenTypeDesignations()){
283
            DerivedUnit specimen = desig.getTypeSpecimen();
284
            if (specimen != null) {
285
                result.add(specimen);
286
            }
287
        }
288
        return result;
289
    }
290

    
291
    private void loadMedia(Taxon taxon, TaxonPageDto result, TaxonPageDtoConfiguration config) {
292

    
293
        try {
294
            ContainerDto<MediaDTO> container = new ContainerDto<TaxonPageDto.MediaDTO>();
295

    
296
            List<Media> medias = new ArrayList<>();
297
            for (TaxonDescription taxonDescription : taxon.getDescriptions()) {
298
                if (!taxonDescription.isImageGallery()) {
299
                    continue;
300
                }
301

    
302
                List<Media> newMedia = taxonDescription.getElements().stream()
303
                    .filter(el->el.isInstanceOf(TextData.class))
304
                    .map(el->CdmBase.deproxy(el, TextData.class))
305
                    .filter(td->true)
306
                    .flatMap(td->td.getMedia().stream())
307
                    .collect(Collectors.toList())
308
                    ;
309
                medias.addAll(newMedia);
310
            }
311
            //TODO collect media from elsewhere
312
            for (Media media : medias) {
313
                MediaDTO dto = new TaxonPageDto.MediaDTO();
314
                loadBaseData(media, dto);
315
                dto.setLabel(media.getTitleCache());
316
                ContainerDto<MediaRepresentationDTO> representations = new ContainerDto<>();
317
                for (MediaRepresentation rep : media.getRepresentations()) {
318
                    MediaRepresentationDTO repDto = new MediaRepresentationDTO();
319
                    loadBaseData(rep, dto);
320
                    repDto.setMimeType(rep.getMimeType());
321
                    repDto.setSuffix(rep.getSuffix());
322
                    if (!rep.getParts().isEmpty()) {
323
                        //TODO handle message if n(parts) > 1
324
                        MediaRepresentationPart part = rep.getParts().get(0);
325
                        repDto.setUri(part.getUri());
326
                        repDto.setClazz(part.getClass());
327
                        repDto.setSize(part.getSize());
328
                        if (part.isInstanceOf(ImageFile.class)) {
329
                            ImageFile image = CdmBase.deproxy(part, ImageFile.class);
330
                            repDto.setHeight(image.getHeight());
331
                            repDto.setWidth(image.getWidth());
332
                        }
333
                        //TODO AudioFile etc.
334
                    }
335
                    representations.addItem(repDto);
336
                }
337
                if (representations.getCount() > 0) {
338
                    dto.setRepresentations(representations);
339
                }
340
                //TODO load representation data
341
                container.addItem(dto);
342
            }
343

    
344
            if (container.getCount() > 0) {
345
                result.setMedia(container);
346
            }
347
        } catch (Exception e) {
348
            //e.printStackTrace();
349
            result.addMessage(MessagesDto.NewErrorInstance("Error when loading media data.", e));
350
        }
351
    }
352

    
353
    private void loadTaxonNodes(Taxon taxon, TaxonPageDto result, TaxonPageDtoConfiguration config) {
354
        try {
355
            ContainerDto<TaxonNodeDTO> container = new ContainerDto<TaxonPageDto.TaxonNodeDTO>();
356
            for (TaxonNode node : taxon.getTaxonNodes()) {
357
                TaxonNodeDTO dto = new TaxonNodeDTO();
358
                loadBaseData(node, dto);
359
                //classification
360
                Classification classification = node.getClassification();
361
                if (classification != null) {
362
                    dto.setClassificationUuid(node.getClassification().getUuid());
363
                    dto.setClassificationLabel(classification.getName().getText());
364
                }
365
                //TODO lang/locale
366
                Language language = Language.DEFAULT();
367

    
368
                //status
369
                TaxonNodeStatus status = node.getStatus();
370
                if (status != null) {
371
                    dto.setStatus(status.getLabel(language));
372
                }
373
                //statusNote
374
                Map<Language, LanguageString> statusNote = node.getStatusNote();
375
                if (statusNote != null) {
376
                    //TODO handle fallback lang
377
                    LanguageString statusNoteStr = statusNote.get(language);
378
                    if (statusNoteStr == null && statusNote.size() > 0) {
379
                        statusNoteStr = statusNote.entrySet().iterator().next().getValue();
380
                    }
381
                    if (statusNoteStr != null) {
382
                        dto.setStatusNote(statusNoteStr.getText());
383
                    }
384
                }
385
                //agent relations
386
                Set<TaxonNodeAgentRelation> agents = node.getAgentRelations();
387
                if (!agents.isEmpty()) {
388
                    for (TaxonNodeAgentRelation rel : agents) {
389
                        TaxonNodeAgentsRelDTO agentDto = new TaxonNodeAgentsRelDTO();
390
                        loadBaseData(rel, agentDto);
391

    
392
                        //TODO laod
393
                        if (rel.getAgent() != null) {
394
                            agentDto.setAgent(rel.getAgent().getFullTitle());
395
                            agentDto.setAgentUuid(rel.getAgent().getUuid());
396
                            //TODO compute preferred external link
397
                            agentDto.setAgentLink(null);
398
                        }
399
                        if (rel.getType() != null) {
400
                            agentDto.setType(rel.getType().getTitleCache());
401
                            agentDto.setTypeUuid(rel.getType().getUuid());
402
                        }
403
                        dto.addAgent(agentDto);
404
                    }
405
                }
406
                container.addItem(dto);
407
            }
408
            if (container.getCount() > 0) {
409
                result.setTaxonNodes(container);
410
            }
411
        } catch (Exception e) {
412
//            e.printStackTrace();
413
            result.addMessage(MessagesDto.NewErrorInstance("Error when loading taxon node data.", e));
414
        }
415
    }
416

    
417
    private void loadSynonyms(Taxon taxon, TaxonPageDto result, TaxonPageDtoConfiguration config) {
418

    
419
        try {
420
            //        List<HomotypicalGroup> homotypicGroups = taxon.getHomotypicSynonymyGroups();
421

    
422
            TaxonComparator comparator = new TaxonComparator();
423

    
424
            TaxonName name = taxon.getName();
425

    
426
            //TODO depending on config add/remove accepted name
427

    
428
            //TODO check publish flag
429

    
430
            //homotypic synonyms
431
            List<Synonym> homotypicSynonmys = taxon.getHomotypicSynonymsByHomotypicGroup(comparator);
432
            TaxonPageDto.HomotypicGroupDTO homotypicGroupDto = new TaxonPageDto.HomotypicGroupDTO();
433
            if (homotypicSynonmys != null && !homotypicSynonmys.isEmpty()) {
434
                loadBaseData(name.getHomotypicalGroup(), homotypicGroupDto);
435

    
436
                for (Synonym syn : homotypicSynonmys) {
437
                    loadSynonymsInGroup(homotypicGroupDto, syn, config, result);
438
                }
439
            }
440
            if (name != null) {
441
                handleTypification(name.getHomotypicalGroup(), homotypicGroupDto, result, config);
442
            }
443
            result.setHomotypicSynonyms(homotypicGroupDto);
444

    
445
            //heterotypic synonyms
446
            List<HomotypicalGroup> heteroGroups = taxon.getHeterotypicSynonymyGroups();
447
            if (heteroGroups.isEmpty()) {
448
                return;
449
            }
450
            ContainerDto<HomotypicGroupDTO> heteroContainer = new ContainerDto<>();
451
            result.setHeterotypicSynonymGroups(heteroContainer);
452

    
453
            for (HomotypicalGroup hg : heteroGroups) {
454
                TaxonPageDto.HomotypicGroupDTO hgDto = new TaxonPageDto.HomotypicGroupDTO();
455
                loadBaseData(taxon.getName().getHomotypicalGroup(), hgDto);
456
                heteroContainer.addItem(hgDto);
457

    
458
                List<Synonym> heteroSyns = taxon.getSynonymsInGroup(hg, comparator);
459
                for (Synonym syn : heteroSyns) {
460
                    loadSynonymsInGroup(hgDto, syn, config, result);
461
                }
462
                handleTypification(hg, hgDto, result, config);
463
            }
464
        } catch (Exception e) {
465
            //e.printStackTrace();
466
            result.addMessage(MessagesDto.NewErrorInstance("Error when loading synonym data.", e));
467
        }
468
    }
469

    
470
    private void handleTypification(HomotypicalGroup homotypicalGroup, HomotypicGroupDTO hgDto,
471
            TaxonPageDto result, TaxonPageDtoConfiguration config) {
472

    
473
        boolean withCitation = true;
474
        boolean withStartingTypeLabel = true;
475
        boolean withNameIfAvailable = false;
476
        TypeDesignationSetFormatter formatter = new TypeDesignationSetFormatter(
477
                withCitation, withStartingTypeLabel, withNameIfAvailable);
478
        Set<TypeDesignationBase<?>> desigs = homotypicalGroup.getTypeDesignations();
479
        try {
480
            TypeDesignationSetContainer manager = TypeDesignationSetContainer.NewDefaultInstance((Set)desigs);
481
            List<TaggedText> tags = formatter.toTaggedText(manager);
482
            String label = TaggedCacheHelper.createString(tags);
483
            hgDto.setTypes(label);
484
            hgDto.setTaggedTypes(tags);
485
//            hgDto.setTypedTypes(null);
486

    
487
        } catch (Exception e) {
488
//          e.printStackTrace();
489
            result.addMessage(MessagesDto.NewErrorInstance("Error when creating type designation information", e));
490
        }
491
    }
492

    
493
    private void loadConceptRelations(Taxon taxon, TaxonPageDto result, TaxonPageDtoConfiguration config) {
494

    
495
        try {
496
            //concept relations
497
            ContainerDto<ConceptRelationDTO> conceptRelContainer = new ContainerDto<>();
498
            TaxonRelationshipFormatter taxRelFormatter = TaxonRelationshipFormatter.INSTANCE();
499

    
500
            //... MAN
501
            Set<TaxonRelationship> misappliedRels = taxon.getMisappliedNameRelations();
502
            for (TaxonRelationship rel : misappliedRels) {
503
                boolean inverse = true;
504
                boolean withoutName = false;
505
                loadConceptRelation(taxRelFormatter, rel, conceptRelContainer, inverse, withoutName);
506
            }
507

    
508
            //... pro parte Synonyms
509
            Set<TaxonRelationship> proParteRels = taxon.getProParteAndPartialSynonymRelations();
510
            for (TaxonRelationship rel : proParteRels) {
511
                boolean inverse = true;
512
                boolean withoutName = false;
513
                loadConceptRelation(taxRelFormatter, rel, conceptRelContainer, inverse, withoutName);
514
            }
515

    
516
            //TODO MAN and pp from this taxon
517

    
518
            //... to-relations
519
            Set<TaxonRelationship> toRels = taxon.getRelationsToThisTaxon();
520
            toRels.removeAll(misappliedRels);
521
            toRels.removeAll(proParteRels);
522
            for (TaxonRelationship rel : toRels) {
523
                boolean inverse = true;
524
                boolean withoutName = false;
525
                loadConceptRelation(taxRelFormatter, rel, conceptRelContainer, inverse, withoutName);
526
            }
527

    
528
            //... from-relations
529
            Set<TaxonRelationship> fromRels = taxon.getRelationsFromThisTaxon();
530
            for (TaxonRelationship rel : fromRels) {
531
                boolean inverse = false;
532
                boolean withoutName = false;
533
                loadConceptRelation(taxRelFormatter, rel, conceptRelContainer, inverse, withoutName);
534
            }
535

    
536
            if (conceptRelContainer.getCount() > 0) {
537
                result.setConceptRelations(conceptRelContainer);
538
            }
539
        } catch (Exception e) {
540
            //e.printStackTrace();
541
            result.addMessage(MessagesDto.NewErrorInstance("Error when loading concept relation data.", e));
542
        }
543
    }
544

    
545
    private void loadConceptRelation(TaxonRelationshipFormatter taxRelFormatter, TaxonRelationship rel, ContainerDto<ConceptRelationDTO> conceptRelContainer, boolean inverse,
546
            boolean withoutName) {
547
        List<Language> languages = Arrays.asList(new Language[] {Language.DEFAULT()}); // TODO config.locales;
548
        List<TaggedText> tags = taxRelFormatter.getTaggedText(rel, inverse, languages, withoutName);
549
        String relLabel = TaggedCacheHelper.createString(tags);
550
        ConceptRelationDTO dto = new TaxonPageDto.ConceptRelationDTO();
551
        loadBaseData(rel, dto);
552
        Taxon relTaxon = inverse ? rel.getFromTaxon() : rel.getToTaxon();
553
        dto.setRelTaxonId(relTaxon.getId());
554
        dto.setRelTaxonUuid(relTaxon.getUuid());
555
        dto.setRelTaxonLabel(relTaxon.getTitleCache());
556
        dto.setLabel(relLabel);
557
        dto.setTaggedLabel(tags);
558
        dto.setNameUuid(relTaxon.getName() != null ? relTaxon.getName().getUuid() : null);
559

    
560
        if (rel.getType() != null) {
561
            dto.setRelTypeUuid(rel.getType().getUuid());
562
        }
563
        for (TaxonNode node : relTaxon.getTaxonNodes()) {
564
            Classification classification = node.getClassification();
565
            if (classification != null) {
566
                dto.addClassificationUuids(classification.getUuid());
567
            }
568
        }
569
        conceptRelContainer.addItem(dto);
570
    }
571

    
572
    private void loadSynonymsInGroup(TaxonPageDto.HomotypicGroupDTO hgDto, Synonym syn,
573
            TaxonPageDtoConfiguration config, TaxonPageDto pageDto) {
574

    
575
        TaxonBaseDto synDto = new TaxonBaseDto();
576
        loadBaseData(syn, synDto);
577
        synDto.setLabel(syn.getTitleCache());
578
        synDto.setTaggedLabel(getTaggedTaxon(syn, config));
579

    
580
        if (syn.getName() != null) {
581
            handleName(config, synDto, syn.getName(), pageDto);
582
        }
583

    
584
        //TODO
585
        hgDto.addSynonym(synDto);
586
    }
587

    
588
    private void loadProtologues(TaxonName name, TaxonBaseDto taxonBaseDto) {
589
        NomenclaturalSource nomSource = name.getNomenclaturalSource();
590
        if (nomSource != null) {
591
            Set<ExternalLink> links = nomSource.getLinks();
592
            for (ExternalLink link : links) {
593
                if (link.getUri() != null) {
594
                    taxonBaseDto.addProtologue(link.getUri());
595
                }
596
            }
597
        }
598
    }
599

    
600
    private void handleRelatedNames(TaxonName name, TaxonBaseDto taxonDto, TaxonPageDtoConfiguration config) {
601
        //exclusions TODO handle via config
602
        Set<UUID> excludedTypes = new HashSet<>();  //both directions
603
        excludedTypes.add(NameRelationshipType.uuidBasionym);
604
        excludedTypes.add(NameRelationshipType.uuidReplacedSynonym);
605
        Set<UUID> excludedFromTypes = new HashSet<>(excludedTypes);
606
        Set<UUID> excludedToTypes = new HashSet<>(excludedTypes);
607
        //TODO non-types
608

    
609
        //TODO config.getLocales();
610
        Language locale = Language.DEFAULT();
611

    
612
        for (NameRelationship rel : name.getRelationsFromThisName()) {
613
            TaxonName relatedName = rel.getToName();
614
            if (relatedName == null || rel.getType() == null || excludedFromTypes.contains(rel.getType().getUuid())) {
615
                continue;
616
            }
617
            NameRelationDTO dto = new NameRelationDTO();
618
            loadBaseData(rel, dto);
619
            //name
620
            dto.setNameUuid(relatedName.getUuid());
621
            dto.setNameLabel(relatedName.getTaggedName());
622
            //type
623
            dto.setRelTypeUuid(rel.getType().getUuid());
624
            Representation rep = rel.getType().getPreferredRepresentation(locale);
625
            dto.setRelType(rep == null ? rel.getType().toString() : rep.getLabel());
626
            //inverse
627
            dto.setInverse(false);
628
            //ruleConsidered
629
            dto.setRuleConsidered(rel.getRuleConsidered());
630
            taxonDto.addRelatedName(dto);
631
        }
632

    
633
        //to relations
634
        for (NameRelationship rel : name.getRelationsToThisName()) {
635
            TaxonName relatedName = rel.getFromName();
636
            if (relatedName == null || rel.getType() == null || excludedFromTypes.contains(rel.getType().getUuid())) {
637
                continue;
638
            }
639
            NameRelationDTO dto = new NameRelationDTO();
640
            loadBaseData(rel, dto);
641
            //name
642
            dto.setNameUuid(relatedName.getUuid());
643
            dto.setNameLabel(relatedName.getTaggedName());
644
            //type
645
            dto.setRelTypeUuid(rel.getType().getUuid());
646
            Representation rep = rel.getType().getPreferredInverseRepresentation(Arrays.asList(new Language[] {locale}));
647
            dto.setRelType(rep == null ? rel.getType().toString() : rep.getLabel());
648
            //inverse
649
            dto.setInverse(true);
650
            taxonDto.addRelatedName(dto);
651
        }
652
    }
653

    
654
    private void loadFacts(Taxon taxon, TaxonPageDto taxonPageDto, TaxonPageDtoConfiguration config) {
655

    
656
        try {
657
            //compute the features that do exist for this taxon
658
            Map<UUID, Feature> existingFeatureUuids = getExistingFeatureUuids(taxon);
659

    
660
            //filter, sort and structure according to feature tree
661
            TreeNode<Feature, UUID> filteredRootNode;
662
            if (config.getFeatureTree() != null) {
663

    
664
                //TODO class cast
665
                TermTree<Feature> featureTree = repository.getTermTreeService().find(config.getFeatureTree());
666
                filteredRootNode = filterFeatureNode(featureTree.getRoot(), existingFeatureUuids.keySet());
667
            } else {
668
                filteredRootNode = createDefaultFeatureNode(taxon);
669
            }
670

    
671
            //load facts per feature
672
            Map<UUID,Set<DescriptionElementBase>> featureMap = loadFeatureMap(taxon);
673

    
674
            //load final result
675
            if (filteredRootNode != null && !filteredRootNode.getChildren().isEmpty()) {
676
                ContainerDto<FeatureDto> features = new ContainerDto<>();
677
                for (TreeNode<Feature,UUID> node : filteredRootNode.getChildren()) {
678
                    handleFeatureNode(config, featureMap, features, node);
679
                }
680
                taxonPageDto.setTaxonFacts(features);
681
            }
682
        } catch (Exception e) {
683
            //e.printStackTrace();
684
            taxonPageDto.addMessage(MessagesDto.NewErrorInstance("Error when loading factual data.", e));
685
        }
686
    }
687

    
688
    //TODO merge with loadFacts, it is almost the same, see //DIFFERENT
689
    private void loadNameFacts(TaxonName name, TaxonBaseDto nameDto, TaxonPageDtoConfiguration config, TaxonPageDto pageDto) {
690

    
691
        try {
692
            //compute the features that do exist for this taxon
693
            Map<UUID, Feature> existingFeatureUuids = getExistingFeatureUuids(name);
694

    
695
            //filter, sort and structure according to feature tree
696
            TreeNode<Feature, UUID> filteredRootNode;
697
            //DIFFERENT
698
//            if (config.getFeatureTree() != null) {
699
//
700
//                //TODO class cast
701
//                TermTree<Feature> featureTree = repository.getTermTreeService().find(config.getFeatureTree());
702
//                filteredRootNode = filterFeatureNode(featureTree.getRoot(), existingFeatureUuids.keySet());
703
//            } else {
704
                filteredRootNode = createDefaultFeatureNode(name);
705
//            }  //DIFFERENT END
706

    
707
            //load facts per feature
708
            Map<UUID,Set<DescriptionElementBase>> featureMap = loadFeatureMap(name);
709

    
710
            //load final result
711
            if (!filteredRootNode.getChildren().isEmpty()) {
712
                ContainerDto<FeatureDto> features = new ContainerDto<>();
713
                for (TreeNode<Feature,UUID> node : filteredRootNode.getChildren()) {
714
                    handleFeatureNode(config, featureMap, features, node);
715
                }
716
                //DIFFERENT
717
                nameDto.setNameFacts(features);
718
            }
719
        } catch (Exception e) {
720
            //e.printStackTrace();
721
            //DIFFERENT
722
            pageDto.addMessage(MessagesDto.NewErrorInstance("Error when loading factual data.", e));
723
        }
724
    }
725

    
726
    private void handleFeatureNode(TaxonPageDtoConfiguration config,
727
            Map<UUID, Set<DescriptionElementBase>> featureMap, ContainerDto<FeatureDto> features,
728
            TreeNode<Feature, UUID> node) {
729

    
730
        Feature feature = node.getData();
731
        //TODO locale
732
        FeatureDto featureDto = new FeatureDto(feature.getUuid(), feature.getId(), feature.getLabel());
733
        features.addItem(featureDto);
734

    
735
        List<Distribution> distributions = new ArrayList<>();
736

    
737
        //
738
        for (DescriptionElementBase fact : featureMap.get(feature.getUuid())){
739
            if (fact.isInstanceOf(Distribution.class)) {
740
                distributions.add(CdmBase.deproxy(fact, Distribution.class));
741
            }else {
742
                //TODO how to handle CommonNames, do we also want to have a data structure
743
                //with Language|
744
//                             -- Area|
745
//                                    --name
746
                // a bit like for distribution??
747
                handleFact(featureDto, fact);
748
            }
749
        }
750

    
751
        handleDistributions(config, featureDto, distributions);
752
        //TODO really needed?
753
        orderFacts(featureDto);
754

    
755
        //children
756
        ContainerDto<FeatureDto> childFeatures = new ContainerDto<>();
757
        for (TreeNode<Feature,UUID> child : node.getChildren()) {
758
            handleFeatureNode(config, featureMap, childFeatures, child);
759
        }
760
        if (childFeatures.getCount() > 0) {
761
            featureDto.setSubFeatures(childFeatures);
762
        }
763
    }
764

    
765
    //TODO not really used yet, only for distinguishing fact classes,
766
    //needs discussion if needed and how to implement.
767
    //we could also move compareTo methods to DTO classes but with this
768
    //remove from having only data in the DTO, no logic
769
    private void orderFacts(FeatureDto featureDto) {
770
        List<IFactDto> list = featureDto.getFacts().getItems();
771
        Collections.sort(list, (f1,f2)->{
772
            if (!f1.getClass().equals(f2.getClass())) {
773
                return f1.getClass().getSimpleName().compareTo(f2.getClass().getSimpleName());
774
            }else {
775
                if (f1 instanceof FactDto) {
776
                   FactDto fact1 = (FactDto)f1;
777
                   FactDto fact2 = (FactDto)f2;
778

    
779
//                   return fact1.getTypedLabel().toString().compareTo(fact2.getTypedLabel().toString());
780
                   return 0; //FIXME;
781
                } else if (f1 instanceof CommonNameDto) {
782
                    int result = 0;
783
                    CommonNameDto fact1 = (CommonNameDto)f1;
784
                    CommonNameDto fact2 = (CommonNameDto)f2;
785
                    return 0;  //FIXME
786
                }
787
            }
788
            return 0;  //FIXME
789
        });
790

    
791
    }
792

    
793
    private Map<UUID, Set<DescriptionElementBase>> loadFeatureMap(IDescribable<?> describable) {
794
        Map<UUID, Set<DescriptionElementBase>> featureMap = new HashMap<>();
795

    
796
        //... load facts
797
        for (DescriptionBase<?> description : describable.getDescriptions()) {
798
            if (description.isImageGallery()) {
799
                continue;
800
            }
801
            for (DescriptionElementBase deb : description.getElements()) {
802
                Feature feature = deb.getFeature();
803
                if (featureMap.get(feature.getUuid()) == null) {
804
                    featureMap.put(feature.getUuid(), new HashSet<>());
805
                }
806
                featureMap.get(feature.getUuid()).add(deb);
807
            }
808
        }
809
        return featureMap;
810
    }
811

    
812
    private TreeNode<Feature, UUID> createDefaultFeatureNode(IDescribable<?> describable) {
813
        TreeNode<Feature, UUID> root = new TreeNode<>();
814
        Set<Feature> requiredFeatures = new HashSet<>();
815

    
816
        for (DescriptionBase<?> description : describable.getDescriptions()) {
817
            if (description.isImageGallery()) {
818
                continue;
819
            }
820
            for (DescriptionElementBase deb : description.getElements()) {
821
                Feature feature = deb.getFeature();
822
                if (feature != null) {  //null should not happen
823
                    requiredFeatures.add(feature);
824
                }
825
            }
826
        }
827
        List<Feature> sortedChildren = new ArrayList<>(requiredFeatures);
828
        Collections.sort(sortedChildren, (f1,f2) -> f1.getTitleCache().compareTo(f2.getTitleCache()));
829
        sortedChildren.stream().forEachOrdered(f->root.addChild(new TreeNode<>(f.getUuid(), f)));
830
        return root;
831
    }
832

    
833
    /**
834
     * Recursive call to a feature tree's feature node in order to creates a tree structure
835
     * ordered in the same way as the according feature tree but only containing features
836
     * that do really exist for the given taxon. If only a child node is required the parent
837
     * node/feature is also considered to be required.<BR>
838
     */
839
    private TreeNode<Feature, UUID> filterFeatureNode(TermNode<Feature> featureNode,
840
            Set<UUID> existingFeatureUuids) {
841

    
842
        //first filter children
843
        List<TreeNode<Feature, UUID>> requiredChildNodes = new ArrayList<>();
844
        for (TermNode<Feature> childNode : featureNode.getChildNodes()) {
845
            TreeNode<Feature, UUID> child = filterFeatureNode(childNode, existingFeatureUuids);
846
            if (child != null) {
847
                requiredChildNodes.add(child);
848
            }
849
        }
850

    
851
        //if any child is required or this node is required ....
852
        if (!requiredChildNodes.isEmpty() ||
853
                featureNode.getTerm() != null && existingFeatureUuids.contains(featureNode.getTerm().getUuid())) {
854

    
855
            TreeNode<Feature,UUID> result = new TreeNode<>();
856
            //add this nodes data
857
            Feature feature = featureNode.getTerm() == null ? null : featureNode.getTerm();
858
            if (feature != null) {
859
                result.setNodeId(feature.getUuid());
860
                result.setData(feature);
861
            }
862
            //add child data
863
            requiredChildNodes.stream().forEachOrdered(c->result.addChild(c));
864
            return result;
865
        }else {
866
            return null;
867
        }
868
    }
869

    
870
    /**
871
     * Computes the (unsorted) set of features for  which facts exist
872
     * for the given taxon.
873
     */
874
    private Map<UUID, Feature> getExistingFeatureUuids(IDescribable<?> describable) {
875
        Map<UUID, Feature> result = new HashMap<>();
876
        for (DescriptionBase<?> description : describable.getDescriptions()) {
877
            if (description.isImageGallery()) {
878
                continue;
879
            }
880
            for (DescriptionElementBase deb : description.getElements()) {
881
                Feature feature = deb.getFeature();
882
                if (feature != null) {  //null should not happen
883
                    result.put(feature.getUuid(), feature);
884
                }
885
            }
886
        }
887
        return result;
888
    }
889

    
890
    private void handleDistributions(TaxonPageDtoConfiguration config, FeatureDto featureDto,
891
            List<Distribution> distributions) {
892

    
893
        if (distributions.isEmpty()) {
894
            return;
895
        }
896
        IDistributionService distributionService = repository.getDistributionService();
897

    
898
        //configs
899
        DistributionInfoConfiguration distributionConfig = config.getDistributionInfoConfiguration();
900
        CondensedDistributionConfiguration condensedConfig = distributionConfig.getCondensedDistrConfig();
901

    
902
        String statusColorsString = distributionConfig.getStatusColorsString();
903

    
904

    
905
        //copied from DescriptionListController
906

    
907
        boolean ignoreDistributionStatusUndefined = true;  //workaround until #9500 is fully implemented
908
        distributionConfig.setIgnoreDistributionStatusUndefined(ignoreDistributionStatusUndefined);
909
        boolean fallbackAsParent = true;  //may become a service parameter in future
910

    
911
        DistributionInfoDto dto;
912

    
913
        //hiddenArea markers include markers for fully hidden areas and fallback areas. The later
914
        //are hidden markers on areas that have non-hidden subareas (#4408)
915
        Set<MarkerType> hiddenAreaMarkerTypes = distributionConfig.getHiddenAreaMarkerTypeList();
916
        if(!CdmUtils.isNullSafeEmpty(hiddenAreaMarkerTypes)){
917
            condensedConfig.hiddenAndFallbackAreaMarkers = hiddenAreaMarkerTypes.stream().map(mt->mt.getUuid()).collect(Collectors.toSet());
918
        }
919

    
920
        List<String> initStrategy = null;
921

    
922
        Map<PresenceAbsenceTerm, Color> distributionStatusColors;
923
        try {
924
            distributionStatusColors = DistributionServiceUtilities.buildStatusColorMap(
925
                    statusColorsString, repository.getTermService(), repository.getVocabularyService());
926
        } catch (JsonProcessingException e) {
927
            logger.error("JsonProcessingException when reading distribution status colors");
928
            //TODO is null allowed?
929
            distributionStatusColors = null;
930
        }
931

    
932
        dto = distributionService.composeDistributionInfoFor(distributionConfig, distributions,
933
                fallbackAsParent,
934
                distributionStatusColors, LocaleContext.getLanguages());
935

    
936
        if (distributionConfig.isUseTreeDto() && dto.getTree() != null) {
937
            DistributionTreeDto tree = (DistributionTreeDto)dto.getTree();
938
            TreeNode<Set<DistributionDto>, NamedAreaDto> root = tree.getRootElement();
939
            //fill uuid->distribution map
940
            Map<UUID,Distribution> distributionMap = new HashMap<>();
941
            distributions.stream().forEach(d->distributionMap.put(d.getUuid(), d));
942
            handleDistributionDtoNode(distributionMap, root);
943
        }
944

    
945
        featureDto.addFact(dto);
946
    }
947

    
948
    private void handleDistributionDtoNode(Map<UUID, Distribution> map,
949
            TreeNode<Set<DistributionDto>, NamedAreaDto> root) {
950
       if (root.getData() != null) {
951
           root.getData().stream().forEach(d->{
952
               Distribution distr  = map.get(d.getUuid());
953
               loadBaseData(distr, d);
954
               d.setTimeperiod(distr.getTimeperiod() == null ? null : distr.getTimeperiod().toString());
955

    
956
           });
957
       }
958
       //handle children
959
       if (root.getChildren() != null) {
960
           root.getChildren().stream().forEach(c->handleDistributionDtoNode(map, c));
961
       }
962
    }
963

    
964
    private FactDtoBase handleFact(FeatureDto featureDto, DescriptionElementBase fact) {
965
        //TODO locale
966
        Language localeLang = null;
967

    
968
        FactDtoBase result;
969
        if (fact.isInstanceOf(TextData.class)) {
970
            TextData td = CdmBase.deproxy(fact, TextData.class);
971
            LanguageString ls = td.getPreferredLanguageString(localeLang);
972
            String text = ls == null ? "" : CdmUtils.Nz(ls.getText());
973

    
974
            FactDto factDto = new FactDto();
975
            featureDto.addFact(factDto);
976
            //TODO do we really need type information for textdata here?
977
            TypedLabel typedLabel = new TypedLabel(text);
978
            typedLabel.setClassAndId(td);
979
            factDto.getTypedLabel().add(typedLabel);
980
            loadBaseData(td, factDto);
981
            //TODO
982
            result = factDto;
983
        }else if (fact.isInstanceOf(CommonTaxonName.class)) {
984
            CommonTaxonName ctn = CdmBase.deproxy(fact, CommonTaxonName.class);
985
            CommonNameDto dto = new CommonNameDto();
986
            featureDto.addFact(dto);
987

    
988
            Language lang = ctn.getLanguage();
989
            if (lang != null) {
990
                String langLabel = getTermLabel(lang, localeLang);
991
                dto.setLanguage(langLabel);
992
                dto.setLanguageUuid(lang.getUuid());
993
            }else {
994
                //TODO
995
                dto.setLanguage("-");
996
            }
997
            //area
998
            NamedArea area = ctn.getArea();
999
            if (area != null) {
1000
                String areaLabel = getTermLabel(area, localeLang);
1001
                dto.setArea(areaLabel);
1002
                dto.setAreaUUID(area.getUuid());
1003
            }
1004
            dto.setName(ctn.getName());
1005
            loadBaseData(ctn, dto);
1006
            //TODO sort all common names
1007
            result = dto;
1008
        } else if (fact.isInstanceOf(IndividualsAssociation.class)) {
1009
            IndividualsAssociation ia = CdmBase.deproxy(fact, IndividualsAssociation.class);
1010
            IndividualsAssociationDto dto = new IndividualsAssociationDto ();
1011

    
1012
            LanguageString description = MultilanguageTextHelper.getPreferredLanguageString(ia.getDescription(), Arrays.asList(localeLang));
1013
            if (description != null) {
1014
                dto.setDescritpion(description.getText());
1015
            }
1016
            SpecimenOrObservationBase<?> specimen = ia.getAssociatedSpecimenOrObservation();
1017
            if (specimen != null) {
1018
                //TODO what to use here??
1019
                dto.setOccurrence(specimen.getTitleCache());
1020
                dto.setOccurrenceUuid(specimen.getUuid());
1021
            }
1022

    
1023
            featureDto.addFact(dto);
1024
            loadBaseData(ia, dto);
1025
            result = dto;
1026
        } else if (fact.isInstanceOf(TaxonInteraction.class)) {
1027
            TaxonInteraction ti = CdmBase.deproxy(fact, TaxonInteraction.class);
1028
            TaxonInteractionDto dto = new TaxonInteractionDto ();
1029

    
1030
            LanguageString description = MultilanguageTextHelper.getPreferredLanguageString(
1031
                    ti.getDescription(), Arrays.asList(localeLang));
1032
            if (description != null) {
1033
                dto.setDescritpion(description.getText());
1034
            }
1035
            Taxon taxon = ti.getTaxon2();
1036
            if (taxon != null) {
1037
                //TODO what to use here??
1038
                dto.setTaxon(taxon.cacheStrategy().getTaggedTitle(taxon));
1039
                dto.setTaxonUuid(taxon.getUuid());
1040
            }
1041
            featureDto.addFact(dto);
1042
            loadBaseData(ti, dto);
1043
            result = dto;
1044
        }else if (fact.isInstanceOf(CategoricalData.class)) {
1045
            CategoricalData cd = CdmBase.deproxy(fact, CategoricalData.class);
1046
            FactDto factDto = new FactDto();
1047
            featureDto.addFact(factDto);
1048
            //TODO do we really need type information for textdata here?
1049
            String label = CategoricalDataFormatter.NewInstance(null).format(cd, localeLang);
1050
            TypedLabel typedLabel = new TypedLabel(label);
1051
            typedLabel.setClassAndId(cd);
1052
            factDto.getTypedLabel().add(typedLabel);
1053
            //TODO
1054
            loadBaseData(cd, factDto);
1055
            result = factDto;
1056
        }else if (fact.isInstanceOf(QuantitativeData.class)) {
1057
            QuantitativeData qd = CdmBase.deproxy(fact, QuantitativeData.class);
1058
            FactDto factDto = new FactDto();
1059
            featureDto.addFact(factDto);
1060
            //TODO do we really need type information for textdata here?
1061
            String label = QuantitativeDataFormatter.NewInstance(null).format(qd, localeLang);
1062
            TypedLabel typedLabel = new TypedLabel(label);
1063
            typedLabel.setClassAndId(qd);
1064
            factDto.getTypedLabel().add(typedLabel);
1065
            //TODO
1066
            loadBaseData(qd, factDto);
1067
            result = factDto;
1068
        }else if (fact.isInstanceOf(TemporalData.class)) {
1069
            TemporalData td = CdmBase.deproxy(fact, TemporalData.class);
1070
            FactDto factDto = new FactDto();
1071
            featureDto.addFact(factDto);
1072
            //TODO do we really need type information for textdata here?
1073
            String label = td.toString();
1074
            TypedLabel typedLabel = new TypedLabel(label);
1075
            typedLabel.setClassAndId(td);
1076
            factDto.getTypedLabel().add(typedLabel);
1077
            //TODO
1078
            loadBaseData(td, factDto);
1079
            result = factDto;
1080
        }else {
1081
//            TODO
1082
            logger.warn("DescriptionElement type not yet handled: " + fact.getClass().getSimpleName());
1083
            return null;
1084
        }
1085
        result.setTimeperiod(fact.getTimeperiod() == null ? null : fact.getTimeperiod().toString());
1086
        return result;
1087
    }
1088

    
1089
    private String getTermLabel(TermBase term, Language localeLang) {
1090
        if (term == null) {
1091
            return null;
1092
        }
1093
        Representation rep = term.getPreferredRepresentation(localeLang);
1094
        String label = rep == null ? null : rep.getLabel();
1095
        label = label == null ? term.getLabel() : label;
1096
        return label;
1097
    }
1098

    
1099
    /**
1100
     * Compares an existing last date and the last date of an entity
1101
     * and returns the resulting last date.
1102
     */
1103
    private LocalDateTime getLastUpdated(LocalDateTime existingLastDate, VersionableEntity dateToAddEntity) {
1104

    
1105
        DateTime dateToAdd = dateToAddEntity.getUpdated() != null ? dateToAddEntity.getUpdated() : dateToAddEntity.getCreated();
1106

    
1107
        LocalDateTime javaLocalDateTimeOfEntity = dateToAdd == null ? null:
1108
                LocalDateTime.of(dateToAdd.getYear(), dateToAdd.getMonthOfYear(),
1109
                        dateToAdd.getDayOfMonth(), dateToAdd.getHourOfDay(),
1110
                        dateToAdd.getMinuteOfHour(), dateToAdd.getSecondOfMinute());
1111

    
1112
       if (existingLastDate == null) {
1113
           return javaLocalDateTimeOfEntity;
1114
       }else if (javaLocalDateTimeOfEntity == null || javaLocalDateTimeOfEntity.compareTo(existingLastDate) < 0)  {
1115
           return existingLastDate;
1116
       }else {
1117
           return javaLocalDateTimeOfEntity;
1118
       }
1119
    }
1120

    
1121
    private void loadBaseData(CdmBase cdmBase, CdmBaseDto dto) {
1122
        dto.setId(cdmBase.getId());
1123
        dto.setUuid(cdmBase.getUuid());
1124

    
1125
        loadAnnotatable(cdmBase, dto);
1126
        loadSources(cdmBase, dto);
1127
        //loadIdentifiable(cdmBase, dto);
1128
    }
1129

    
1130
    private void loadSources(CdmBase cdmBase, CdmBaseDto dto) {
1131
        if (dto instanceof SingleSourcedDto && cdmBase.isInstanceOf(SingleSourcedEntityBase.class)) {
1132
            //TODO other sourced
1133
            SingleSourcedEntityBase sourced = CdmBase.deproxy(cdmBase, SingleSourcedEntityBase.class);
1134
            SingleSourcedDto sourcedDto = (SingleSourcedDto)dto;
1135
            NamedSource source = sourced.getSource();
1136
            if (source != null) { //TODO  && !source.isEmpty() - does not exist yet
1137
                SourceDto sourceDto = new SourceDto();
1138
                loadSource(source, sourceDto);
1139
                sourcedDto.setSource(sourceDto);
1140
            }
1141
        } else if (dto instanceof SourcedDto && cdmBase instanceof ISourceable) {
1142
            @SuppressWarnings("unchecked")
1143
            ISourceable<OriginalSourceBase> sourced = (ISourceable<OriginalSourceBase>)cdmBase;
1144
            SourcedDto sourcedDto = (SourcedDto)dto;
1145
            for (OriginalSourceBase source : sourced.getSources()) {
1146
                SourceDto sourceDto = new SourceDto();
1147
                loadSource(source, sourceDto);
1148
                sourcedDto.addSource(sourceDto);
1149
            }
1150
        }
1151
    }
1152

    
1153
    private void loadSource(OriginalSourceBase source, SourceDto sourceDto) {
1154

    
1155
        source = CdmBase.deproxy(source);
1156
        //base data
1157
        loadBaseData(source, sourceDto);
1158

    
1159
        ICdmBase linkedObject = source.getCitation();
1160
        if (linkedObject == null) {
1161
            //cdmsource
1162
            linkedObject = source.getCdmSource();
1163
        }
1164

    
1165
        //citation doi & uri & links
1166
        Reference ref = source.getCitation();
1167
        if (ref != null) {
1168
            sourceDto.setDoi(ref.getDoiString());
1169
            sourceDto.setUri(ref.getUri());
1170
            Set<ExternalLink> links = ref.getLinks();
1171
            for (ExternalLink link : links) {
1172
                if (link.getUri() != null) {
1173
                    sourceDto.addLink(link.getUri());
1174
                }
1175
            }
1176
        }
1177

    
1178
        //label
1179
        //TODO this probably does not use specimen or cdmSource if necessary,
1180
        //     also long citation is still preliminary
1181
        String label = OriginalSourceFormatter.INSTANCE_LONG_CITATION.format(source);
1182
        TypedLabel typedLabel = new TypedLabel(source, label);
1183
        sourceDto.addLabel(typedLabel);
1184
        sourceDto.setType(source.getType() != null ? source.getType().toString() : null);
1185

    
1186
        if (source.isInstanceOf(NamedSourceBase.class)) {
1187
            NamedSourceBase ns = CdmBase.deproxy(source, NamedSourceBase.class);
1188

    
1189
            //nameUsedInSource
1190
            TaxonName name =  ns.getNameUsedInSource();
1191
            if (name != null) {
1192
                List<TaggedText> taggedName = name.cacheStrategy().getTaggedTitle(name);
1193
                //TODO nom status?
1194
                sourceDto.setNameInSource(taggedName);
1195
                sourceDto.setNameInSourceUuid(name.getUuid());
1196
            }
1197

    
1198
            //specimen uuid
1199
            if (source.isInstanceOf(DescriptionElementSource.class)) {
1200
                DescriptionElementSource des = CdmBase.deproxy(source, DescriptionElementSource.class);
1201
                if (linkedObject == null) {
1202
                    linkedObject = des.getSpecimen();
1203
                }
1204
            }
1205
        }
1206

    
1207
        sourceDto.setLinkedUuid(getUuid(linkedObject));
1208
        String linkedObjectStr = linkedObject == null ? null : CdmBase.deproxy(linkedObject).getClass().getSimpleName();
1209
        sourceDto.setLinkedClass(linkedObjectStr);
1210
    }
1211

    
1212
    private UUID getUuid(ICdmBase cdmBase) {
1213
        return cdmBase == null ? null : cdmBase.getUuid();
1214
    }
1215

    
1216
    private void loadAnnotatable(CdmBase cdmBase, CdmBaseDto dto) {
1217
        if (dto instanceof AnnotatableDto && cdmBase.isInstanceOf(AnnotatableEntity.class)) {
1218
            AnnotatableEntity annotatable = CdmBase.deproxy(cdmBase, AnnotatableEntity.class);
1219
            AnnotatableDto annotatableDto = (AnnotatableDto)dto;
1220
            //annotation
1221
            for (Annotation annotation : annotatable.getAnnotations()) {
1222
                if (annotation.getAnnotationType() != null
1223
                        //TODO annotation type filter
1224
                        && annotation.getAnnotationType().getUuid().equals(AnnotationType.uuidEditorial)
1225
                        && StringUtils.isNotBlank(annotation.getText())) {
1226

    
1227
                    AnnotationDto annotationDto = new AnnotationDto();
1228
                    annotatableDto.addAnnotation(annotationDto);
1229
                    //TODO id needed? but need to adapt dto and container then
1230
                    loadBaseData(annotation, annotationDto);
1231
                    annotationDto.setText(annotation.getText());
1232
                    UUID uuidAnnotationType = annotation.getAnnotationType() == null ? null :annotation.getAnnotationType().getUuid();
1233
                    annotationDto.setTypeUuid(uuidAnnotationType);
1234
                    //language etc. currently not yet used
1235
                }
1236
            }
1237

    
1238
            //marker
1239
            for (Marker marker : annotatable.getMarkers()) {
1240
                if (marker.getMarkerType() != null
1241
                        //TODO markertype filter
1242
//                        && marker.getMarkerType().getUuid().equals(AnnotationType.uuidEditorial)
1243
                           ){
1244

    
1245
                    MarkerDto markerDto = new MarkerDto();
1246
                    annotatableDto.addMarker(markerDto);
1247
                    //TODO id needed? but need to adapt dto and container then
1248
                    loadBaseData(marker, markerDto);
1249
                    if (marker.getMarkerType() != null) {
1250
                        markerDto.setTypeUuid(marker.getMarkerType().getUuid());
1251
                        //TODO locale
1252
                        markerDto.setType(marker.getMarkerType().getTitleCache());
1253
                    }
1254
                    markerDto.setValue(marker.getValue());
1255
                }
1256
            }
1257
        }
1258
    }
1259
}
(3-3/4)