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