ref #10137: improve derivate handling in dtos
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / dto / SpecimenOrObservationBaseDTO.java
1 /**
2 * Copyright (C) 2015 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.dto;
10
11 import java.io.Serializable;
12 import java.util.AbstractMap;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Comparator;
16 import java.util.EnumSet;
17 import java.util.HashSet;
18 import java.util.List;
19 import java.util.Set;
20 import java.util.TreeSet;
21 import java.util.stream.Collectors;
22
23 import eu.etaxonomy.cdm.common.CdmUtils;
24 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
25 import eu.etaxonomy.cdm.model.agent.AgentBase;
26 import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
27 import eu.etaxonomy.cdm.model.common.CdmBase;
28 import eu.etaxonomy.cdm.model.common.IdentifiableSource;
29 import eu.etaxonomy.cdm.model.description.DescriptionBase;
30 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
31 import eu.etaxonomy.cdm.model.description.Feature;
32 import eu.etaxonomy.cdm.model.description.SpecimenDescription;
33 import eu.etaxonomy.cdm.model.description.TextData;
34 import eu.etaxonomy.cdm.model.media.Media;
35 import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
36 import eu.etaxonomy.cdm.model.occurrence.DerivationEvent;
37 import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
38 import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
39 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
40 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;
41 import eu.etaxonomy.cdm.model.term.DefinedTerm;
42 import eu.etaxonomy.cdm.model.term.TermBase;
43 import eu.etaxonomy.cdm.ref.TypedEntityReference;
44
45
46 public abstract class SpecimenOrObservationBaseDTO extends TypedEntityReference<SpecimenOrObservationBase<?>>{
47
48 private static final long serialVersionUID = -7597690654462090732L;
49
50 private int id;
51 private TreeSet<AbstractMap.SimpleEntry<String, String>> characterData;
52 private DerivationTreeSummaryDTO derivationTreeSummary;
53 protected String taxonName;
54
55 protected String summaryLabel;
56 protected boolean hasDetailImage;
57 private boolean hasCharacterData;
58 private boolean hasDna;
59 private boolean hasSpecimenScan;
60
61 private SpecimenOrObservationType recordBase;
62 private TermBase kindOfUnit;
63 private String collectorsString;
64 private String individualCount;
65 private Set<DerivedUnitDTO> derivatives;
66 private Set<AnnotationDTO> annotations = new HashSet<>();
67
68 private Set<SpecimenTypeDesignationDTO> specimenTypeDesignations;
69
70 private EventDTO<DerivationEvent> derivationEvent;
71
72 // TODO use DTO !!!
73 private Set<IdentifiableSource> sources;
74
75 private List<MediaDTO> listOfMedia = new ArrayList<>();
76
77 private DefinedTerm sex;
78
79 private DefinedTerm lifeStage;
80
81 private List<DeterminationEventDTO>determinations;
82
83 protected SpecimenOrObservationBaseDTO(SpecimenOrObservationBase<?> specimenOrObservation) {
84 super(HibernateProxyHelper.getClassWithoutInitializingProxy(specimenOrObservation), specimenOrObservation.getUuid(), specimenOrObservation.getTitleCache());
85 this.id = specimenOrObservation.getId();
86 Set<Media> collectedMedia = collectMedia(specimenOrObservation);
87 addMediaAsDTO(collectedMedia);
88 setKindOfUnit(specimenOrObservation.getKindOfUnit());
89 setSex(specimenOrObservation.getSex());
90 setIndividualCount(specimenOrObservation.getIndividualCount());
91 lifeStage = specimenOrObservation.getLifeStage();
92 FieldUnit fieldUnit = null;
93 if (specimenOrObservation instanceof FieldUnit){
94 fieldUnit = (FieldUnit)specimenOrObservation;
95 }else{
96 fieldUnit = getFieldUnit((DerivedUnit)specimenOrObservation);
97 }
98 if (fieldUnit != null){
99 AgentBase<?> collector = null;
100 if (fieldUnit.getGatheringEvent() != null){
101 collector = fieldUnit.getGatheringEvent().getCollector();
102 }
103 String fieldNumberString = CdmUtils.Nz(fieldUnit.getFieldNumber());
104 if (collector != null){
105 if (collector.isInstanceOf(TeamOrPersonBase.class)){
106 collectorsString = CdmBase.deproxy(collector, TeamOrPersonBase.class).getCollectorTitleCache();
107 }else{
108 collectorsString = collector.getTitleCache(); //institutions
109 }
110 }
111 collectorsString = CdmUtils.concat(" - ", collectorsString, fieldNumberString);
112 }
113 setDeterminations(specimenOrObservation.getDeterminations().stream()
114 .map(det -> DeterminationEventDTO.from(det))
115 .collect(Collectors.toList())
116 );
117 if (specimenOrObservation instanceof DerivedUnit){
118 DerivedUnit derivedUnit = (DerivedUnit)specimenOrObservation;
119 if (derivedUnit.getSpecimenTypeDesignations() != null){
120 setSpecimenTypeDesignations(derivedUnit.getSpecimenTypeDesignations());
121 }
122 }
123 }
124
125 /**
126 * finds the field unit of the derived unit or null if no field unit exist
127 * @param specimenOrObservation
128 * @return
129 */
130 private FieldUnit getFieldUnit(DerivedUnit specimenOrObservation) {
131 if (specimenOrObservation.getDerivedFrom() != null && !specimenOrObservation.getDerivedFrom().getOriginals().isEmpty()){
132 for (SpecimenOrObservationBase<?> specimen: specimenOrObservation.getDerivedFrom().getOriginals()){
133 if (specimen instanceof FieldUnit){
134 return (FieldUnit)specimen;
135 }else if (specimen instanceof DerivedUnit){
136 getFieldUnit(HibernateProxyHelper.deproxy(specimen,DerivedUnit.class));
137 }
138 }
139 }
140 return null;
141 }
142
143 public String getCollectorsString() {
144 return collectorsString;
145 }
146 public void setCollectorsString(String collectorsString) {
147 this.collectorsString = collectorsString;
148 }
149
150 public Set<SpecimenTypeDesignationDTO> getSpecimenTypeDesignations() {
151 return specimenTypeDesignations;
152 }
153
154 public void setSpecimenTypeDesignations(Set<SpecimenTypeDesignation> specimenTypeDesignations) {
155 this.specimenTypeDesignations = new HashSet<>();
156 for (SpecimenTypeDesignation typeDes: specimenTypeDesignations){
157 if (typeDes != null){
158 this.specimenTypeDesignations.add(new SpecimenTypeDesignationDTO(typeDes));
159 }
160 }
161 }
162
163 public Set<IdentifiableSource> getSources() {
164 return sources;
165 }
166
167 public void setSources(Set<IdentifiableSource> sources) {
168 this.sources = sources;
169 }
170
171 public DerivationTreeSummaryDTO getDerivationTreeSummary() {
172 return derivationTreeSummary;
173 }
174 public void setDerivationTreeSummary(DerivationTreeSummaryDTO derivationTreeSummary) {
175 this.derivationTreeSummary = derivationTreeSummary;
176 if(derivationTreeSummary != null) {
177 setHasSpecimenScan(isHasSpecimenScan() || !derivationTreeSummary.getSpecimenScans().isEmpty());
178 setHasDetailImage(isHasDetailImage() || !derivationTreeSummary.getDetailImages().isEmpty());
179 setHasDna(isHasDna() || !derivationTreeSummary.getMolecularDataList().isEmpty());
180 }
181 }
182
183 public TreeSet<AbstractMap.SimpleEntry<String, String>> getCharacterData() {
184 return characterData;
185 }
186 public void addCharacterData(String character, String state){
187 if(characterData==null){
188 characterData = new TreeSet<>(new PairComparator());
189 }
190 characterData.add(new AbstractMap.SimpleEntry<>(character, state));
191 this.setHasCharacterData(!this.characterData.isEmpty());
192 }
193
194 private class PairComparator implements Comparator<AbstractMap.SimpleEntry<String,String>>, Serializable {
195
196 private static final long serialVersionUID = -8589392050761963540L;
197
198 @Override
199 public int compare(AbstractMap.SimpleEntry<String, String> o1, AbstractMap.SimpleEntry<String, String> o2) {
200 if(o1==null && o2!=null){
201 return -1;
202 }
203 if(o1!=null && o2==null){
204 return 1;
205 }
206 if(o1!=null && o2!=null){
207 return o1.getKey().compareTo(o2.getKey());
208 }
209 return 0;
210 }
211 }
212
213 public boolean isHasCharacterData() {
214 return hasCharacterData;
215 }
216 public void setHasCharacterData(boolean hasCharacterData) {
217 this.hasCharacterData = hasCharacterData;
218 }
219
220 public boolean isHasDna() {
221 return hasDna;
222 }
223 public void setHasDna(boolean hasDna) {
224 this.hasDna = hasDna;
225 }
226
227 public boolean isHasDetailImage() {
228 return hasDetailImage;
229 }
230 public void setHasDetailImage(boolean hasDetailImage) {
231 this.hasDetailImage = hasDetailImage;
232 }
233
234 public boolean isHasSpecimenScan() {
235 return hasSpecimenScan;
236 }
237 public void setHasSpecimenScan(boolean hasSpecimenScan) {
238 this.hasSpecimenScan = hasSpecimenScan;
239 }
240 /**
241 * @return The summary of all DerivedUnit identifiers with the label of
242 * this SpecimenOrObservationBase.
243 * This label is usually being user for citing the unit in publications.
244 */
245 public String getSummaryLabel() {
246 return summaryLabel;
247 }
248
249 /**
250 * Summary of all DerivedUnit identifiers with the label of this SpecimenOrObservationBase.
251 * This label is usually being user for citing the unit in publications.
252 */
253 public void setSummaryLabel(String summaryLabel) {
254 this.summaryLabel = summaryLabel;
255 }
256
257 public SpecimenOrObservationType getRecordBase() {
258 return recordBase;
259 }
260 public void setRecordBase(SpecimenOrObservationType specimenOrObservationType) {
261 this.recordBase = specimenOrObservationType;
262 }
263
264 public Set<DerivedUnitDTO> getDerivatives() {
265 if (this.derivatives == null){
266 this.derivatives = new HashSet<>();
267 }
268 return derivatives;
269 }
270
271 public void setDerivatives(Set<DerivedUnitDTO> derivatives) {
272 this.derivatives = derivatives;
273 updateTreeDependantData(derivatives);
274 }
275
276 public void addDerivative(DerivedUnitDTO derivate){
277 if (this.derivatives == null){
278 this.derivatives = new HashSet<>();
279 }
280 this.derivatives.add(derivate);
281 Set<DerivedUnitDTO> derivatives = new HashSet<>();
282 derivatives.add(derivate);
283 updateTreeDependantData(derivatives);
284 }
285 public void addAllDerivatives(Set<DerivedUnitDTO> derivatives){
286 if (this.derivatives == null){
287 this.derivatives = new HashSet<>();
288 }
289 this.derivatives.addAll(derivatives);
290 updateTreeDependantData(derivatives);
291 }
292
293
294
295 /**
296 * To be overwritten by implementing classes to
297 * update data which depends on the derivation tree
298 */
299 protected abstract void updateTreeDependantData(Set<DerivedUnitDTO> derivatives) ;
300
301 /**
302 * Recursively collects all derivatives from this.
303 */
304 public Collection<DerivedUnitDTO> collectDerivatives() {
305 return collectDerivatives(new HashSet<>());
306 }
307
308 /**
309 * private partner method to {@link #collectDerivatives()} for recursive calls.
310 *
311 * @param dtos
312 */
313 private Collection<DerivedUnitDTO> collectDerivatives(Set<DerivedUnitDTO> dtos) {
314 dtos.addAll(getDerivatives());
315 if(derivatives != null) {
316 for(SpecimenOrObservationBaseDTO subDto : derivatives) {
317 dtos.addAll(subDto.collectDerivatives(dtos));
318 }
319 }
320 return dtos;
321 }
322
323 public EventDTO<DerivationEvent> getDerivationEvent() {
324 return derivationEvent;
325 }
326 public void setDerivationEvent(EventDTO<DerivationEvent> derivationEvent) {
327 this.derivationEvent = derivationEvent;
328 }
329
330 public List<MediaDTO> getListOfMedia() {
331 return listOfMedia;
332 }
333 public void setListOfMedia(List<MediaDTO> listOfMedia) {
334 this.listOfMedia = listOfMedia;
335 }
336
337 protected Set<Media> collectMedia(SpecimenOrObservationBase<?> specimenOrObservation){
338 Set<Media> collectedMedia = new HashSet<>();
339 Set<SpecimenDescription> descriptions = specimenOrObservation.getSpecimenDescriptionImageGallery();
340 for (DescriptionBase<?> desc : descriptions){
341 if (desc instanceof SpecimenDescription){
342 SpecimenDescription specimenDesc = (SpecimenDescription)desc;
343 for (DescriptionElementBase element : specimenDesc.getElements()){
344 if (element.isInstanceOf(TextData.class)&& element.getFeature().equals(Feature.IMAGE())) {
345 for (Media media :element.getMedia()){
346 collectedMedia.add(media);
347 }
348 }
349 }
350 }
351 }
352 return collectedMedia;
353 }
354
355 private void addMediaAsDTO(Set<Media> media) {
356 for(Media m : media) {
357 List<MediaDTO> dtos = MediaDTO.fromEntity(m);
358 getListOfMedia().addAll(dtos);
359 }
360 }
361
362 /**
363 * @param sob
364 * The Unit to assemble the derivatives information for
365 * @param maxDepth
366 * The maximum number of derivation events levels up to which derivatives are to be assembled.
367 * <code>NULL</code> means infinitely.
368 * @param includeTypes
369 * Allows for positive filtering by {@link SpecimenOrObservationType}.
370 * Filter is disabled when <code>NULL</code>. This only affects the derivatives assembled in the
371 * {@link #derivatives} list. The <code>unitLabelsByCollection</code> are always collected for the
372 * whole bouquet of derivatives.
373 * @param unitLabelsByCollection
374 * A map to record the unit labels (most significant identifier + collection code) per collection.
375 * Optional parameter, may be <code>NULL</code>.
376 * @return
377 */
378 protected Set<DerivedUnitDTO> assembleDerivatives(SpecimenOrObservationBase<?> sob,
379 Integer maxDepth, EnumSet<SpecimenOrObservationType> includeTypes) {
380
381 boolean doDescend = maxDepth == null || maxDepth > 0;
382 Integer nextLevelMaxDepth = maxDepth != null ? maxDepth - 1 : null;
383 Set<DerivedUnitDTO> derivateDTOs = new HashSet<>();
384 // collectDerivedUnitsMaxdepth => 0 to avoid aggregation of sub ordinate
385 // derivatives at each level
386 Integer collectDerivedUnitsMaxdepth = 0;
387 for (DerivedUnit derivedUnit : sob.collectDerivedUnits(collectDerivedUnitsMaxdepth)) {
388 if(!derivedUnit.isPublish()){
389 continue;
390 }
391
392 if (doDescend && (includeTypes == null || includeTypes.contains(derivedUnit.getRecordBasis()))) {
393 SpecimenOrObservationBaseDTO derivedUnitDTO = SpecimenOrObservationDTOFactory.fromEntity(derivedUnit, nextLevelMaxDepth);
394 if (derivedUnitDTO instanceof DerivedUnitDTO) {
395 derivateDTOs.add((DerivedUnitDTO)derivedUnitDTO);
396 }
397 setHasCharacterData(isHasCharacterData() || derivedUnitDTO.isHasCharacterData());
398 // NOTE! the flags setHasDetailImage, setHasDna, setHasSpecimenScan are also set in
399 // setDerivateDataDTO(), see below
400 setHasDetailImage(isHasDetailImage() || derivedUnitDTO.isHasDetailImage());
401 setHasDna(isHasDna() || derivedUnitDTO.isHasDna());
402 setHasSpecimenScan(isHasSpecimenScan() || derivedUnitDTO.isHasSpecimenScan());
403 setHasCharacterData(isHasCharacterData() || derivedUnitDTO.isHasCharacterData());
404 }
405 }
406 return derivateDTOs;
407 }
408
409 public TermBase getKindOfUnit() {
410 return kindOfUnit;
411 }
412 public void setKindOfUnit(TermBase kindOfUnit) {
413 this.kindOfUnit = HibernateProxyHelper.deproxy(kindOfUnit);
414 }
415
416 public DefinedTerm getSex() {
417 return sex;
418 }
419 public void setSex(DefinedTerm sex) {
420 this.sex = sex;
421 }
422
423 public DefinedTerm getLifeStage() {
424 return lifeStage;
425 }
426 public void setLifeStage(DefinedTerm lifeStage) {
427 this.lifeStage = lifeStage;
428 }
429
430 public int getId() {
431 return id;
432 }
433
434 public String getIndividualCount() {
435 return individualCount;
436 }
437 public void setIndividualCount(String individualCount) {
438 this.individualCount = individualCount;
439 }
440
441 public List<DeterminationEventDTO> getDeterminations() {
442 return determinations;
443 }
444
445 public void setDeterminations(List<DeterminationEventDTO> determinations) {
446 this.determinations = determinations;
447 }
448
449 public Set<AnnotationDTO> getAnnotations() {
450 return annotations;
451 }
452
453 protected void addAnnotation(AnnotationDTO annotation) {
454 this.annotations.add(annotation);
455 }
456 }