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