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