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