ref #9965: revert changes and remove trailing dots
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / facade / DerivedUnitFacade.java
1 /**
2 * Copyright (C) 2007 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.facade;
10
11 import java.beans.PropertyChangeEvent;
12 import java.beans.PropertyChangeListener;
13 import java.text.ParseException;
14 import java.util.ArrayList;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Set;
20
21 import javax.persistence.Transient;
22
23 import org.apache.commons.lang.StringUtils;
24 import org.apache.log4j.Logger;
25
26 import eu.etaxonomy.cdm.api.service.IOccurrenceService;
27 import eu.etaxonomy.cdm.common.URI;
28 import eu.etaxonomy.cdm.format.occurrences.DistanceStringFormatter;
29 import eu.etaxonomy.cdm.model.agent.AgentBase;
30 import eu.etaxonomy.cdm.model.agent.Person;
31 import eu.etaxonomy.cdm.model.common.Annotation;
32 import eu.etaxonomy.cdm.model.common.CdmBase;
33 import eu.etaxonomy.cdm.model.common.IdentifiableSource;
34 import eu.etaxonomy.cdm.model.common.Identifier;
35 import eu.etaxonomy.cdm.model.common.Language;
36 import eu.etaxonomy.cdm.model.common.LanguageString;
37 import eu.etaxonomy.cdm.model.common.TimePeriod;
38 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
39 import eu.etaxonomy.cdm.model.description.Feature;
40 import eu.etaxonomy.cdm.model.description.SpecimenDescription;
41 import eu.etaxonomy.cdm.model.description.TextData;
42 import eu.etaxonomy.cdm.model.location.NamedArea;
43 import eu.etaxonomy.cdm.model.location.Point;
44 import eu.etaxonomy.cdm.model.location.ReferenceSystem;
45 import eu.etaxonomy.cdm.model.media.Media;
46 import eu.etaxonomy.cdm.model.media.Rights;
47 import eu.etaxonomy.cdm.model.name.TaxonName;
48 import eu.etaxonomy.cdm.model.occurrence.Collection;
49 import eu.etaxonomy.cdm.model.occurrence.DerivationEvent;
50 import eu.etaxonomy.cdm.model.occurrence.DerivationEventType;
51 import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
52 import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
53 import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
54 import eu.etaxonomy.cdm.model.occurrence.GatheringEvent;
55 import eu.etaxonomy.cdm.model.occurrence.PreservationMethod;
56 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
57 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;
58 import eu.etaxonomy.cdm.model.reference.IOriginalSource;
59 import eu.etaxonomy.cdm.model.reference.OriginalSourceType;
60 import eu.etaxonomy.cdm.model.reference.Reference;
61 import eu.etaxonomy.cdm.model.term.DefinedTerm;
62 import eu.etaxonomy.cdm.ref.TypedEntityReference;
63 import eu.etaxonomy.cdm.strategy.cache.occurrence.DerivedUnitDefaultCacheStrategy;
64
65 /**
66 * This class is a facade to the eu.etaxonomy.cdm.model.occurrence package from
67 * a specimen based view. It does not support all functionality available in the
68 * occurrence package.<BR>
69 * The most significant restriction is that a specimen may derive only from one
70 * direct derivation event and there must be only one field unit
71 * (gathering event) it derives from.<BR>
72 *
73 * @author a.mueller
74 * @since 14.05.2010
75 */
76 public class DerivedUnitFacade {
77 private static final String METER = "m";
78
79 @SuppressWarnings("unused")
80 private static final Logger logger = Logger.getLogger(DerivedUnitFacade.class);
81
82 private static final String notSupportMessage = "A specimen facade not supported exception has occurred at a place where this should not have happened. The developer should implement not support check properly during class initialization ";
83
84 private static final boolean CREATE = true;
85 private static final boolean CREATE_NOT = false;
86
87 private final DerivedUnitFacadeConfigurator config;
88
89 private final Map<PropertyChangeListener, CdmBase> listeners = new HashMap<>();
90
91 // Either fieldUnit or derivedUnit must not be null.
92 private FieldUnit fieldUnit;
93 private final DerivedUnit derivedUnit;
94
95 // media - the text data holding the media
96 private TextData derivedUnitMediaTextData;
97 private TextData fieldObjectMediaTextData;
98
99 private TextData ecology;
100 private TextData plantDescription;
101 private TextData lifeform;
102
103 /**
104 * Creates a derived unit facade for a new derived unit of type
105 * <code>type</code>.
106 *
107 * @param type
108 * @return
109 */
110 public static DerivedUnitFacade NewInstance(SpecimenOrObservationType type) {
111 return new DerivedUnitFacade(type, null, null);
112 }
113
114 /**
115 * Creates a derived unit facade for a new derived unit of type
116 * {@link SpecimenOrObservationType#PreservedSpecimen}.
117 *
118 * @return the derived unit facade
119 */
120 public static DerivedUnitFacade NewPreservedSpecimenInstance() {
121 return new DerivedUnitFacade(SpecimenOrObservationType.PreservedSpecimen, null, null);
122 }
123
124 /**
125 * Creates a derived unit facade for a new derived unit of type
126 * <code>type</code>.
127 *
128 * @param type
129 * @return
130 */
131 public static DerivedUnitFacade NewInstance(SpecimenOrObservationType type, FieldUnit fieldUnit) {
132 return new DerivedUnitFacade(type, fieldUnit, null);
133 }
134
135 /**
136 * Creates a derived unit facade for a new derived unit of type
137 * <code>type</code>.
138 *
139 * @param type
140 * @param fieldUnit the field unit to use
141 * @param config the facade configurator to use
142 * //TODO are there any ambiguities to solve with defining a field unit or a configurator
143 * @return
144 */
145 public static DerivedUnitFacade NewInstance(SpecimenOrObservationType type, FieldUnit fieldUnit, DerivedUnitFacadeConfigurator config) {
146 return new DerivedUnitFacade(type, fieldUnit, config);
147 }
148
149
150 /**
151 * Creates a derived unit facade for a given derived unit using the default
152 * configuration.
153 *
154 * @param derivedUnit
155 * @return
156 * @throws DerivedUnitFacadeNotSupportedException
157 */
158 public static DerivedUnitFacade NewInstance(DerivedUnit derivedUnit)
159 throws DerivedUnitFacadeNotSupportedException {
160 return new DerivedUnitFacade(derivedUnit, null);
161 }
162
163 public static DerivedUnitFacade NewInstance(DerivedUnit derivedUnit,
164 DerivedUnitFacadeConfigurator config)
165 throws DerivedUnitFacadeNotSupportedException {
166 return new DerivedUnitFacade(derivedUnit, config);
167 }
168
169 // ****************** CONSTRUCTOR ******************************************
170
171 private DerivedUnitFacade(SpecimenOrObservationType type, FieldUnit fieldUnit, DerivedUnitFacadeConfigurator config) {
172 if (config == null){
173 config = DerivedUnitFacadeConfigurator.NewInstance();
174 }
175 this.config = config;
176 // derivedUnit
177 derivedUnit = getNewDerivedUnitInstance(type);
178 //TODO parameter checking should be solved in a more generic way if we start using other entity facades
179 if(derivedUnit==null && fieldUnit==null && type.isFieldUnit()){
180 this.fieldUnit = getFieldUnit(CREATE);
181 }
182 setFieldUnit(fieldUnit);
183 if (derivedUnit != null){
184 setCacheStrategy();
185 }else{
186 setFieldUnitCacheStrategy();
187 }
188 }
189
190 private DerivedUnit getNewDerivedUnitInstance(SpecimenOrObservationType type) {
191 if (type.isFieldUnit()){
192 return null;
193 }else if(type.isAnyDerivedUnit()){
194 return DerivedUnit.NewInstance(type);
195 } else {
196 String message = "Unknown specimen or observation type %s";
197 message = String.format(message, type.getLabel());
198 throw new IllegalStateException(message);
199 }
200 }
201
202 private DerivedUnitFacade(DerivedUnit derivedUnit, DerivedUnitFacadeConfigurator config)
203 throws DerivedUnitFacadeNotSupportedException {
204
205 if(derivedUnit==null){
206 throw new IllegalArgumentException("DerivedUnit must not be null");
207 }
208
209 if (config == null) {
210 config = DerivedUnitFacadeConfigurator.NewInstance();
211 }
212 this.config = config;
213
214 // derived unit
215 this.derivedUnit = derivedUnit;
216
217 // derivation event
218 if (this.derivedUnit.getDerivedFrom() != null) {
219 DerivationEvent derivationEvent = getDerivationEvent(CREATE);
220 // fieldUnit
221 Set<FieldUnit> fieldOriginals = getFieldUnitOriginals(derivationEvent, null);
222 if (fieldOriginals.size() > 1) {
223 throw new DerivedUnitFacadeNotSupportedException(
224 "Specimen must not have more than 1 derivation event");
225 } else if (fieldOriginals.size() == 0) {
226 // fieldUnit = FieldUnit.NewInstance();
227 } else if (fieldOriginals.size() == 1) {
228 fieldUnit = fieldOriginals.iterator().next();
229 // ###fieldUnit =
230 // getInitializedFieldUnit(fieldUnit);
231 if (config.isFirePropertyChangeEvents()){
232 addNewEventPropagationListener(fieldUnit);
233 }
234 } else {
235 throw new IllegalStateException("Illegal state");
236 }
237 }
238
239 this.derivedUnitMediaTextData = inititializeTextDataWithSupportTest(Feature.IMAGE(), this.derivedUnit, false, true);
240
241 fieldObjectMediaTextData = initializeFieldObjectTextDataWithSupportTest(Feature.IMAGE(), false, true);
242
243
244 //direct media have been removed from specimenorobservationbase #3597
245 // // handle derivedUnit.getMedia()
246 // if (derivedUnit.getMedia().size() > 0) {
247 // // TODO better changed model here to allow only one place for images
248 // if (this.config.isMoveDerivedUnitMediaToGallery()) {
249 // Set<Media> mediaSet = derivedUnit.getMedia();
250 // for (Media media : mediaSet) {
251 // this.addDerivedUnitMedia(media);
252 // }
253 // mediaSet.removeAll(getDerivedUnitMedia());
254 // } else {
255 // throw new DerivedUnitFacadeNotSupportedException(
256 // "Specimen may not have direct media. Only (one) image gallery is allowed");
257 // }
258 // }
259 //
260 // // handle fieldUnit.getMedia()
261 // if (fieldUnit != null && fieldUnit.getMedia() != null
262 // && fieldUnit.getMedia().size() > 0) {
263 // // TODO better changed model here to allow only one place for images
264 // if (this.config.isMoveFieldObjectMediaToGallery()) {
265 // Set<Media> mediaSet = fieldUnit.getMedia();
266 // for (Media media : mediaSet) {
267 // this.addFieldObjectMedia(media);
268 // }
269 // mediaSet.removeAll(getFieldObjectMedia());
270 // } else {
271 // throw new DerivedUnitFacadeNotSupportedException(
272 // "Field object may not have direct media. Only (one) image gallery is allowed");
273 // }
274 // }
275
276 // test if descriptions are supported
277 ecology = initializeFieldObjectTextDataWithSupportTest(
278 Feature.ECOLOGY(), false, false);
279 plantDescription = initializeFieldObjectTextDataWithSupportTest(
280 Feature.DESCRIPTION(), false, false);
281
282 setCacheStrategy();
283
284 }
285
286 private DerivedUnit getInitializedDerivedUnit(
287 DerivedUnit derivedUnit) {
288 IOccurrenceService occurrenceService = this.config
289 .getOccurrenceService();
290 if (occurrenceService == null) {
291 return derivedUnit;
292 }
293 List<String> propertyPaths = this.config.getPropertyPaths();
294 if (propertyPaths == null) {
295 return derivedUnit;
296 }
297 propertyPaths = getDerivedUnitPropertyPaths(propertyPaths);
298 DerivedUnit result = (DerivedUnit) occurrenceService.load(
299 derivedUnit.getUuid(), propertyPaths);
300 return result;
301 }
302
303 /**
304 * Initializes the derived unit according to the configuartions property
305 * path. If the property path is <code>null</code> or no occurrence service
306 * is given the returned object is the same as the input parameter.
307 *
308 * @param fieldUnit
309 * @return
310 */
311 private FieldUnit getInitializedFieldUnit(FieldUnit fieldUnit) {
312 IOccurrenceService occurrenceService = this.config
313 .getOccurrenceService();
314 if (occurrenceService == null) {
315 return fieldUnit;
316 }
317 List<String> propertyPaths = this.config.getPropertyPaths();
318 if (propertyPaths == null) {
319 return fieldUnit;
320 }
321 propertyPaths = getFieldObjectPropertyPaths(propertyPaths);
322 FieldUnit result = (FieldUnit) occurrenceService.load(
323 fieldUnit.getUuid(), propertyPaths);
324 return result;
325 }
326
327 /**
328 * Transforms the property paths in a way that the facade is handled just
329 * like an ordinary CdmBase object.<BR>
330 * E.g. a property path "collectinAreas" will be translated into
331 * gatheringEvent.collectingAreas
332 *
333 * @param propertyPaths
334 * @return
335 */
336 private List<String> getFieldObjectPropertyPaths(List<String> propertyPaths) {
337 List<String> result = new ArrayList<String>();
338 for (String facadePath : propertyPaths) {
339 // collecting areas (named area)
340 if (facadePath.startsWith("collectingAreas")) {
341 facadePath = "gatheringEvent." + facadePath;
342 result.add(facadePath);
343 }
344 // collector (agentBase)
345 else if (facadePath.startsWith("collector")) {
346 facadePath = facadePath.replace("collector",
347 "gatheringEvent.actor");
348 result.add(facadePath);
349 }
350 // exactLocation (agentBase)
351 else if (facadePath.startsWith("exactLocation")) {
352 facadePath = "gatheringEvent." + facadePath;
353 result.add(facadePath);
354 }
355 // gatheringPeriod (TimePeriod)
356 else if (facadePath.startsWith("gatheringPeriod")) {
357 facadePath = facadePath.replace("gatheringPeriod",
358 "gatheringEvent.timeperiod");
359 result.add(facadePath);
360 }
361 // (locality/ localityLanguage , LanguageString)
362 else if (facadePath.startsWith("locality")) {
363 facadePath = "gatheringEvent." + facadePath;
364 result.add(facadePath);
365 }
366
367 // *********** FIELD OBJECT ************
368 // fieldObjectDefinitions (Map<language, languageString)
369 else if (facadePath.startsWith("fieldObjectDefinitions")) {
370 // TODO or definition ???
371 facadePath = facadePath.replace("fieldObjectDefinitions",
372 "description");
373 result.add(facadePath);
374 }
375 // fieldObjectMedia (Media)
376 else if (facadePath.startsWith("fieldObjectMedia")) {
377 // TODO ???
378 facadePath = facadePath.replace("fieldObjectMedia",
379 "descriptions.elements.media");
380 result.add(facadePath);
381 }
382
383 // Gathering Event will always be added
384 result.add("gatheringEvent");
385
386 }
387
388 /*
389 * Gathering Event ==================== - gatheringEvent
390 * (GatheringEvent)
391 *
392 * Field Object ================= - ecology/ ecologyAll (String) ??? -
393 * plant description (like ecology)
394 *
395 * - fieldObjectImageGallery (SpecimenDescription) - is automatically
396 * initialized via fieldObjectMedia
397 */
398
399 return result;
400 }
401
402 /**
403 * Transforms the property paths in a way that the facade is handled just
404 * like an ordinary CdmBase object.<BR>
405 * E.g. a property path "collectinAreas" will be translated into
406 * gatheringEvent.collectingAreas
407 *
408 * Not needed (?) as the facade works with REST service property paths
409 * without using this method.
410 *
411 * @param propertyPaths
412 * @return
413 */
414 private List<String> getDerivedUnitPropertyPaths(List<String> propertyPaths) {
415 List<String> result = new ArrayList<String>();
416 for (String facadePath : propertyPaths) {
417 // determinations (DeterminationEvent)
418 if (facadePath.startsWith("determinations")) {
419 facadePath = "" + facadePath; // no change
420 result.add(facadePath);
421 }
422 // storedUnder (TaxonName)
423 else if (facadePath.startsWith("storedUnder")) {
424 facadePath = "" + facadePath; // no change
425 result.add(facadePath);
426 }
427 // sources (IdentifiableSource)
428 else if (facadePath.startsWith("sources")) {
429 facadePath = "" + facadePath; // no change
430 result.add(facadePath);
431 }
432 // collection (Collection)
433 else if (facadePath.startsWith("collection")) {
434 facadePath = "" + facadePath; // no change
435 result.add(facadePath);
436 }
437 // (locality/ localityLanguage , LanguageString)
438 else if (facadePath.startsWith("locality")) {
439 facadePath = "gatheringEvent." + facadePath;
440 result.add(facadePath);
441 }
442
443 // *********** FIELD OBJECT ************
444 // derivedUnitDefinitions (Map<language, languageString)
445 else if (facadePath.startsWith("derivedUnitDefinitions")) {
446 // TODO or definition ???
447 facadePath = facadePath.replace("derivedUnitDefinitions",
448 "description");
449 result.add(facadePath);
450 }
451
452 // derivedUnitMedia (Media)
453 else if (facadePath.startsWith("derivedUnitMedia")) {
454 // TODO ???
455 facadePath = facadePath.replace("derivedUnitMedia",
456 "descriptions.elements.media");
457 result.add(facadePath);
458 }
459
460 }
461
462 /*
463 * //TODO Derived Unit =====================
464 *
465 * - derivedUnitImageGallery (SpecimenDescription) - is automatically
466 * initialized via derivedUnitMedia
467 *
468 * - derivationEvent (DerivationEvent) - will always be initialized -
469 * duplicates (??? Specimen???) ???
470 */
471
472 return result;
473 }
474
475 private void setCacheStrategy() {
476 if (derivedUnit == null) {
477 throw new NullPointerException(
478 "Facade's derviedUnit must not be null to set cache strategy");
479 }else{
480 derivedUnit.setCacheStrategy(DerivedUnitFacadeCacheStrategy.NewInstance(false));
481 setFieldUnitCacheStrategy();
482 }
483 }
484
485 private void setFieldUnitCacheStrategy() {
486 if (this.hasFieldObject()){
487 DerivedUnitFacadeFieldUnitCacheStrategy strategy = new DerivedUnitFacadeFieldUnitCacheStrategy();
488 this.fieldUnit.setCacheStrategy(strategy);
489 }
490 }
491
492 private TextData initializeFieldObjectTextDataWithSupportTest(
493 Feature feature, boolean createIfNotExists, boolean isImageGallery)
494 throws DerivedUnitFacadeNotSupportedException {
495 // field object
496 FieldUnit fieldObject = getFieldUnit(createIfNotExists);
497 if (fieldObject == null) {
498 return null;
499 }
500 return inititializeTextDataWithSupportTest(feature, fieldObject,
501 createIfNotExists, isImageGallery);
502 }
503
504 /**
505 * @param feature
506 * @param specimen
507 * @param createIfNotExists
508 * @param isImageGallery
509 * @return
510 * @throws DerivedUnitFacadeNotSupportedException
511 */
512 private TextData inititializeTextDataWithSupportTest(Feature feature,
513 SpecimenOrObservationBase<?> specimen, boolean createIfNotExists,
514 boolean isImageGallery)
515 throws DerivedUnitFacadeNotSupportedException {
516 if (feature == null) {
517 return null;
518 }
519 TextData textData = null;
520 if (createIfNotExists) {
521 textData = TextData.NewInstance(feature);
522 }
523
524 Set<SpecimenDescription> descriptions;
525 if (isImageGallery) {
526 descriptions = specimen.getSpecimenDescriptionImageGallery();
527 } else {
528 descriptions = specimen.getSpecimenDescriptions(false);
529 }
530 // no description exists yet for this specimen
531 if (descriptions.size() == 0) {
532 if (createIfNotExists) {
533 SpecimenDescription newSpecimenDescription = SpecimenDescription
534 .NewInstance(specimen);
535 newSpecimenDescription.addElement(textData);
536 newSpecimenDescription.setImageGallery(isImageGallery);
537 return textData;
538 } else {
539 return null;
540 }
541 }
542 // description already exists
543 Set<DescriptionElementBase> existingTextData = new HashSet<>();
544 for (SpecimenDescription description : descriptions) {
545 // collect all existing text data
546 for (DescriptionElementBase element : description.getElements()) {
547 if (element.isInstanceOf(TextData.class)
548 && (feature.equals(element.getFeature()) || isImageGallery)) {
549 existingTextData.add(element);
550 }
551 }
552 }
553 // use existing text data if exactly one exists
554 if (existingTextData.size() > 1) {
555 throw new DerivedUnitFacadeNotSupportedException(
556 "Specimen facade does not support more than one description text data of type "
557 + feature.getLabel());
558
559 } else if (existingTextData.size() == 1) {
560 return CdmBase.deproxy(existingTextData.iterator().next(),
561 TextData.class);
562 } else {
563 if (createIfNotExists) {
564 SpecimenDescription description = descriptions.iterator()
565 .next();
566 description.addElement(textData);
567 }
568 return textData;
569 }
570 }
571
572 /**
573 * Tests if a given image gallery is supported by the derived unit facade.
574 * It returns the only text data attached to the given image gallery. If the
575 * given image gallery does not have text data attached, it is created and
576 * attached.
577 *
578 * @param imageGallery
579 * @return
580 * @throws DerivedUnitFacadeNotSupportedException
581 */
582 private TextData testImageGallery(SpecimenDescription imageGallery)
583 throws DerivedUnitFacadeNotSupportedException {
584 if (imageGallery.isImageGallery() == false) {
585 throw new DerivedUnitFacadeNotSupportedException(
586 "Image gallery needs to have image gallery flag set");
587 }
588 if (imageGallery.getElements().size() > 1) {
589 throw new DerivedUnitFacadeNotSupportedException(
590 "Image gallery must not have more then one description element");
591 }
592 TextData textData;
593 if (imageGallery.getElements().size() == 0) {
594 textData = TextData.NewInstance(Feature.IMAGE());
595 imageGallery.addElement(textData);
596 } else {
597 if (!imageGallery.getElements().iterator().next()
598 .isInstanceOf(TextData.class)) {
599 throw new DerivedUnitFacadeNotSupportedException(
600 "Image gallery must only have TextData as element");
601 } else {
602 textData = CdmBase.deproxy(imageGallery.getElements()
603 .iterator().next(), TextData.class);
604 }
605 }
606 return textData;
607 }
608
609 // ************************** METHODS
610 // *****************************************
611
612 private TextData getDerivedUnitImageGalleryTextData(
613 boolean createIfNotExists)
614 throws DerivedUnitFacadeNotSupportedException {
615 if (this.derivedUnitMediaTextData == null && createIfNotExists) {
616 this.derivedUnitMediaTextData = getImageGalleryTextData(
617 derivedUnit, "Specimen");
618 }
619 return this.derivedUnitMediaTextData;
620 }
621
622 private TextData getObservationImageGalleryTextData(
623 boolean createIfNotExists)
624 throws DerivedUnitFacadeNotSupportedException {
625 if (this.fieldObjectMediaTextData == null && createIfNotExists) {
626 this.fieldObjectMediaTextData = getImageGalleryTextData(fieldUnit, "Field unit");
627 }
628 return this.fieldObjectMediaTextData;
629 }
630
631 /**
632 * @param derivationEvent
633 * @return
634 * @throws DerivedUnitFacadeNotSupportedException
635 */
636 private Set<FieldUnit> getFieldUnitOriginals(
637 DerivationEvent derivationEvent,
638 Set<SpecimenOrObservationBase> recursionAvoidSet)
639 throws DerivedUnitFacadeNotSupportedException {
640 if (recursionAvoidSet == null) {
641 recursionAvoidSet = new HashSet<>();
642 }
643 Set<FieldUnit> result = new HashSet<>();
644 Set<SpecimenOrObservationBase> originals = derivationEvent.getOriginals();
645 for (SpecimenOrObservationBase original : originals) {
646 if (original.isInstanceOf(FieldUnit.class)) {
647 result.add(CdmBase.deproxy(original, FieldUnit.class));
648 } else if (original.isInstanceOf(DerivedUnit.class)) {
649 // if specimen has already been tested exclude it from further
650 // recursion
651 if (recursionAvoidSet.contains(original)) {
652 continue;
653 }
654 DerivedUnit derivedUnit = CdmBase.deproxy(original, DerivedUnit.class);
655 DerivationEvent originalDerivation = derivedUnit.getDerivedFrom();
656 // Set<DerivationEvent> derivationEvents =
657 // original.getDerivationEvents();
658 // for (DerivationEvent originalDerivation : derivationEvents){
659 if(originalDerivation!=null){
660 Set<FieldUnit> fieldUnits = getFieldUnitOriginals(
661 originalDerivation, recursionAvoidSet);
662 result.addAll(fieldUnits);
663 }
664 // }
665 } else {
666 throw new DerivedUnitFacadeNotSupportedException(
667 "Unhandled specimen or observation base type: "
668 + original.getClass().getName());
669 }
670
671 }
672 return result;
673 }
674
675 // *********** MEDIA METHODS ******************************
676
677 // /**
678 // * Returns the media list for a specimen. Throws an exception if the
679 // existing specimen descriptions
680 // * are not supported by this facade.
681 // * @param specimen the specimen the media belongs to
682 // * @param specimenExceptionText text describing the specimen for exception
683 // messages
684 // * @return
685 // * @throws DerivedUnitFacadeNotSupportedException
686 // */
687 // private List<Media> getImageGalleryMedia(SpecimenOrObservationBase
688 // specimen, String specimenExceptionText) throws
689 // DerivedUnitFacadeNotSupportedException{
690 // List<Media> result;
691 // SpecimenDescription imageGallery =
692 // getImageGalleryWithSupportTest(specimen, specimenExceptionText, true);
693 // TextData textData = getImageTextDataWithSupportTest(imageGallery,
694 // specimenExceptionText);
695 // result = textData.getMedia();
696 // return result;
697 // }
698
699 /**
700 * Returns the media list for a specimen. Throws an exception if the
701 * existing specimen descriptions are not supported by this facade.
702 *
703 * @param specimen
704 * the specimen the media belongs to
705 * @param specimenExceptionText
706 * text describing the specimen for exception messages
707 * @return
708 * @throws DerivedUnitFacadeNotSupportedException
709 */
710 private TextData getImageGalleryTextData(SpecimenOrObservationBase specimen, String specimenExceptionText)
711 throws DerivedUnitFacadeNotSupportedException {
712 TextData result;
713 SpecimenDescription imageGallery = getImageGalleryWithSupportTest(
714 specimen, specimenExceptionText, true);
715 result = getImageTextDataWithSupportTest(imageGallery,
716 specimenExceptionText);
717 return result;
718 }
719
720 /**
721 * Returns the image gallery of the according specimen. Throws an exception
722 * if the attached image gallerie(s) are not supported by this facade. If no
723 * image gallery exists a new one is created if
724 * <code>createNewIfNotExists</code> is true and if specimen is not
725 * <code>null</code>.
726 *
727 * @param specimen
728 * @param specimenText
729 * @param createNewIfNotExists
730 * @return
731 * @throws DerivedUnitFacadeNotSupportedException
732 */
733 private SpecimenDescription getImageGalleryWithSupportTest(
734 SpecimenOrObservationBase<?> specimen, String specimenText,
735 boolean createNewIfNotExists)
736 throws DerivedUnitFacadeNotSupportedException {
737 if (specimen == null) {
738 return null;
739 }
740 SpecimenDescription imageGallery;
741 if (hasMultipleImageGalleries(specimen)) {
742 throw new DerivedUnitFacadeNotSupportedException(specimenText
743 + " must not have more than 1 image gallery");
744 } else {
745 imageGallery = getImageGallery(specimen, createNewIfNotExists);
746 getImageTextDataWithSupportTest(imageGallery, specimenText);
747 }
748 return imageGallery;
749 }
750
751 /**
752 * Returns the media holding text data element of the image gallery. Throws
753 * an exception if multiple such text data already exist. Creates a new text
754 * data if none exists and adds it to the image gallery. If image gallery is
755 * <code>null</code> nothing happens.
756 *
757 * @param imageGallery
758 * @param textData
759 * @return
760 * @throws DerivedUnitFacadeNotSupportedException
761 */
762 private TextData getImageTextDataWithSupportTest(
763 SpecimenDescription imageGallery, String specimenText)
764 throws DerivedUnitFacadeNotSupportedException {
765 if (imageGallery == null) {
766 return null;
767 }
768 TextData textData = null;
769 for (DescriptionElementBase element : imageGallery.getElements()) {
770 if (element.isInstanceOf(TextData.class)
771 && element.getFeature().equals(Feature.IMAGE())) {
772 if (textData != null) {
773 throw new DerivedUnitFacadeNotSupportedException(
774 specimenText
775 + " must not have more than 1 image text data element in image gallery");
776 }
777 textData = CdmBase.deproxy(element, TextData.class);
778 }
779 }
780 if (textData == null) {
781 textData = TextData.NewInstance(Feature.IMAGE());
782 imageGallery.addElement(textData);
783 }
784 return textData;
785 }
786
787 /**
788 * Checks, if a specimen belongs to more than one description that is an
789 * image gallery
790 *
791 * @param derivedUnit
792 * @return
793 */
794 private boolean hasMultipleImageGalleries(
795 SpecimenOrObservationBase<?> derivedUnit) {
796 int count = 0;
797 Set<SpecimenDescription> descriptions = derivedUnit
798 .getSpecimenDescriptions();
799 for (SpecimenDescription description : descriptions) {
800 if (description.isImageGallery()) {
801 count++;
802 }
803 }
804 return (count > 1);
805 }
806
807 /**
808 * Returns the image gallery for a specimen. If there are multiple specimen
809 * descriptions marked as image galleries an arbitrary one is chosen. If no
810 * image gallery exists, a new one is created if
811 * <code>createNewIfNotExists</code> is <code>true</code>.<Br>
812 * If specimen is <code>null</code> a null pointer exception is thrown.
813 *
814 * @param createNewIfNotExists
815 * @return
816 */
817 private SpecimenDescription getImageGallery(SpecimenOrObservationBase<?> specimen, boolean createIfNotExists) {
818 SpecimenDescription result = null;
819 Set<SpecimenDescription> descriptions = specimen.getSpecimenDescriptions();
820 for (SpecimenDescription description : descriptions) {
821 if (description.isImageGallery()) {
822 result = description;
823 break;
824 }
825 }
826 if (result == null && createIfNotExists) {
827 result = SpecimenDescription.NewInstance(specimen);
828 result.setImageGallery(true);
829 }
830 return result;
831 }
832
833 /**
834 * Adds a media to the specimens image gallery. If media is
835 * <code>null</code> nothing happens.
836 *
837 * @param media
838 * @param specimen
839 * @return true if media is not null (as specified by
840 * {@link java.util.Collection#add(Object) Collection.add(E e)}
841 * @throws DerivedUnitFacadeNotSupportedException
842 */
843 private boolean addMedia(Media media, SpecimenOrObservationBase<?> specimen) throws DerivedUnitFacadeNotSupportedException {
844 if (media != null) {
845 List<Media> mediaList = getMediaList(specimen, true);
846 if (! mediaList.contains(media)){
847 return mediaList.add(media);
848 }else{
849 return true;
850 }
851 } else {
852 return false;
853 }
854 }
855
856 /**
857 * Removes a media from the specimens image gallery.
858 *
859 * @param media
860 * @param specimen
861 * @return true if an element was removed as a result of this call (as
862 * specified by {@link java.util.Collection#remove(Object)
863 * Collection.remove(E e)}
864 * @throws DerivedUnitFacadeNotSupportedException
865 */
866 private boolean removeMedia(Media media,
867 SpecimenOrObservationBase<?> specimen)
868 throws DerivedUnitFacadeNotSupportedException {
869 List<Media> mediaList = getMediaList(specimen, true);
870 return mediaList == null ? null : mediaList.remove(media);
871 }
872
873 private List<Media> getMediaList(SpecimenOrObservationBase<?> specimen, boolean createIfNotExists)
874 throws DerivedUnitFacadeNotSupportedException {
875 TextData textData = getMediaTextData(specimen, createIfNotExists);
876 return textData == null ? null : textData.getMedia();
877 }
878
879 /**
880 * Returns the one media list of a specimen which is part of the only image
881 * gallery that this specimen is part of.<BR>
882 * If these conditions are not hold an exception is thrwon.
883 *
884 * @param specimen
885 * @return
886 * @throws DerivedUnitFacadeNotSupportedException
887 */
888 // private List<Media> getMedia(SpecimenOrObservationBase<?> specimen)
889 // throws DerivedUnitFacadeNotSupportedException {
890 // if (specimen == null){
891 // return null;
892 // }
893 // if (specimen == this.derivedUnit){
894 // return getDerivedUnitImageGalleryMedia();
895 // }else if (specimen == this.fieldUnit){
896 // return getObservationImageGalleryTextData();
897 // }else{
898 // return getImageGalleryMedia(specimen, "Undefined specimen ");
899 // }
900 // }
901
902 /**
903 * Returns the one media list of a specimen which is part of the only image
904 * gallery that this specimen is part of.<BR>
905 * If these conditions are not hold an exception is thrown.
906 *
907 * @param specimen
908 * @return
909 * @throws DerivedUnitFacadeNotSupportedException
910 */
911 private TextData getMediaTextData(SpecimenOrObservationBase<?> specimen,
912 boolean createIfNotExists)
913 throws DerivedUnitFacadeNotSupportedException {
914 if (specimen == null) {
915 return null;
916 }
917 if (specimen == this.derivedUnit) {
918 return getDerivedUnitImageGalleryTextData(createIfNotExists);
919 } else if (specimen == this.fieldUnit) {
920 return getObservationImageGalleryTextData(createIfNotExists);
921 } else {
922 return getImageGalleryTextData(specimen, "Undefined specimen ");
923 }
924 }
925
926 // ****************** GETTER / SETTER / ADDER / REMOVER
927 // ***********************/
928
929 // ****************** Gathering Event *********************************/
930
931 // country
932 @Transient
933 public NamedArea getCountry() {
934 return (hasGatheringEvent() ? getGatheringEvent(true).getCountry()
935 : null);
936 }
937
938 public void setCountry(NamedArea country) {
939 getGatheringEvent(true).setCountry(country);
940 }
941
942 // Collecting area
943 public void addCollectingArea(NamedArea area) {
944 getGatheringEvent(true).addCollectingArea(area);
945 }
946
947 public void addCollectingAreas(java.util.Collection<NamedArea> areas) {
948 for (NamedArea area : areas) {
949 getGatheringEvent(true).addCollectingArea(area);
950 }
951 }
952
953 @Transient
954 public Set<NamedArea> getCollectingAreas() {
955 return (hasGatheringEvent() ? getGatheringEvent(true)
956 .getCollectingAreas() : null);
957 }
958
959 public void removeCollectingArea(NamedArea area) {
960 if (hasGatheringEvent()) {
961 getGatheringEvent(true).removeCollectingArea(area);
962 }
963 }
964
965 static final String ALTITUDE_POSTFIX = " m";
966
967 /**
968 * Returns the correctly formatted <code>absolute elevation</code> information.
969 * If absoluteElevationText is set, this will be returned,
970 * otherwise we absoluteElevation will be returned, followed by absoluteElevationMax
971 * if existing, separated by " - "
972 * @return
973 */
974 @Transient
975 public String absoluteElevationToString() {
976 if (! hasGatheringEvent()){
977 return null;
978 }else{
979 GatheringEvent ev = getGatheringEvent(true);
980 if (StringUtils.isNotBlank(ev.getAbsoluteElevationText())){
981 return ev.getAbsoluteElevationText();
982 }else{
983 String text = ev.getAbsoluteElevationText();
984 Integer min = getAbsoluteElevation();
985 Integer max = getAbsoluteElevationMaximum();
986 return DistanceStringFormatter.distanceString(min, max, text, METER);
987 }
988 }
989 }
990
991
992 /**
993 * meter above/below sea level of the surface
994 *
995 * @see #getAbsoluteElevationError()
996 * @see #getAbsoluteElevationRange()
997 **/
998 @Transient
999 public Integer getAbsoluteElevation() {
1000 return (hasGatheringEvent() ? getGatheringEvent(true).getAbsoluteElevation() : null);
1001 }
1002
1003 public void setAbsoluteElevation(Integer absoluteElevation) {
1004 getGatheringEvent(true).setAbsoluteElevation(absoluteElevation);
1005 }
1006
1007 public void setAbsoluteElevationMax(Integer absoluteElevationMax) {
1008 getGatheringEvent(true).setAbsoluteElevationMax(absoluteElevationMax);
1009 }
1010
1011 public void setAbsoluteElevationText(String absoluteElevationText) {
1012 getGatheringEvent(true).setAbsoluteElevationText(absoluteElevationText);
1013 }
1014
1015 /**
1016 * @see #getAbsoluteElevation()
1017 * @see #getAbsoluteElevationError()
1018 * @see #setAbsoluteElevationRange(Integer, Integer)
1019 * @see #getAbsoluteElevationMinimum()
1020 */
1021 @Transient
1022 public Integer getAbsoluteElevationMaximum() {
1023 if (!hasGatheringEvent()) {
1024 return null;
1025 }else{
1026 return getGatheringEvent(true).getAbsoluteElevationMax();
1027 }
1028 }
1029
1030 /**
1031 * @see #getAbsoluteElevation()
1032 * @see #getAbsoluteElevationError()
1033 * @see #setAbsoluteElevationRange(Integer, Integer)
1034 * @see #getAbsoluteElevationMinimum()
1035 */
1036 @Transient
1037 public String getAbsoluteElevationText() {
1038 if (!hasGatheringEvent()) {
1039 return null;
1040 }else{
1041 return getGatheringEvent(true).getAbsoluteElevationText();
1042 }
1043 }
1044
1045 /**
1046 * Convenience method to set absolute elevation minimum and maximum.
1047 *
1048 * @see #setAbsoluteElevation(Integer)
1049 * @see #setAbsoluteElevationMax(Integer)
1050 * @param minimumElevation minimum of the range
1051 * @param maximumElevation maximum of the range
1052 */
1053 public void setAbsoluteElevationRange(Integer minimumElevation, Integer maximumElevation) {
1054 getGatheringEvent(true).setAbsoluteElevation(minimumElevation);
1055 getGatheringEvent(true).setAbsoluteElevationMax(maximumElevation);
1056 }
1057
1058 // collector
1059 @Transient
1060 public AgentBase getCollector() {
1061 return (hasGatheringEvent() ? getGatheringEvent(true).getCollector()
1062 : null);
1063 }
1064
1065 public void setCollector(AgentBase collector) {
1066 getGatheringEvent(true).setCollector(collector);
1067 }
1068
1069 // collecting method
1070 @Transient
1071 public String getCollectingMethod() {
1072 return (hasGatheringEvent() ? getGatheringEvent(true).getCollectingMethod() : null);
1073 }
1074
1075 public void setCollectingMethod(String collectingMethod) {
1076 getGatheringEvent(true).setCollectingMethod(collectingMethod);
1077 }
1078
1079 // distance to ground
1080
1081 /**
1082 * Returns the correctly formatted <code>distance to ground</code> information.
1083 * If distanceToGroundText is not blank, it will be returned,
1084 * otherwise distanceToGround will be returned, followed by distanceToGroundMax
1085 * if existing, separated by " - "
1086 * @return
1087 */
1088 @Transient
1089 public String distanceToGroundToString() {
1090 if (! hasGatheringEvent()){
1091 return null;
1092 }else{
1093 GatheringEvent ev = getGatheringEvent(true);
1094 String text = ev.getDistanceToGroundText();
1095 Double min = getDistanceToGround();
1096 Double max = getDistanceToGroundMax();
1097 return DistanceStringFormatter.distanceString(min, max, text, METER);
1098 }
1099 }
1100
1101 @Transient
1102 public Double getDistanceToGround() {
1103 return (hasGatheringEvent() ? getGatheringEvent(true).getDistanceToGround() : null);
1104 }
1105
1106 public void setDistanceToGround(Double distanceToGround) {
1107 getGatheringEvent(true).setDistanceToGround(distanceToGround);
1108 }
1109
1110 /**
1111 * @see #getDistanceToGround()
1112 * @see #getDistanceToGroundRange(Integer, Integer)
1113 */
1114 @Transient
1115 public Double getDistanceToGroundMax() {
1116 if (!hasGatheringEvent()) {
1117 return null;
1118 }else{
1119 return getGatheringEvent(true).getDistanceToGroundMax();
1120 }
1121 }
1122
1123 public void setDistanceToGroundMax(Double distanceToGroundMax) {
1124 getGatheringEvent(true).setDistanceToGroundMax(distanceToGroundMax);
1125 }
1126
1127 /**
1128 * @see #getDistanceToGround()
1129 * @see #setDistanceToGroundRange(Integer, Integer)
1130 */
1131 @Transient
1132 public String getDistanceToGroundText() {
1133 if (!hasGatheringEvent()) {
1134 return null;
1135 }else{
1136 return getGatheringEvent(true).getDistanceToGroundText();
1137 }
1138 }
1139 public void setDistanceToGroundText(String distanceToGroundText) {
1140 getGatheringEvent(true).setDistanceToGroundText(distanceToGroundText);
1141 }
1142
1143 /**
1144 * Convenience method to set distance to ground minimum and maximum.
1145 *
1146 * @see #getDistanceToGround()
1147 * @see #getDistanceToGroundMax()
1148 * @param minimumDistance minimum of the range
1149 * @param maximumDistance maximum of the range
1150 */
1151 public void setDistanceToGroundRange(Double minimumDistance, Double maximumDistance) throws IllegalArgumentException{
1152 getGatheringEvent(true).setDistanceToGround(minimumDistance);
1153 getGatheringEvent(true).setDistanceToGroundMax(maximumDistance);
1154 }
1155
1156
1157 /**
1158 * Returns the correctly formatted <code>distance to water surface</code> information.
1159 * If distanceToWaterSurfaceText is not blank, it will be returned,
1160 * otherwise distanceToWaterSurface will be returned, followed by distanceToWatersurfaceMax
1161 * if existing, separated by " - "
1162 * @return
1163 */
1164 @Transient
1165 public String distanceToWaterSurfaceToString() {
1166 if (! hasGatheringEvent()){
1167 return null;
1168 }else{
1169 GatheringEvent ev = getGatheringEvent(true);
1170 String text = ev.getDistanceToWaterSurfaceText();
1171 Double min = getDistanceToWaterSurface();
1172 Double max = getDistanceToWaterSurfaceMax();
1173 return DistanceStringFormatter.distanceString(min, max, text, METER);
1174 }
1175 }
1176
1177 // distance to water surface
1178 @Transient
1179 public Double getDistanceToWaterSurface() {
1180 return (hasGatheringEvent() ? getGatheringEvent(true).getDistanceToWaterSurface() : null);
1181 }
1182
1183 public void setDistanceToWaterSurface(Double distanceToWaterSurface) {
1184 getGatheringEvent(true).setDistanceToWaterSurface(distanceToWaterSurface);
1185 }
1186
1187 /**
1188 * @see #getDistanceToWaterSurface()
1189 * @see #getDistanceToWaterSurfaceRange(Double, Double)
1190 */
1191 @Transient
1192 public Double getDistanceToWaterSurfaceMax() {
1193 if (!hasGatheringEvent()) {
1194 return null;
1195 }else{
1196 return getGatheringEvent(true).getDistanceToWaterSurfaceMax();
1197 }
1198 }
1199
1200 public void setDistanceToWaterSurfaceMax(Double distanceToWaterSurfaceMax) {
1201 getGatheringEvent(true).setDistanceToWaterSurfaceMax(distanceToWaterSurfaceMax);
1202 }
1203
1204 /**
1205 * @see #getDistanceToWaterSurface()
1206 * @see #getDistanceToWaterSurfaceRange(Double, Double)
1207 */
1208 @Transient
1209 public String getDistanceToWaterSurfaceText() {
1210 if (!hasGatheringEvent()) {
1211 return null;
1212 }else{
1213 return getGatheringEvent(true).getDistanceToWaterSurfaceText();
1214 }
1215 }
1216 public void setDistanceToWaterSurfaceText(String distanceToWaterSurfaceText) {
1217 getGatheringEvent(true).setDistanceToWaterSurfaceText(distanceToWaterSurfaceText);
1218 }
1219
1220 /**
1221 * Convenience method to set distance to ground minimum and maximum.
1222 *
1223 * @see #getDistanceToWaterSurface()
1224 * @see #getDistanceToWaterSurfaceMax()
1225 * @param minimumDistance minimum of the range, this is the distance which is closer to the water surface
1226 * @param maximumDistance maximum of the range, this is the distance which is farer to the water surface
1227 */
1228 public void setDistanceToWaterSurfaceRange(Double minimumDistance, Double maximumDistance) throws IllegalArgumentException{
1229 getGatheringEvent(true).setDistanceToWaterSurface(minimumDistance);
1230 getGatheringEvent(true).setDistanceToWaterSurfaceMax(maximumDistance);
1231 }
1232
1233
1234 // exact location
1235 @Transient
1236 public Point getExactLocation() {
1237 return (hasGatheringEvent() ? getGatheringEvent(true).getExactLocation() : null);
1238 }
1239
1240 /**
1241 * Returns a sexagesimal representation of the exact location (e.g.
1242 * 12°59'N, 35°23E). If the exact location is <code>null</code> the empty
1243 * string is returned.
1244 *
1245 * @param includeEmptySeconds
1246 * @param includeReferenceSystem
1247 * @return
1248 */
1249 public String getExactLocationText(boolean includeEmptySeconds,
1250 boolean includeReferenceSystem) {
1251 return (this.getExactLocation() == null ? "" : this.getExactLocation()
1252 .toSexagesimalString(includeEmptySeconds,
1253 includeReferenceSystem));
1254 }
1255
1256 public void setExactLocation(Point exactLocation) {
1257 getGatheringEvent(true).setExactLocation(exactLocation);
1258 }
1259
1260 public void setExactLocationByParsing(String longitudeToParse,
1261 String latitudeToParse, ReferenceSystem referenceSystem,
1262 Integer errorRadius) throws ParseException {
1263 Point point = Point.NewInstance(null, null, referenceSystem,
1264 errorRadius);
1265 point.setLongitudeByParsing(longitudeToParse);
1266 point.setLatitudeByParsing(latitudeToParse);
1267 setExactLocation(point);
1268 }
1269
1270 // gathering event description
1271 @Transient
1272 public String getGatheringEventDescription() {
1273 return (hasGatheringEvent() ? getGatheringEvent(true).getDescription()
1274 : null);
1275 }
1276
1277 public void setGatheringEventDescription(String description) {
1278 getGatheringEvent(true).setDescription(description);
1279 }
1280
1281 // gathering period
1282 @Transient
1283 public TimePeriod getGatheringPeriod() {
1284 return (hasGatheringEvent() ? getGatheringEvent(true).getTimeperiod()
1285 : null);
1286 }
1287
1288 public void setGatheringPeriod(TimePeriod timeperiod) {
1289 getGatheringEvent(true).setTimeperiod(timeperiod);
1290 }
1291
1292 // locality
1293 @Transient
1294 public LanguageString getLocality() {
1295 return (hasGatheringEvent() ? getGatheringEvent(true).getLocality()
1296 : null);
1297 }
1298
1299 /**
1300 * convienience method for {@link #getLocality()}.
1301 * {@link LanguageString#getText() getText()}
1302 */
1303 @Transient
1304 public String getLocalityText() {
1305 LanguageString locality = getLocality();
1306 if (locality != null) {
1307 return locality.getText();
1308 }
1309 return null;
1310 }
1311
1312 /**
1313 * Convenience method for {@link #getLocality()}.
1314 * {@link LanguageString#getLanguage() getLanguage()}
1315 *
1316 * @return
1317 */
1318 @Transient
1319 public Language getLocalityLanguage() {
1320 LanguageString locality = getLocality();
1321 if (locality != null) {
1322 return locality.getLanguage();
1323 }
1324 return null;
1325 }
1326
1327 /**
1328 * Sets the locality string in the default language
1329 *
1330 * @param locality
1331 */
1332 public void setLocality(String locality) {
1333 Language language = Language.DEFAULT();
1334 setLocality(locality, language);
1335 }
1336
1337 public void setLocality(String locality, Language language) {
1338 LanguageString langString = LanguageString.NewInstance(locality, language);
1339 setLocality(langString);
1340 }
1341
1342 public void setLocality(LanguageString locality) {
1343 getGatheringEvent(true).setLocality(locality);
1344 }
1345
1346 /**
1347 * The gathering event will be used for the field object instead of the old
1348 * gathering event.<BR>
1349 * <B>This method will override all gathering values (see below).</B>
1350 *
1351 * @see #getAbsoluteElevation()
1352 * @see #getAbsoluteElevationError()
1353 * @see #getDistanceToGround()
1354 * @see #getDistanceToWaterSurface()
1355 * @see #getExactLocation()
1356 * @see #getGatheringEventDescription()
1357 * @see #getGatheringPeriod()
1358 * @see #getCollectingAreas()
1359 * @see #getCollectingMethod()
1360 * @see #getLocality()
1361 * @see #getCollector()
1362 * @param gatheringEvent
1363 */
1364 public void setGatheringEvent(GatheringEvent gatheringEvent) {
1365 getFieldUnit(true).setGatheringEvent(gatheringEvent);
1366 }
1367
1368 public boolean hasGatheringEvent() {
1369 return (getGatheringEvent(false) != null);
1370 }
1371
1372 public GatheringEvent innerGatheringEvent() {
1373 return getGatheringEvent(false);
1374 }
1375
1376 public GatheringEvent getGatheringEvent(boolean createIfNotExists) {
1377 if (!hasFieldUnit() && !createIfNotExists) {
1378 return null;
1379 }
1380 if (createIfNotExists && getFieldUnit(true).getGatheringEvent() == null) {
1381 GatheringEvent gatheringEvent = GatheringEvent.NewInstance();
1382 getFieldUnit(true).setGatheringEvent(gatheringEvent);
1383 }
1384 return getFieldUnit(true).getGatheringEvent();
1385 }
1386
1387 // ****************** Field Object ************************************/
1388
1389 /**
1390 * Returns true if a field unit exists (even if all attributes are
1391 * empty or <code>null<code>.
1392 *
1393 * @return
1394 */
1395 public boolean hasFieldObject() {
1396 return this.fieldUnit != null;
1397 }
1398
1399 // ecology
1400 @Transient
1401 public String getEcology() {
1402 return getEcology(Language.DEFAULT());
1403 }
1404
1405 public String getEcology(Language language) {
1406 LanguageString languageString = getEcologyAll().get(language);
1407 return (languageString == null ? null : languageString.getText());
1408 }
1409
1410 // public String getEcologyPreferred(List<Language> languages){
1411 // LanguageString languageString =
1412 // getEcologyAll().getPreferredLanguageString(languages);
1413 // return languageString.getText();
1414 // }
1415 /**
1416 * Returns a copy of the multilanguage text holding the ecology data.
1417 *
1418 * @see {@link TextData#getMultilanguageText()}
1419 * @return
1420 */
1421 @Transient
1422 public Map<Language, LanguageString> getEcologyAll() {
1423 if (ecology == null) {
1424 try {
1425 ecology = initializeFieldObjectTextDataWithSupportTest(
1426 Feature.ECOLOGY(), false, false);
1427 } catch (DerivedUnitFacadeNotSupportedException e) {
1428 throw new IllegalStateException(notSupportMessage, e);
1429 }
1430 if (ecology == null) {
1431 return new HashMap<>();
1432 }
1433 }
1434 return ecology.getMultilanguageText();
1435 }
1436
1437 public void setEcology(String ecology) {
1438 setEcology(ecology, null);
1439 }
1440
1441 public void setEcology(String ecologyText, Language language) {
1442 if (language == null) {
1443 language = Language.DEFAULT();
1444 }
1445 boolean isEmpty = StringUtils.isBlank(ecologyText);
1446 if (ecology == null) {
1447 try {
1448 ecology = initializeFieldObjectTextDataWithSupportTest(
1449 Feature.ECOLOGY(), !isEmpty, false);
1450 } catch (DerivedUnitFacadeNotSupportedException e) {
1451 throw new IllegalStateException(notSupportMessage, e);
1452 }
1453 }
1454 if (ecology != null){
1455 if (ecologyText == null) {
1456 ecology.removeText(language);
1457 } else {
1458 ecology.putText(language, ecologyText);
1459 }
1460 }
1461 }
1462
1463 public void removeEcology(Language language) {
1464 setEcology(null, language);
1465 }
1466
1467 /**
1468 * Removes ecology for the default language
1469 */
1470 public void removeEcology() {
1471 setEcology(null, null);
1472 }
1473
1474 // plant description
1475 @Transient
1476 public String getPlantDescription() {
1477 return getPlantDescription(null);
1478 }
1479
1480 public String getPlantDescription(Language language) {
1481 if (language == null) {
1482 language = Language.DEFAULT();
1483 }
1484 LanguageString languageString = getPlantDescriptionAll().get(language);
1485 return (languageString == null ? null : languageString.getText());
1486 }
1487
1488 // public String getPlantDescriptionPreferred(List<Language> languages){
1489 // LanguageString languageString =
1490 // getPlantDescriptionAll().getPreferredLanguageString(languages);
1491 // return languageString.getText();
1492 // }
1493 /**
1494 * Returns a copy of the multilanguage text holding the description data.
1495 *
1496 * @see {@link TextData#getMultilanguageText()}
1497 * @return
1498 */
1499 @Transient
1500 public Map<Language, LanguageString> getPlantDescriptionAll() {
1501 if (plantDescription == null) {
1502 try {
1503 plantDescription = initializeFieldObjectTextDataWithSupportTest(
1504 Feature.DESCRIPTION(), false, false);
1505 } catch (DerivedUnitFacadeNotSupportedException e) {
1506 throw new IllegalStateException(notSupportMessage, e);
1507 }
1508 if (plantDescription == null) {
1509 return new HashMap<>();
1510 }
1511 }
1512 return plantDescription.getMultilanguageText();
1513 }
1514
1515 public void setPlantDescription(String plantDescription) {
1516 setPlantDescription(plantDescription, null);
1517 }
1518
1519 public void setPlantDescription(String plantDescriptionText, Language language) {
1520 if (language == null) {
1521 language = Language.DEFAULT();
1522 }
1523 boolean isEmpty = StringUtils.isBlank(plantDescriptionText);
1524 if (plantDescription == null) {
1525 try {
1526 plantDescription = initializeFieldObjectTextDataWithSupportTest(
1527 Feature.DESCRIPTION(), !isEmpty, false);
1528 } catch (DerivedUnitFacadeNotSupportedException e) {
1529 throw new IllegalStateException(notSupportMessage, e);
1530 }
1531 }
1532 if (plantDescription != null){
1533 if (plantDescriptionText == null) {
1534 plantDescription.removeText(language);
1535 } else {
1536 plantDescription.putText(language, plantDescriptionText);
1537 }
1538 }
1539 }
1540
1541 public void removePlantDescription(Language language) {
1542 setPlantDescription(null, language);
1543 }
1544
1545 // life-form
1546 @Transient
1547 public String getLifeform() {
1548 return getLifeform(Language.DEFAULT());
1549 }
1550
1551 public String getLifeform(Language language) {
1552 LanguageString languageString = getLifeformAll().get(language);
1553 return (languageString == null ? null : languageString.getText());
1554 }
1555
1556 // public String getLifeformPreferred(List<Language> languages){
1557 // LanguageString languageString =
1558 // getLifeformAll().getPreferredLanguageString(languages);
1559 // return languageString.getText();
1560 // }
1561 /**
1562 * Returns a copy of the multi language text holding the life-form data.
1563 *
1564 * @see {@link TextData#getMultilanguageText()}
1565 * @return
1566 */
1567 @Transient
1568 public Map<Language, LanguageString> getLifeformAll() {
1569 if (lifeform == null) {
1570 try {
1571 lifeform = initializeFieldObjectTextDataWithSupportTest(
1572 Feature.LIFEFORM(), false, false);
1573 } catch (DerivedUnitFacadeNotSupportedException e) {
1574 throw new IllegalStateException(notSupportMessage, e);
1575 }
1576 if (lifeform == null) {
1577 return new HashMap<>();
1578 }
1579 }
1580 return lifeform.getMultilanguageText();
1581 }
1582
1583 public void setLifeform(String lifeform) {
1584 setLifeform(lifeform, null);
1585 }
1586
1587 public void setLifeform(String lifeformText, Language language) {
1588 if (language == null) {
1589 language = Language.DEFAULT();
1590 }
1591 boolean isEmpty = StringUtils.isBlank(lifeformText);
1592 if (lifeform == null) {
1593 try {
1594 lifeform = initializeFieldObjectTextDataWithSupportTest(
1595 Feature.LIFEFORM(), !isEmpty, false);
1596 } catch (DerivedUnitFacadeNotSupportedException e) {
1597 throw new IllegalStateException(notSupportMessage, e);
1598 }
1599 }
1600 if (lifeform != null){
1601 if (lifeformText == null) {
1602 lifeform.removeText(language);
1603 } else {
1604 lifeform.putText(language, lifeformText);
1605 }
1606 }
1607 }
1608
1609 public void removeLifeform(Language language) {
1610 setLifeform(null, language);
1611 }
1612
1613 /**
1614 * Removes life-form for the default language
1615 */
1616 public void removeLifeform() {
1617 setLifeform(null, null);
1618 }
1619
1620 // field object definition
1621 public void addFieldObjectDefinition(String text, Language language) {
1622 getFieldUnit(true).putDefinition(language, text);
1623 }
1624
1625 @Transient
1626 public Map<Language, LanguageString> getFieldObjectDefinition() {
1627 if (!hasFieldUnit()) {
1628 return new HashMap<>();
1629 } else {
1630 return getFieldUnit(true).getDefinition();
1631 }
1632 }
1633
1634 public String getFieldObjectDefinition(Language language) {
1635 Map<Language, LanguageString> map = getFieldObjectDefinition();
1636 LanguageString languageString = (map == null ? null : map.get(language));
1637 if (languageString != null) {
1638 return languageString.getText();
1639 } else {
1640 return null;
1641 }
1642 }
1643
1644 public void removeFieldObjectDefinition(Language lang) {
1645 if (hasFieldUnit()) {
1646 getFieldUnit(true).removeDefinition(lang);
1647 }
1648 }
1649
1650 // media
1651 public boolean addFieldObjectMedia(Media media) {
1652 try {
1653 return addMedia(media, getFieldUnit(true));
1654 } catch (DerivedUnitFacadeNotSupportedException e) {
1655 throw new IllegalStateException(notSupportMessage, e);
1656 }
1657 }
1658
1659 /**
1660 * Returns true, if an image gallery for the field object exists.<BR>
1661 * Returns also <code>true</code> if the image gallery is empty.
1662 *
1663 * @return
1664 */
1665 public boolean hasFieldObjectImageGallery() {
1666 if (!hasFieldObject()) {
1667 return false;
1668 } else {
1669 return (getImageGallery(fieldUnit, false) != null);
1670 }
1671 }
1672
1673 public void setFieldObjectImageGallery(SpecimenDescription imageGallery)
1674 throws DerivedUnitFacadeNotSupportedException {
1675 SpecimenDescription existingGallery = getFieldObjectImageGallery(false);
1676
1677 // test attached specimens contain this.derivedUnit
1678 SpecimenOrObservationBase<?> facadeFieldUnit = innerFieldUnit();
1679 testSpecimenInImageGallery(imageGallery, facadeFieldUnit);
1680
1681 if (existingGallery != null) {
1682 if (existingGallery != imageGallery) {
1683 throw new DerivedUnitFacadeNotSupportedException(
1684 "DerivedUnitFacade does not allow more than one image gallery");
1685 } else {
1686 // do nothing
1687 }
1688 } else {
1689 TextData textData = testImageGallery(imageGallery);
1690 this.fieldObjectMediaTextData = textData;
1691 }
1692 }
1693
1694 /**
1695 * Returns the field object image gallery. If no such image gallery exists
1696 * and createIfNotExists is true an new one is created. Otherwise null is
1697 * returned.
1698 *
1699 * @param createIfNotExists
1700 * @return
1701 */
1702 public SpecimenDescription getFieldObjectImageGallery(
1703 boolean createIfNotExists) {
1704 TextData textData;
1705 try {
1706 textData = initializeFieldObjectTextDataWithSupportTest(
1707 Feature.IMAGE(), createIfNotExists, true);
1708 } catch (DerivedUnitFacadeNotSupportedException e) {
1709 throw new IllegalStateException(notSupportMessage, e);
1710 }
1711 if (textData != null) {
1712 return CdmBase.deproxy(textData.getInDescription(),
1713 SpecimenDescription.class);
1714 } else {
1715 return null;
1716 }
1717 }
1718
1719 /**
1720 * Returns the media for the field object.<BR>
1721 *
1722 * @return
1723 */
1724 @Transient
1725 public List<Media> getFieldObjectMedia() {
1726 try {
1727 List<Media> result = getMediaList(getFieldUnit(false), false);
1728 return result == null ? new ArrayList<>() : result;
1729 } catch (DerivedUnitFacadeNotSupportedException e) {
1730 throw new IllegalStateException(notSupportMessage, e);
1731 }
1732 }
1733
1734 public boolean removeFieldObjectMedia(Media media) {
1735 try {
1736 return removeMedia(media, getFieldUnit(false));
1737 } catch (DerivedUnitFacadeNotSupportedException e) {
1738 throw new IllegalStateException(notSupportMessage, e);
1739 }
1740 }
1741
1742 // field number
1743 @Transient
1744 public String getFieldNumber() {
1745 if (!hasFieldUnit()) {
1746 return null;
1747 } else {
1748 return getFieldUnit(true).getFieldNumber();
1749 }
1750 }
1751
1752 public void setFieldNumber(String fieldNumber) {
1753 getFieldUnit(true).setFieldNumber(fieldNumber);
1754 }
1755
1756 // primary collector
1757 @Transient
1758 public Person getPrimaryCollector() {
1759 if (!hasFieldUnit()) {
1760 return null;
1761 } else {
1762 return getFieldUnit(true).getPrimaryCollector();
1763 }
1764 }
1765
1766 public void setPrimaryCollector(Person primaryCollector) {
1767 getFieldUnit(true).setPrimaryCollector(primaryCollector);
1768 }
1769
1770 // field notes
1771 @Transient
1772 public String getFieldNotes() {
1773 if (!hasFieldUnit()) {
1774 return null;
1775 } else {
1776 return getFieldUnit(true).getFieldNotes();
1777 }
1778 }
1779
1780 public void setFieldNotes(String fieldNotes) {
1781 getFieldUnit(true).setFieldNotes(fieldNotes);
1782 }
1783
1784 // individual counts
1785 @Transient
1786 public String getIndividualCount() {
1787 return (hasFieldUnit() ? getFieldUnit(true).getIndividualCount() : null);
1788 }
1789
1790 public void setIndividualCount(String individualCount) {
1791 getFieldUnit(true).setIndividualCount(individualCount);
1792 }
1793
1794 // life stage
1795 @Transient
1796 public DefinedTerm getLifeStage() {
1797 return (hasFieldUnit() ? getFieldUnit(true).getLifeStage() : null);
1798 }
1799
1800 public void setLifeStage(DefinedTerm lifeStage) {
1801 FieldUnit fieldUnit = getFieldUnit(lifeStage != null);
1802 if (fieldUnit != null){
1803 fieldUnit.setLifeStage(lifeStage);
1804 }
1805 }
1806
1807 // sex
1808 @Transient
1809 public DefinedTerm getSex() {
1810 return (hasFieldUnit() ? getFieldUnit(true).getSex(): null);
1811 }
1812
1813 public void setSex(DefinedTerm sex) {
1814 FieldUnit fieldUnit = getFieldUnit(sex != null);
1815 if (fieldUnit != null){
1816 fieldUnit.setSex(sex);
1817 }
1818 }
1819
1820 // kind of Unit
1821 @Transient
1822 public DefinedTerm getFieldUnitKindOfUnit() {
1823 return (hasFieldUnit() ? fieldUnit.getKindOfUnit() : null);
1824 }
1825 //
1826 @Transient
1827 public DefinedTerm getDerivedUnitKindOfUnit() {
1828 checkDerivedUnit();
1829 return checkDerivedUnit() ? derivedUnit.getKindOfUnit() : null;
1830 }
1831
1832
1833 /**
1834 * Sets the kind-of-unit
1835 * @param kindOfUnit
1836 */
1837 public void setFieldUnitKindOfUnit(DefinedTerm kindOfUnit) {
1838 FieldUnit fieldUnit = getFieldUnit(kindOfUnit != null);
1839 if (fieldUnit != null){
1840 fieldUnit.setKindOfUnit(kindOfUnit);
1841 }
1842 }
1843
1844 public void setDerivedUnitKindOfUnit(DefinedTerm kindOfUnit) {
1845 testDerivedUnit();
1846
1847 baseUnit().setKindOfUnit(kindOfUnit);
1848 }
1849
1850
1851 // field unit
1852 public boolean hasFieldUnit() {
1853 return (getFieldUnit(CREATE_NOT) != null);
1854 }
1855
1856 /**
1857 * Returns the field unit as an object.
1858 *
1859 * @return
1860 */
1861 public FieldUnit innerFieldUnit() {
1862 return getFieldUnit(CREATE_NOT);
1863 }
1864
1865 /**
1866 * Returns the field unit as an object.
1867 *
1868 * @return
1869 */
1870 public FieldUnit getFieldUnit(boolean createIfNotExists) {
1871 if (fieldUnit == null && createIfNotExists) {
1872 setFieldUnit(FieldUnit.NewInstance());
1873 }
1874 return this.fieldUnit;
1875 }
1876
1877
1878 public void setFieldUnit(FieldUnit fieldUnit) {
1879 this.fieldUnit = fieldUnit;
1880 if (fieldUnit != null){
1881 if (config.isFirePropertyChangeEvents()){
1882 addNewEventPropagationListener(fieldUnit);
1883 }
1884 if (derivedUnit != null){
1885 DerivationEvent derivationEvent = getDerivationEvent(CREATE);
1886 derivationEvent.addOriginal(fieldUnit);
1887 }
1888 setFieldUnitCacheStrategy();
1889 }
1890 }
1891
1892 // ****************** Specimen *******************************************
1893
1894
1895 // Definition
1896 public void addDerivedUnitDefinition(String text, Language language) {
1897 innerDerivedUnit().putDefinition(language, text);
1898 }
1899
1900 @Transient
1901 public Map<Language, LanguageString> getDerivedUnitDefinitions() {
1902 return ! checkDerivedUnit()? null : this.derivedUnit.getDefinition();
1903 }
1904
1905 public TypedEntityReference<DerivedUnit> getDerivedUnitEntityReference() {
1906 return new TypedEntityReference(derivedUnit.getClass(), derivedUnit.getUuid());
1907 }
1908
1909 public TypedEntityReference<FieldUnit> getFieldUnitEntityReference() {
1910 if(fieldUnit == null) {
1911 return null;
1912 } else {
1913 return new TypedEntityReference(FieldUnit.class, fieldUnit.getUuid());
1914 }
1915 }
1916
1917 public String getDerivedUnitDefinition(Language language) {
1918 if (! checkDerivedUnit()){
1919 return null;
1920 }
1921 Map<Language, LanguageString> languageMap = derivedUnit.getDefinition();
1922 LanguageString languageString = languageMap.get(language);
1923 if (languageString != null) {
1924 return languageString.getText();
1925 } else {
1926 return null;
1927 }
1928 }
1929
1930 public void removeDerivedUnitDefinition(Language lang) {
1931 testDerivedUnit();
1932 derivedUnit.removeDefinition(lang);
1933 }
1934
1935 // Determination
1936 public void addDetermination(DeterminationEvent determination) {
1937 //TODO implement correct bidirectional mapping in model classes
1938 determination.setIdentifiedUnit(baseUnit());
1939 baseUnit().addDetermination(determination);
1940 }
1941
1942 @Transient
1943 public DeterminationEvent getPreferredDetermination() {
1944 return baseUnit().getPreferredDetermination();
1945 }
1946
1947 /**
1948 * This method returns the preferred determination.
1949 * @see #getOtherDeterminations()
1950 * @see #getDeterminations()
1951 */
1952 @Transient
1953 public void setPreferredDetermination(DeterminationEvent newEvent) {
1954 baseUnit().setPreferredDetermination(newEvent);
1955 }
1956
1957 /**
1958 * This method returns all determinations except for the preferred one.
1959 * @see #getPreferredDetermination()
1960 * @see #getDeterminations()
1961 * @return
1962 */
1963 @Transient
1964 public Set<DeterminationEvent> getOtherDeterminations() {
1965 return baseUnit().getOtherDeterminations();
1966 }
1967
1968 /**
1969 * This method returns all determination events. The preferred one {@link #getPreferredDetermination()}
1970 * and all others {@link #getOtherDeterminations()}.
1971 * @return
1972 */
1973 @Transient
1974 public Set<DeterminationEvent> getDeterminations() {
1975 return baseUnit().getDeterminations();
1976 }
1977
1978 public void removeDetermination(DeterminationEvent determination) {
1979 baseUnit().removeDetermination(determination);
1980 }
1981
1982 // Media
1983 public boolean addDerivedUnitMedia(Media media) {
1984 testDerivedUnit();
1985 try {
1986 return addMedia(media, derivedUnit);
1987 } catch (DerivedUnitFacadeNotSupportedException e) {
1988 throw new IllegalStateException(notSupportMessage, e);
1989 }
1990 }
1991
1992 /**
1993 * Returns true, if an image gallery exists for the specimen.<BR>
1994 * Returns also <code>true</code> if the image gallery is empty.
1995 */
1996 public boolean hasDerivedUnitImageGallery() {
1997 return (getImageGallery(derivedUnit, false) != null);
1998 }
1999
2000 public SpecimenDescription getDerivedUnitImageGallery(boolean createIfNotExists) {
2001 if (!checkDerivedUnit()){
2002 return null;
2003 }
2004 TextData textData;
2005 try {
2006 textData = inititializeTextDataWithSupportTest(Feature.IMAGE(),
2007 derivedUnit, createIfNotExists, true);
2008 } catch (DerivedUnitFacadeNotSupportedException e) {
2009 throw new IllegalStateException(notSupportMessage, e);
2010 }
2011 if (textData != null) {
2012 return CdmBase.deproxy(textData.getInDescription(),
2013 SpecimenDescription.class);
2014 } else {
2015 return null;
2016 }
2017 }
2018
2019 public void setDerivedUnitImageGallery(SpecimenDescription imageGallery)
2020 throws DerivedUnitFacadeNotSupportedException {
2021 testDerivedUnit();
2022 SpecimenDescription existingGallery = getDerivedUnitImageGallery(false);
2023
2024 // test attached specimens contain this.derivedUnit
2025 SpecimenOrObservationBase facadeDerivedUnit = innerDerivedUnit();
2026 testSpecimenInImageGallery(imageGallery, facadeDerivedUnit);
2027
2028 if (existingGallery != null) {
2029 if (existingGallery != imageGallery) {
2030 throw new DerivedUnitFacadeNotSupportedException(
2031 "DerivedUnitFacade does not allow more than one image gallery");
2032 } else {
2033 // do nothing
2034 }
2035 } else {
2036 TextData textData = testImageGallery(imageGallery);
2037 this.derivedUnitMediaTextData = textData;
2038 }
2039 }
2040
2041 /**
2042 * @param imageGallery
2043 * @throws DerivedUnitFacadeNotSupportedException
2044 */
2045 private void testSpecimenInImageGallery(SpecimenDescription imageGallery, SpecimenOrObservationBase specimen)
2046 throws DerivedUnitFacadeNotSupportedException {
2047 SpecimenOrObservationBase imageGallerySpecimen = imageGallery.getDescribedSpecimenOrObservation();
2048 if (imageGallerySpecimen == null) {
2049 throw new DerivedUnitFacadeNotSupportedException(
2050 "Image Gallery has no Specimen attached. Please attache according specimen or field unit.");
2051 }
2052 if (! imageGallerySpecimen.equals(specimen)) {
2053 throw new DerivedUnitFacadeNotSupportedException(
2054 "Image Gallery has not the facade's field object attached. Please add field object first " +
2055 "to image gallery specimenOrObservation list.");
2056 }
2057 }
2058
2059 /**
2060 * Returns the media for the specimen.<BR>
2061 *
2062 * @return
2063 */
2064 @Transient
2065 public List<Media> getDerivedUnitMedia() {
2066 if (! checkDerivedUnit()){
2067 return new ArrayList<>();
2068 }
2069 try {
2070 List<Media> result = getMediaList(derivedUnit, false);
2071 return result == null ? new ArrayList<>() : result;
2072 } catch (DerivedUnitFacadeNotSupportedException e) {
2073 throw new IllegalStateException(notSupportMessage, e);
2074 }
2075 }
2076
2077 public boolean removeDerivedUnitMedia(Media media) {
2078 testDerivedUnit();
2079 try {
2080 return removeMedia(media, derivedUnit);
2081 } catch (DerivedUnitFacadeNotSupportedException e) {
2082 throw new IllegalStateException(notSupportMessage, e);
2083 }
2084 }
2085
2086 // Accession Number
2087 @Transient
2088 public String getAccessionNumber() {
2089 return ! checkDerivedUnit()? null : derivedUnit.getAccessionNumber();
2090 }
2091
2092
2093 /**
2094 * The combination of collection code with accession number, barcode or collection number.
2095 *
2096 * @return
2097 */
2098 public String getSpecimenLabel() {
2099 if(checkDerivedUnit()){
2100 if(derivedUnit.getCacheStrategy() instanceof DerivedUnitFacadeCacheStrategy){
2101 return ((DerivedUnitFacadeCacheStrategy)derivedUnit.getCacheStrategy()).getSpecimenLabel(this);
2102 }
2103
2104 }
2105 return null;
2106 }
2107
2108 public void setAccessionNumber(String accessionNumber) {
2109 testDerivedUnit();
2110 derivedUnit.setAccessionNumber(accessionNumber);
2111 }
2112
2113 @Transient
2114 public String getCatalogNumber() {
2115 return ! checkDerivedUnit()? null : derivedUnit.getCatalogNumber();
2116 }
2117
2118 public void setCatalogNumber(String catalogNumber) {
2119 testDerivedUnit();
2120 derivedUnit.setCatalogNumber(catalogNumber);
2121 }
2122
2123 @Transient
2124 public String getBarcode() {
2125 return ! checkDerivedUnit()? null : derivedUnit.getBarcode();
2126 }
2127
2128 public void setBarcode(String barcode) {
2129 testDerivedUnit();
2130 derivedUnit.setBarcode(barcode);
2131 }
2132
2133 // Preservation Method
2134
2135 /**
2136 * Only supported by specimen and fossils
2137 *
2138 * @see #DerivedUnitType
2139 * @return
2140 */
2141 @Transient
2142 public PreservationMethod getPreservationMethod() throws MethodNotSupportedByDerivedUnitTypeException {
2143 if (derivedUnit!=null && derivedUnit.getRecordBasis().isPreservedSpecimen()) {
2144 return CdmBase.deproxy(derivedUnit, DerivedUnit.class).getPreservation();
2145 } else {
2146 if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2147 throw new MethodNotSupportedByDerivedUnitTypeException(
2148 "A preservation method is only available in derived units of type 'Preserved Specimen' or one of its specializations like 'Fossil Specimen' ");
2149 } else {
2150 return null;
2151 }
2152 }
2153 }
2154
2155 /**
2156 * Only supported by specimen and fossils
2157 *
2158 * @see #DerivedUnitType
2159 * @return
2160 */
2161 public void setPreservationMethod(PreservationMethod preservation)
2162 throws MethodNotSupportedByDerivedUnitTypeException {
2163 if (derivedUnit!=null && derivedUnit.getRecordBasis().isPreservedSpecimen()) {
2164 CdmBase.deproxy(derivedUnit, DerivedUnit.class).setPreservation(preservation);
2165 } else {
2166 if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2167 throw new MethodNotSupportedByDerivedUnitTypeException(
2168 "A preservation method is only available in derived units of type 'Specimen' or 'Fossil'");
2169 } else {
2170 return;
2171 }
2172 }
2173 }
2174
2175 //preferred stable URI #5606
2176 public URI getPreferredStableUri(){
2177 return baseUnit().getPreferredStableUri();
2178 }
2179 public void setPreferredStableUri(URI stableUri){
2180 baseUnit().setPreferredStableUri(stableUri);
2181 }
2182
2183
2184 // Stored under name
2185 @Transient
2186 public TaxonName getStoredUnder() {
2187 return ! checkDerivedUnit()? null : derivedUnit.getStoredUnder();
2188 }
2189
2190 public void setStoredUnder(TaxonName storedUnder) {
2191 testDerivedUnit();
2192 derivedUnit.setStoredUnder(storedUnder);
2193 }
2194
2195 // title cache
2196 public String getTitleCache() {
2197 SpecimenOrObservationBase<?> titledUnit = baseUnit();
2198
2199 if (!titledUnit.isProtectedTitleCache()) {
2200 // always compute title cache anew as long as there are no property
2201 // change listeners on
2202 // field unit, gathering event etc
2203 titledUnit.setTitleCache(null, false);
2204 }
2205 return titledUnit.getTitleCache();
2206 }
2207
2208 public boolean isProtectedTitleCache() {
2209 return baseUnit().isProtectedTitleCache();
2210 }
2211
2212 public void setTitleCache(String titleCache, boolean isProtected) {
2213 this.baseUnit().setTitleCache(titleCache, isProtected);
2214 }
2215
2216 /**
2217 * Returns the derived unit itself.
2218 *
2219 * @return the derived unit
2220 */
2221 public DerivedUnit innerDerivedUnit() {
2222 return this.derivedUnit;
2223 }
2224
2225 // /**
2226 // * Returns the derived unit itself.
2227 // *
2228 // * @return the derived unit
2229 // */
2230 // public DerivedUnit innerDerivedUnit(boolean createIfNotExists) {
2231 // DerivedUnit result = this.derivedUnit;
2232 // if (result == null && createIfNotExists){
2233 // if (this.fieldUnit == null){
2234 // String message = "Field unit must exist to create derived unit.";
2235 // throw new IllegalStateException(message);
2236 // }else{
2237 // DerivedUnit =
2238 // DerivationEvent derivationEvent = getDerivationEvent(true);
2239 // derivationEvent.addOriginal(fieldUnit);
2240 // return this.derivedUnit;
2241 // }
2242 // }
2243 // }
2244
2245 private boolean hasDerivationEvent() {
2246 return getDerivationEvent() == null ? false : true;
2247 }
2248
2249 private DerivationEvent getDerivationEvent() {
2250 return getDerivationEvent(CREATE_NOT);
2251 }
2252
2253 /**
2254 * Returns the derivation event. If no derivation event exists and <code>createIfNotExists</code>
2255 * is <code>true</code> a new derivation event is created and returned.
2256 * Otherwise <code>null</code> is returned.
2257 * @param createIfNotExists
2258 */
2259 private DerivationEvent getDerivationEvent(boolean createIfNotExists) {
2260 DerivationEvent result = null;
2261 if (derivedUnit != null){
2262 result = derivedUnit.getDerivedFrom();
2263 }else{
2264 return null;
2265 }
2266 if (result == null && createIfNotExists) {
2267 DerivationEventType type = null;
2268 if (isAccessioned(derivedUnit)){
2269 type = DerivationEventType.ACCESSIONING();
2270 }
2271
2272 result = DerivationEvent.NewInstance(type);
2273 derivedUnit.setDerivedFrom(result);
2274 }
2275 return result;
2276 }
2277
2278 /**
2279 * TODO still unclear which classes do definetly require accessioning.
2280 * Only return true for those classes which are clear.
2281 * @param derivedUnit
2282 * @return
2283 */
2284 private boolean isAccessioned(DerivedUnit derivedUnit) {
2285 if (derivedUnit.getRecordBasis().equals(SpecimenOrObservationType.PreservedSpecimen) ){
2286 return true; //maybe also subtypes should be true
2287 }else{
2288 return false;
2289 }
2290 }
2291
2292 @Transient
2293 public String getExsiccatum() throws MethodNotSupportedByDerivedUnitTypeException {
2294 testDerivedUnit();
2295 if (derivedUnit.getRecordBasis().isPreservedSpecimen()) {
2296 return derivedUnit.getExsiccatum();
2297 } else {
2298 if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2299 throw new MethodNotSupportedByDerivedUnitTypeException(
2300 "An exsiccatum is only available in derived units of type 'Specimen' or 'Fossil'");
2301 } else {
2302 return null;
2303 }
2304 }
2305 }
2306
2307 public void setExsiccatum(String exsiccatum) throws Exception {
2308 testDerivedUnit();
2309 if (derivedUnit.getRecordBasis().isPreservedSpecimen()) {
2310 derivedUnit.setExsiccatum(exsiccatum);
2311 } else {
2312 if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2313 throw new MethodNotSupportedByDerivedUnitTypeException(
2314 "An exsiccatum is only available in derived units of type 'Specimen' or 'Fossil'");
2315 } else {
2316 return;
2317 }
2318 }
2319 }
2320
2321 /**
2322 * Returns the original label information of the derived unit.
2323 * @return
2324 */
2325 @Transient
2326 public String getOriginalLabelInfo() {
2327 return ! checkDerivedUnit()? null : derivedUnit.getOriginalLabelInfo();
2328 }
2329 public void setOriginalLabelInfo(String originalLabelInfo) {
2330 testDerivedUnit();
2331 derivedUnit.setOriginalLabelInfo(originalLabelInfo);
2332 }
2333
2334 // **** sources **/
2335 public void addSource(IdentifiableSource source) {
2336 this.baseUnit().addSource(source);
2337 }
2338
2339 /**
2340 * Creates an {@link IOriginalSource orignal source} or type ,
2341 * adds it to the specimen and returns it.
2342 *
2343 * @param reference
2344 * @param microReference
2345 * @param originalNameString
2346 * @return
2347 */
2348 public IdentifiableSource addSource(OriginalSourceType type, Reference reference, String microReference, String originalNameString) {
2349 IdentifiableSource source = IdentifiableSource.NewInstance(type, null, null, reference, microReference);
2350 source.setOriginalNameString(originalNameString);
2351 addSource(source);
2352 return source;
2353 }
2354
2355 @Transient
2356 public Set<IdentifiableSource> getSources() {
2357 return baseUnit().getSources();
2358 }
2359
2360 public void removeSource(IdentifiableSource source) {
2361 this.baseUnit().removeSource(source);
2362 }
2363
2364 //*** identifiers ***/
2365
2366
2367 public void addIdentifier(Identifier identifier) {
2368 this.baseUnit().addIdentifier(identifier);
2369 }
2370
2371 @Transient
2372 public List<Identifier> getIdentifiers() {
2373 return baseUnit().getIdentifiers();
2374 }
2375
2376 public void removeIdentifier(Identifier identifier) {
2377 this.baseUnit().removeIdentifier(identifier);
2378 }
2379
2380 @Transient
2381 public Set<Rights> getRights() {
2382 return baseUnit().getRights();
2383 }
2384
2385 /**
2386 * @return the collection
2387 */
2388 @Transient
2389 public Collection getCollection() {
2390 return ! checkDerivedUnit()? null : derivedUnit.getCollection();
2391 }
2392
2393 /**
2394 * @param collection
2395 * the collection to set
2396 */
2397 public void setCollection(Collection collection) {
2398 testDerivedUnit();
2399 derivedUnit.setCollection(collection);
2400 }
2401
2402 // annotation
2403 public void addAnnotation(Annotation annotation) {
2404 this.baseUnit().addAnnotation(annotation);
2405 }
2406
2407 @Transient
2408 public void getAnnotations() {
2409 this.baseUnit().getAnnotations();
2410 }
2411
2412 public void removeAnnotation(Annotation annotation) {
2413 this.baseUnit().removeAnnotation(annotation);
2414 }
2415
2416 // ******************************* Events ***************************
2417
2418 //set of events that were currently fired by this facades field unit
2419 //to avoid recursive fireing of the same event
2420 private final Set<PropertyChangeEvent> fireingEvents = new HashSet<>();
2421
2422 /**
2423 * @return
2424 */
2425 private void addNewEventPropagationListener(CdmBase listeningObject) {
2426 //if there is already a listener, don't do anything
2427 for (PropertyChangeListener listener : this.listeners.keySet()){
2428 if (listeners.get(listener) == listeningObject){
2429 return;
2430 }
2431 }
2432 //create new listener
2433 PropertyChangeListener listener = new PropertyChangeListener() {
2434 @Override
2435 public void propertyChange(PropertyChangeEvent event) {
2436 if (derivedUnit != null){
2437 derivedUnit.firePropertyChange(event);
2438 }else{
2439 if (! event.getSource().equals(fieldUnit) && ! fireingEvents.contains(event) ){
2440 fireingEvents.add(event);
2441 fieldUnit.firePropertyChange(event);
2442 fireingEvents.remove(event);
2443 }
2444 }
2445 }
2446 };
2447 //add listener to listening object and to list of listeners
2448 listeningObject.addPropertyChangeListener(listener);
2449 listeners.put(listener, listeningObject);
2450 }
2451
2452 // **************** Other Collections ********************************
2453
2454 /**
2455 * Creates a duplicate specimen which derives from the same derivation event
2456 * as the facade specimen and adds collection data to it (all data available
2457 * in DerivedUnit and Specimen. Data from SpecimenOrObservationBase and
2458 * above are not yet shared at the moment.
2459 *
2460 * @param collection
2461 * @param catalogNumber
2462 * @param accessionNumber
2463 * @param storedUnder
2464 * @param preservation
2465 * @return
2466 */
2467 public DerivedUnit addDuplicate(Collection collection, String catalogNumber,
2468 String accessionNumber, TaxonName storedUnder, PreservationMethod preservation){
2469 testDerivedUnit();
2470 DerivedUnit duplicate = DerivedUnit.NewPreservedSpecimenInstance();
2471 duplicate.setDerivedFrom(getDerivationEvent(CREATE));
2472 duplicate.setCollection(collection);
2473 duplicate.setCatalogNumber(catalogNumber);
2474 duplicate.setAccessionNumber(accessionNumber);
2475 duplicate.setStoredUnder(storedUnder);
2476 duplicate.setPreservation(preservation);
2477 return duplicate;
2478 }
2479
2480 public void addDuplicate(DerivedUnit duplicateSpecimen) {
2481 testDerivedUnit();
2482 getDerivationEvent(CREATE).addDerivative(duplicateSpecimen);
2483 }
2484
2485 @Transient
2486 public Set<DerivedUnit> getDuplicates() {
2487 if (! checkDerivedUnit()){
2488 return new HashSet<>();
2489 }
2490 Set<DerivedUnit> result = new HashSet<>();
2491 if (hasDerivationEvent()) {
2492 for (DerivedUnit derivedUnit : getDerivationEvent(CREATE)
2493 .getDerivatives()) {
2494 if (derivedUnit.isInstanceOf(DerivedUnit.class)
2495 && !derivedUnit.equals(this.derivedUnit)) {
2496 result.add(CdmBase.deproxy(derivedUnit, DerivedUnit.class));
2497 }
2498 }
2499 }
2500 return result;
2501 }
2502
2503 public void removeDuplicate(DerivedUnit duplicateSpecimen) {
2504 testDerivedUnit();
2505 if (hasDerivationEvent()) {
2506 getDerivationEvent(CREATE).removeDerivative(duplicateSpecimen);
2507 }
2508 }
2509
2510 public SpecimenOrObservationBase<?> baseUnit(){
2511 if(derivedUnit!=null){
2512 return derivedUnit;
2513 }
2514 else if(fieldUnit!=null){
2515 return fieldUnit;
2516 }
2517 else{
2518 throw new IllegalStateException("A DerivedUnitFacade must always have either a field unit or a derived unit");
2519 }
2520 }
2521
2522 /**
2523 * @return true if <code>this.derivedUnit</code> exists
2524 */
2525 private boolean checkDerivedUnit() {
2526 if (derivedUnit == null){
2527 return false;
2528 }else{
2529 return true;
2530 }
2531 }
2532
2533 private void testDerivedUnit() /* throws MethodNotSupportedByDerivedUnitTypeException */ {
2534 if (derivedUnit == null){
2535 throw new IllegalStateException("This method is not allowed for this specimen or observation type. Probably you have tried to add specimen(derived unit) information to a field unit");
2536 }
2537 }
2538
2539 public void setType(SpecimenOrObservationType type) {
2540 if (type == null){
2541 throw new IllegalArgumentException("The type of a specimen or observation may not be null");
2542 }
2543 SpecimenOrObservationBase<?> baseUnit = baseUnit();
2544 if(baseUnit.isInstanceOf(FieldUnit.class) && !type.isFieldUnit()){
2545 throw new IllegalArgumentException("A FieldUnit may only be of type FieldUnit") ;
2546 }
2547 else if(baseUnit.isInstanceOf(DerivedUnit.class) && type.isFieldUnit()){
2548 throw new IllegalArgumentException("A derived unit may not be of type FieldUnit") ;
2549 }
2550 baseUnit.setRecordBasis(type);
2551 }
2552
2553 public SpecimenOrObservationType getType() {
2554 return baseUnit().getRecordBasis();
2555 }
2556
2557 /**
2558 * Closes this facade. As a minimum this method removes all listeners created by this facade from their
2559 * listening objects.
2560 */
2561 public void close(){
2562 for (PropertyChangeListener listener : this.listeners.keySet()){
2563 CdmBase listeningObject = listeners.get(listener);
2564 listeningObject.removePropertyChangeListener(listener);
2565 }
2566 }
2567
2568
2569
2570
2571 /**
2572 * First checks the inner field unit for the publish flag. If set to <code>true</code>
2573 * then <code>true</code> is returned. If the field unit is <code>null</code> the inner derived unit
2574 * is checked.
2575 * @return <code>true</code> if this facade can be published
2576 */
2577 public boolean isPublish(){
2578 if(fieldUnit!=null){
2579 return fieldUnit.isPublish();
2580 }
2581 if(derivedUnit!=null){
2582 return derivedUnit.isPublish();
2583 }
2584 return false;
2585 }
2586 }