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