service for occurrence maps
[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.beans.PropertyChangeSupport;
15 import java.text.ParseException;
16 import java.util.ArrayList;
17 import java.util.HashMap;
18 import java.util.HashSet;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Set;
22
23 import javax.mail.MethodNotSupportedException; //FIMXE use other execption class
24 import javax.persistence.Transient;
25
26
27 import org.apache.log4j.Logger;
28 import org.springframework.beans.factory.annotation.Autowired;
29
30 import eu.etaxonomy.cdm.api.service.IOccurrenceService;
31 import eu.etaxonomy.cdm.model.agent.AgentBase;
32 import eu.etaxonomy.cdm.model.common.Annotation;
33 import eu.etaxonomy.cdm.model.common.CdmBase;
34 import eu.etaxonomy.cdm.model.common.IdentifiableSource;
35 import eu.etaxonomy.cdm.model.common.Language;
36 import eu.etaxonomy.cdm.model.common.LanguageString;
37 import eu.etaxonomy.cdm.model.common.TimePeriod;
38 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
39 import eu.etaxonomy.cdm.model.description.Feature;
40 import eu.etaxonomy.cdm.model.description.Sex;
41 import eu.etaxonomy.cdm.model.description.SpecimenDescription;
42 import eu.etaxonomy.cdm.model.description.Stage;
43 import eu.etaxonomy.cdm.model.description.TextData;
44 import eu.etaxonomy.cdm.model.location.NamedArea;
45 import eu.etaxonomy.cdm.model.location.Point;
46 import eu.etaxonomy.cdm.model.location.ReferenceSystem;
47 import eu.etaxonomy.cdm.model.media.Media;
48 import eu.etaxonomy.cdm.model.name.TaxonNameBase;
49 import eu.etaxonomy.cdm.model.occurrence.Collection;
50 import eu.etaxonomy.cdm.model.occurrence.DerivationEvent;
51 import eu.etaxonomy.cdm.model.occurrence.DerivedUnitBase;
52 import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
53 import eu.etaxonomy.cdm.model.occurrence.FieldObservation;
54 import eu.etaxonomy.cdm.model.occurrence.GatheringEvent;
55 import eu.etaxonomy.cdm.model.occurrence.PreservationMethod;
56 import eu.etaxonomy.cdm.model.occurrence.Specimen;
57 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
58 import eu.etaxonomy.cdm.model.reference.ReferenceBase;
59
60 /**
61 * This class is a facade to the eu.etaxonomy.cdm.model.occurrence package from
62 * a specimen based view. It does not support all functionality available in the
63 * occurrence package.<BR>
64 * The most significant restriction is that a specimen may derive only from
65 * one direct derivation event and there must be only one field observation (gathering event)
66 * it derives from.<BR>
67 *
68 * @author a.mueller
69 * @date 14.05.2010
70 */
71 public class DerivedUnitFacade {
72 private static final Logger logger = Logger.getLogger(DerivedUnitFacade.class);
73
74 private static final String notSupportMessage = "A specimen facade not supported exception has occurred at a place where this should not have happened. The developer should implement not support check properly during class initialization ";
75
76 /**
77 * Enum that defines the class the "Specimen" belongs to.
78 * Some methods of the facade are not available for certain classes
79 * and will throw an Exception when invoking them.
80 */
81 public enum DerivedUnitType{
82 Specimen ("Specimen"),
83 Observation("Observation"),
84 LivingBeing("Living Being"),
85 Fossil("Fossil"),
86 DerivedUnit("Derived Unit");
87
88 String representation;
89 private DerivedUnitType(String representation){
90 this.representation = representation;
91 }
92
93 /**
94 * @return the representation
95 */
96 public String getRepresentation() {
97 return representation;
98 }
99
100 private DerivedUnitBase getNewDerivedUnitInstance(){
101 if (this == DerivedUnitType.Specimen){
102 return eu.etaxonomy.cdm.model.occurrence.Specimen.NewInstance();
103 }else if (this == DerivedUnitType.Observation){
104 return eu.etaxonomy.cdm.model.occurrence.Observation.NewInstance();
105 }else if (this == DerivedUnitType.LivingBeing){
106 return eu.etaxonomy.cdm.model.occurrence.LivingBeing.NewInstance();
107 }else if (this == DerivedUnitType.Fossil){
108 return eu.etaxonomy.cdm.model.occurrence.Fossil.NewInstance();
109 }else if (this == DerivedUnitType.DerivedUnit){
110 return eu.etaxonomy.cdm.model.occurrence.DerivedUnit.NewInstance();
111 }else{
112 throw new IllegalStateException("Unknown derived unit type " + this.getRepresentation());
113 }
114 }
115
116 }
117
118
119 private DerivedUnitFacadeConfigurator config;
120
121 //private GatheringEvent gatheringEvent;
122 private DerivedUnitType type; //needed?
123
124 private FieldObservation fieldObservation;
125
126 private DerivedUnitBase derivedUnit;
127
128 //media - the text data holding the media
129 private TextData derivedUnitMediaTextData;
130 private TextData fieldObjectMediaTextData;
131
132
133 private TextData ecology;
134 private TextData plantDescription;
135
136
137 /**
138 * Creates a derived unit facade for a new derived unit of type <code>type</code>.
139 * @param type
140 * @return
141 */
142 public static DerivedUnitFacade NewInstance(DerivedUnitType type){
143 return new DerivedUnitFacade(type);
144 }
145
146 /**
147 * Creates a derived unit facade for a given derived unit using the default configuation.
148 * @param derivedUnit
149 * @return
150 * @throws DerivedUnitFacadeNotSupportedException
151 */
152 public static DerivedUnitFacade NewInstance(DerivedUnitBase derivedUnit) throws DerivedUnitFacadeNotSupportedException{
153 return new DerivedUnitFacade(derivedUnit, null);
154 }
155
156 public static DerivedUnitFacade NewInstance(DerivedUnitBase derivedUnit, DerivedUnitFacadeConfigurator config) throws DerivedUnitFacadeNotSupportedException{
157 return new DerivedUnitFacade(derivedUnit, config);
158 }
159
160
161
162 // ****************** CONSTRUCTOR ****************************************************
163
164 private DerivedUnitFacade(DerivedUnitType type){
165 this.config = DerivedUnitFacadeConfigurator.NewInstance();
166
167 //derivedUnit
168 derivedUnit = type.getNewDerivedUnitInstance();
169 setCacheStrategy();
170 }
171
172 private DerivedUnitFacade(DerivedUnitBase derivedUnit, DerivedUnitFacadeConfigurator config) throws DerivedUnitFacadeNotSupportedException{
173
174 if (config == null){
175 config = DerivedUnitFacadeConfigurator.NewInstance();
176 }
177 this.config = config;
178
179 //derived unit
180 this.derivedUnit = derivedUnit;
181 setCacheStrategy();
182
183 //derivation event
184 if (this.derivedUnit.getDerivedFrom() != null){
185 DerivationEvent derivationEvent = getDerivationEvent(true);
186 //fieldObservation
187 Set<FieldObservation> fieldOriginals = getFieldObservationsOriginals(derivationEvent, null);
188 if (fieldOriginals.size() > 1){
189 throw new DerivedUnitFacadeNotSupportedException("Specimen must not have more than 1 derivation event");
190 }else if (fieldOriginals.size() == 0){
191 //fieldObservation = FieldObservation.NewInstance();
192 }else if (fieldOriginals.size() == 1){
193 fieldObservation = fieldOriginals.iterator().next();
194 //###fieldObservation = getInitializedFieldObservation(fieldObservation);
195 fieldObservation.addPropertyChangeListener(getNewEventPropagationListener());
196 }else{
197 throw new IllegalStateException("Illegal state");
198 }
199 }
200 // #### derivedUnit = getInitializedDerivedUnit(derivedUnit);
201
202 //test if unsupported
203
204 //media
205 //specimen
206 // String objectTypeExceptionText = "Specimen";
207 // SpecimenDescription imageGallery = getImageGalleryWithSupportTest(derivedUnit, objectTypeExceptionText, false);
208 // getImageTextDataWithSupportTest(imageGallery, objectTypeExceptionText);
209 this.derivedUnitMediaTextData = inititialzeTextDataWithSupportTest(Feature.IMAGE(), this.derivedUnit, false, true);
210
211 //field observation
212 // objectTypeExceptionText = "Field observation";
213 // imageGallery = getImageGalleryWithSupportTest(fieldObservation, objectTypeExceptionText, false);
214 // getImageTextDataWithSupportTest(imageGallery, objectTypeExceptionText);
215 fieldObjectMediaTextData = initializeFieldObjectTextDataWithSupportTest(Feature.IMAGE(), false, true);
216
217 //handle derivedUnit.getMedia()
218 if (derivedUnit.getMedia().size() > 0){
219 //TODO better changed model here to allow only one place for images
220 if (this.config.isMoveDerivedUnitMediaToGallery()){
221 Set<Media> mediaSet = derivedUnit.getMedia();
222 for (Media media : mediaSet){
223 this.addDerivedUnitMedia(media);
224 }
225 mediaSet.removeAll(getDerivedUnitMedia());
226 }else{
227 throw new DerivedUnitFacadeNotSupportedException("Specimen may not have direct media. Only (one) image gallery is allowed");
228 }
229 }
230
231 //handle fieldObservation.getMedia()
232 if (fieldObservation != null && fieldObservation.getMedia() != null && fieldObservation.getMedia().size() > 0){
233 //TODO better changed model here to allow only one place for images
234 if (this.config.isMoveFieldObjectMediaToGallery()){
235 Set<Media> mediaSet = fieldObservation.getMedia();
236 for (Media media : mediaSet){
237 this.addFieldObjectMedia(media);
238 }
239 mediaSet.removeAll(getFieldObjectMedia());
240 }else{
241 throw new DerivedUnitFacadeNotSupportedException("Field object may not have direct media. Only (one) image gallery is allowed");
242 }
243 }
244
245 //test if descriptions are supported
246 ecology = initializeFieldObjectTextDataWithSupportTest(Feature.ECOLOGY(), false, false);
247 plantDescription = initializeFieldObjectTextDataWithSupportTest(Feature.DESCRIPTION(), false, false);
248 }
249
250
251 private DerivedUnitBase getInitializedDerivedUnit(DerivedUnitBase derivedUnit) {
252 IOccurrenceService occurrenceService = this.config.getOccurrenceService();
253 if (occurrenceService == null){
254 return derivedUnit;
255 }
256 List<String> propertyPaths = this.config.getPropertyPaths();
257 if (propertyPaths == null){
258 return derivedUnit;
259 }
260 propertyPaths = getDerivedUnitPropertyPaths(propertyPaths);
261 DerivedUnitBase result = (DerivedUnitBase)occurrenceService.load(derivedUnit.getUuid(), propertyPaths);
262 return result;
263 }
264
265 /**
266 * Initializes the derived unit according to the configuartions property path.
267 * If the property path is <code>null</code> or no occurrence service is given the
268 * returned object is the same as the input parameter.
269 * @param fieldObservation2
270 * @return
271 */
272 private FieldObservation getInitializedFieldObservation(FieldObservation fieldObservation) {
273 IOccurrenceService occurrenceService = this.config.getOccurrenceService();
274 if (occurrenceService == null){
275 return fieldObservation;
276 }
277 List<String> propertyPaths = this.config.getPropertyPaths();
278 if (propertyPaths == null){
279 return fieldObservation;
280 }
281 propertyPaths = getFieldObjectPropertyPaths(propertyPaths);
282 FieldObservation result = (FieldObservation)occurrenceService.load(fieldObservation.getUuid(), propertyPaths);
283 return result;
284 }
285
286 /**
287 * Transforms the property paths in a way that the facade is handled just like an
288 * ordinary CdmBase object.<BR>
289 * E.g. a property path "collectinAreas" will be translated into gatheringEvent.collectingAreas
290 * @param propertyPaths
291 * @return
292 */
293 private List<String> getFieldObjectPropertyPaths(List<String> propertyPaths) {
294 List<String> result = new ArrayList<String>();
295 for (String facadePath : propertyPaths){
296 // collecting areas (named area)
297 if (facadePath.startsWith("collectingAreas")){
298 facadePath = "gatheringEvent." + facadePath;
299 result.add(facadePath);
300 }
301 // collector (agentBase)
302 else if (facadePath.startsWith("collector")){
303 facadePath = facadePath.replace("collector", "gatheringEvent.actor");
304 result.add(facadePath);
305 }
306 // exactLocation (agentBase)
307 else if (facadePath.startsWith("exactLocation")){
308 facadePath = "gatheringEvent." + facadePath;
309 result.add(facadePath);
310 }
311 // gatheringPeriod (TimePeriod)
312 else if (facadePath.startsWith("gatheringPeriod")){
313 facadePath = facadePath.replace("gatheringPeriod", "gatheringEvent.timeperiod");
314 result.add(facadePath);
315 }
316 // (locality/ localityLanguage , LanguageString)
317 else if (facadePath.startsWith("locality")){
318 facadePath = "gatheringEvent." + facadePath;
319 result.add(facadePath);
320 }
321
322 //*********** FIELD OBJECT ************
323 // fieldObjectDefinitions (Map<language, languageString)
324 else if (facadePath.startsWith("fieldObjectDefinitions")){
325 // TODO or definition ???
326 facadePath = facadePath.replace("fieldObjectDefinitions", "description");
327 result.add(facadePath);
328 }
329 // fieldObjectMedia (Media)
330 else if (facadePath.startsWith("fieldObjectMedia")){
331 // TODO ???
332 facadePath = facadePath.replace("fieldObjectMedia", "descriptions.elements.media");
333 result.add(facadePath);
334 }
335
336 //Gathering Event will always be added
337 result.add("gatheringEvent");
338
339 }
340
341 /*
342 Gathering Event
343 ====================
344 - gatheringEvent (GatheringEvent)
345
346 Field Object
347 =================
348 - ecology/ ecologyAll (String) ???
349 - plant description (like ecology)
350
351 - fieldObjectImageGallery (SpecimenDescription) - is automatically initialized via fieldObjectMedia
352
353 */
354
355 return result;
356 }
357
358 /**
359 * Transforms the property paths in a way that the facade is handled just like an
360 * ordinary CdmBase object.<BR>
361 * E.g. a property path "collectinAreas" will be translated into gatheringEvent.collectingAreas
362 * @param propertyPaths
363 * @return
364 */
365 private List<String> getDerivedUnitPropertyPaths(List<String> propertyPaths) {
366 List<String> result = new ArrayList<String>();
367 for (String facadePath : propertyPaths){
368 // determinations (DeterminationEvent)
369 if (facadePath.startsWith("determinations")){
370 facadePath = "" + facadePath; //no change
371 result.add(facadePath);
372 }
373 // storedUnder (TaxonNameBase)
374 else if (facadePath.startsWith("storedUnder")){
375 facadePath = "" + facadePath; //no change
376 result.add(facadePath);
377 }
378 // sources (IdentifiableSource)
379 else if (facadePath.startsWith("sources")){
380 facadePath = "" + facadePath; //no change
381 result.add(facadePath);
382 }
383 // collection (Collection)
384 else if (facadePath.startsWith("collection")){
385 facadePath = "" + facadePath; //no change
386 result.add(facadePath);
387 }
388 // (locality/ localityLanguage , LanguageString)
389 else if (facadePath.startsWith("locality")){
390 facadePath = "gatheringEvent." + facadePath;
391 result.add(facadePath);
392 }
393
394 //*********** FIELD OBJECT ************
395 // derivedUnitDefinitions (Map<language, languageString)
396 else if (facadePath.startsWith("derivedUnitDefinitions")){
397 // TODO or definition ???
398 facadePath = facadePath.replace("derivedUnitDefinitions", "description");
399 result.add(facadePath);
400 }
401
402 // derivedUnitMedia (Media)
403 else if (facadePath.startsWith("derivedUnitMedia")){
404 // TODO ???
405 facadePath = facadePath.replace("derivedUnitMedia", "descriptions.elements.media");
406 result.add(facadePath);
407 }
408
409 }
410
411 /*
412 //TODO
413 Derived Unit
414 =====================
415
416 - derivedUnitImageGallery (SpecimenDescription) - is automatically initialized via derivedUnitMedia
417
418 - derivationEvent (DerivationEvent) - will always be initialized
419 - duplicates (??? Specimen???) ???
420 */
421
422 return result;
423 }
424
425 /**
426 *
427 */
428 private void setCacheStrategy() {
429 derivedUnit.setCacheStrategy(new DerivedUnitFacadeCacheStrategy());
430 }
431
432
433 /**
434 * @param feature
435 * @param createIfNotExists
436 * @param isImageGallery
437 * @return
438 * @throws DerivedUnitFacadeNotSupportedException
439 */
440 private TextData initializeFieldObjectTextDataWithSupportTest(Feature feature, boolean createIfNotExists, boolean isImageGallery) throws DerivedUnitFacadeNotSupportedException {
441 //field object
442 FieldObservation fieldObject = getFieldObservation(createIfNotExists) ;
443 if (fieldObject == null){
444 return null;
445 }
446 return inititialzeTextDataWithSupportTest(feature, fieldObject, createIfNotExists, isImageGallery);
447 }
448
449
450 /**
451 * @param feature
452 * @param specimen
453 * @param createIfNotExists
454 * @param isImageGallery
455 * @return
456 * @throws DerivedUnitFacadeNotSupportedException
457 */
458 private TextData inititialzeTextDataWithSupportTest(Feature feature, SpecimenOrObservationBase specimen, boolean createIfNotExists,
459 boolean isImageGallery) throws DerivedUnitFacadeNotSupportedException {
460 if (feature == null ){
461 return null;
462 }
463 TextData textData = null;
464 if (createIfNotExists){
465 textData = TextData.NewInstance(feature);
466 }
467
468 Set<SpecimenDescription> descriptions;
469 if (isImageGallery){
470 descriptions = specimen.getSpecimenDescriptionImageGallery();
471 }else{
472 descriptions = specimen.getSpecimenDescriptions(false);
473 }
474 if (descriptions.size() == 0){
475 if (createIfNotExists){
476 SpecimenDescription newSpecimenDescription = SpecimenDescription.NewInstance(specimen);
477 newSpecimenDescription.addElement(textData);
478 return textData;
479 }else{
480 return null;
481 }
482 }
483 Set<DescriptionElementBase> existingTextData = new HashSet<DescriptionElementBase>();
484 for (SpecimenDescription description : descriptions){
485 for (DescriptionElementBase element: description.getElements()){
486 if (element.isInstanceOf(TextData.class) && ( feature.equals(element.getFeature() )|| isImageGallery ) ){
487 existingTextData.add(element);
488 }
489 }
490 }
491 if (existingTextData.size() > 1){
492 throw new DerivedUnitFacadeNotSupportedException("Specimen facade does not support more than one description text data of type " + feature.getLabel());
493
494 }else if (existingTextData.size() == 1){
495 return CdmBase.deproxy(existingTextData.iterator().next(), TextData.class);
496 }else{
497 SpecimenDescription description = descriptions.iterator().next();
498 description.addElement(textData);
499 return textData;
500 }
501 }
502
503 //************************** METHODS *****************************************
504
505 private TextData getDerivedUnitImageGalleryTextData(boolean createIfNotExists) throws DerivedUnitFacadeNotSupportedException{
506 if (this.derivedUnitMediaTextData == null && createIfNotExists){
507 this.derivedUnitMediaTextData = getImageGalleryTextData(derivedUnit, "Specimen");
508 }
509 return this.derivedUnitMediaTextData;
510 }
511
512 private TextData getObservationImageGalleryTextData(boolean createIfNotExists) throws DerivedUnitFacadeNotSupportedException{
513 if (this.fieldObjectMediaTextData == null && createIfNotExists){
514 this.fieldObjectMediaTextData = getImageGalleryTextData(fieldObservation, "Field observation");
515 }
516 return this.fieldObjectMediaTextData;
517 }
518
519
520
521 /**
522 * @param derivationEvent2
523 * @return
524 * @throws DerivedUnitFacadeNotSupportedException
525 */
526 private Set<FieldObservation> getFieldObservationsOriginals(DerivationEvent derivationEvent, Set<SpecimenOrObservationBase> recursionAvoidSet) throws DerivedUnitFacadeNotSupportedException {
527 if (recursionAvoidSet == null){
528 recursionAvoidSet = new HashSet<SpecimenOrObservationBase>();
529 }
530 Set<FieldObservation> result = new HashSet<FieldObservation>();
531 Set<SpecimenOrObservationBase> originals = derivationEvent.getOriginals();
532 for (SpecimenOrObservationBase original : originals){
533 if (original.isInstanceOf(FieldObservation.class)){
534 result.add(CdmBase.deproxy(original, FieldObservation.class));
535 }else if (original.isInstanceOf(DerivedUnitBase.class)){
536 //if specimen has already been tested exclude it from further recursion
537 if (recursionAvoidSet.contains(original)){
538 continue;
539 }
540 DerivedUnitBase derivedUnit = CdmBase.deproxy(original, DerivedUnitBase.class);
541 DerivationEvent originalDerivation = derivedUnit.getDerivedFrom();
542 // Set<DerivationEvent> derivationEvents = original.getDerivationEvents();
543 // for (DerivationEvent originalDerivation : derivationEvents){
544 Set<FieldObservation> fieldObservations = getFieldObservationsOriginals(originalDerivation, recursionAvoidSet);
545 result.addAll(fieldObservations);
546 // }
547 }else{
548 throw new DerivedUnitFacadeNotSupportedException("Unhandled specimen or observation base type: " + original.getClass().getName() );
549 }
550
551 }
552 return result;
553 }
554
555 //*********** MEDIA METHODS ******************************
556
557 // /**
558 // * Returns the media list for a specimen. Throws an exception if the existing specimen descriptions
559 // * are not supported by this facade.
560 // * @param specimen the specimen the media belongs to
561 // * @param specimenExceptionText text describing the specimen for exception messages
562 // * @return
563 // * @throws DerivedUnitFacadeNotSupportedException
564 // */
565 // private List<Media> getImageGalleryMedia(SpecimenOrObservationBase specimen, String specimenExceptionText) throws DerivedUnitFacadeNotSupportedException{
566 // List<Media> result;
567 // SpecimenDescription imageGallery = getImageGalleryWithSupportTest(specimen, specimenExceptionText, true);
568 // TextData textData = getImageTextDataWithSupportTest(imageGallery, specimenExceptionText);
569 // result = textData.getMedia();
570 // return result;
571 // }
572
573 /**
574 * Returns the media list for a specimen. Throws an exception if the existing specimen descriptions
575 * are not supported by this facade.
576 * @param specimen the specimen the media belongs to
577 * @param specimenExceptionText text describing the specimen for exception messages
578 * @return
579 * @throws DerivedUnitFacadeNotSupportedException
580 */
581 private TextData getImageGalleryTextData(SpecimenOrObservationBase specimen, String specimenExceptionText) throws DerivedUnitFacadeNotSupportedException{
582 TextData result;
583 SpecimenDescription imageGallery = getImageGalleryWithSupportTest(specimen, specimenExceptionText, true);
584 result = getImageTextDataWithSupportTest(imageGallery, specimenExceptionText);
585 return result;
586 }
587
588
589 /**
590 * Returns the image gallery of the according specimen. Throws an exception if the attached
591 * image gallerie(s) are not supported by this facade.
592 * If no image gallery exists a new one is created if <code>createNewIfNotExists</code> is true and
593 * if specimen is not <code>null</code>.
594 * @param specimen
595 * @param specimenText
596 * @param createNewIfNotExists
597 * @return
598 * @throws DerivedUnitFacadeNotSupportedException
599 */
600 private SpecimenDescription getImageGalleryWithSupportTest(SpecimenOrObservationBase<?> specimen, String specimenText, boolean createNewIfNotExists) throws DerivedUnitFacadeNotSupportedException{
601 if (specimen == null){
602 return null;
603 }
604 SpecimenDescription imageGallery;
605 if (hasMultipleImageGalleries(specimen)){
606 throw new DerivedUnitFacadeNotSupportedException( specimenText + " must not have more than 1 image gallery");
607 }else{
608 imageGallery = getImageGallery(specimen, createNewIfNotExists);
609 getImageTextDataWithSupportTest(imageGallery, specimenText);
610 }
611 return imageGallery;
612 }
613
614 /**
615 * Returns the media holding text data element of the image gallery. Throws an exception if multiple
616 * such text data already exist.
617 * Creates a new text data if none exists and adds it to the image gallery.
618 * If image gallery is <code>null</code> nothing happens.
619 * @param imageGallery
620 * @param textData
621 * @return
622 * @throws DerivedUnitFacadeNotSupportedException
623 */
624 private TextData getImageTextDataWithSupportTest(SpecimenDescription imageGallery, String specimenText) throws DerivedUnitFacadeNotSupportedException {
625 if (imageGallery == null){
626 return null;
627 }
628 TextData textData = null;
629 for (DescriptionElementBase element: imageGallery.getElements()){
630 if (element.isInstanceOf(TextData.class) && element.getFeature().equals(Feature.IMAGE())){
631 if (textData != null){
632 throw new DerivedUnitFacadeNotSupportedException( specimenText + " must not have more than 1 image text data element in image gallery");
633 }
634 textData = CdmBase.deproxy(element, TextData.class);
635 }
636 }
637 if (textData == null){
638 textData = TextData.NewInstance(Feature.IMAGE());
639 imageGallery.addElement(textData);
640 }
641 return textData;
642 }
643
644 /**
645 * Checks, if a specimen belongs to more than one description that is an image gallery
646 * @param derivedUnit
647 * @return
648 */
649 private boolean hasMultipleImageGalleries(SpecimenOrObservationBase<?> derivedUnit){
650 int count = 0;
651 Set<SpecimenDescription> descriptions= derivedUnit.getSpecimenDescriptions();
652 for (SpecimenDescription description : descriptions){
653 if (description.isImageGallery()){
654 count++;
655 }
656 }
657 return (count > 1);
658 }
659
660
661 /**
662 * Returns the image gallery for a specimen. If there are multiple specimen descriptions
663 * marked as image galleries an arbitrary one is chosen.
664 * If no image gallery exists, a new one is created if <code>createNewIfNotExists</code>
665 * is <code>true</code>.<Br>
666 * If specimen is <code>null</code> a null pointer exception is thrown.
667 * @param createNewIfNotExists
668 * @return
669 */
670 private SpecimenDescription getImageGallery(SpecimenOrObservationBase<?> specimen, boolean createIfNotExists) {
671 SpecimenDescription result = null;
672 Set<SpecimenDescription> descriptions= specimen.getSpecimenDescriptions();
673 for (SpecimenDescription description : descriptions){
674 if (description.isImageGallery()){
675 result = description;
676 break;
677 }
678 }
679 if (result == null && createIfNotExists){
680 result = SpecimenDescription.NewInstance(specimen);
681 result.setImageGallery(true);
682 }
683 return result;
684 }
685
686 /**
687 * Adds a media to the specimens image gallery. If media is <code>null</code> nothing happens.
688 * @param media
689 * @param specimen
690 * @return true if media is not null (as specified by {@link java.util.Collection#add(Object) Collection.add(E e)}
691 * @throws DerivedUnitFacadeNotSupportedException
692 */
693 private boolean addMedia(Media media, SpecimenOrObservationBase<?> specimen) throws DerivedUnitFacadeNotSupportedException {
694 if (media != null){
695 List<Media> mediaList = getMedia(specimen, true);
696 return mediaList.add(media);
697 }else{
698 return false;
699 }
700 }
701
702 /**
703 * Removes a media from the specimens image gallery.
704 * @param media
705 * @param specimen
706 * @return true if an element was removed as a result of this call (as specified by {@link java.util.Collection#remove(Object) Collection.remove(E e)}
707 * @throws DerivedUnitFacadeNotSupportedException
708 */
709 private boolean removeMedia(Media media, SpecimenOrObservationBase<?> specimen) throws DerivedUnitFacadeNotSupportedException {
710 List<Media> mediaList = getMedia(specimen, true);
711 return mediaList == null ? null : mediaList.remove(media);
712 }
713
714 private List<Media> getMedia(SpecimenOrObservationBase<?> specimen, boolean createIfNotExists) throws DerivedUnitFacadeNotSupportedException {
715 TextData textData = getMediaTextData(specimen, createIfNotExists);
716 return textData == null ? null : textData.getMedia();
717 }
718
719 /**
720 * Returns the one media list of a specimen which is part of the only image gallery that
721 * this specimen is part of.<BR>
722 * If these conditions are not hold an exception is thrwon.
723 * @param specimen
724 * @return
725 * @throws DerivedUnitFacadeNotSupportedException
726 */
727 // private List<Media> getMedia(SpecimenOrObservationBase<?> specimen) throws DerivedUnitFacadeNotSupportedException {
728 // if (specimen == null){
729 // return null;
730 // }
731 // if (specimen == this.derivedUnit){
732 // return getDerivedUnitImageGalleryMedia();
733 // }else if (specimen == this.fieldObservation){
734 // return getObservationImageGalleryTextData();
735 // }else{
736 // return getImageGalleryMedia(specimen, "Undefined specimen ");
737 // }
738 // }
739
740 /**
741 * Returns the one media list of a specimen which is part of the only image gallery that
742 * this specimen is part of.<BR>
743 * If these conditions are not hold an exception is thrwon.
744 * @param specimen
745 * @return
746 * @throws DerivedUnitFacadeNotSupportedException
747 */
748 private TextData getMediaTextData(SpecimenOrObservationBase<?> specimen, boolean createIfNotExists) throws DerivedUnitFacadeNotSupportedException {
749 if (specimen == null){
750 return null;
751 }
752 if (specimen == this.derivedUnit){
753 return getDerivedUnitImageGalleryTextData(createIfNotExists);
754 }else if (specimen == this.fieldObservation){
755 return getObservationImageGalleryTextData(createIfNotExists);
756 }else{
757 return getImageGalleryTextData(specimen, "Undefined specimen ");
758 }
759 }
760
761
762 //****************** GETTER / SETTER / ADDER / REMOVER ***********************/
763
764 // ****************** Gathering Event *********************************/
765
766 //Collecting area
767 public void addCollectingArea(NamedArea area) {
768 getGatheringEvent(true).addCollectingArea(area);
769 }
770 public void addCollectingAreas(java.util.Collection<NamedArea> areas) {
771 for (NamedArea area : areas){
772 getGatheringEvent(true).addCollectingArea(area);
773 }
774 }
775 public Set<NamedArea> getCollectingAreas() {
776 return (hasGatheringEvent() ? getGatheringEvent(true).getCollectingAreas() : null);
777 }
778 public void removeCollectingArea(NamedArea area) {
779 if (hasGatheringEvent()){
780 getGatheringEvent(true).removeCollectingArea(area);
781 }
782 }
783
784 //absolute elevation
785 /** meter above/below sea level of the surface
786 * @see #getAbsoluteElevationError()
787 * @see #getAbsoluteElevationRange()
788 **/
789 @Transient
790 public Integer getAbsoluteElevation() {
791 return (hasGatheringEvent() ? getGatheringEvent(true).getAbsoluteElevation() : null);
792 }
793 public void setAbsoluteElevation(Integer absoluteElevation) {
794 getGatheringEvent(true).setAbsoluteElevation(absoluteElevation);
795 }
796
797 //absolute elevation error
798 @Transient
799 public Integer getAbsoluteElevationError() {
800 return (hasGatheringEvent() ? getGatheringEvent(true).getAbsoluteElevationError() : null);
801 }
802 public void setAbsoluteElevationError(Integer absoluteElevationError) {
803 getGatheringEvent(true).setAbsoluteElevationError(absoluteElevationError);
804 }
805
806 /**
807 * @see #getAbsoluteElevation()
808 * @see #getAbsoluteElevationError()
809 * @see #setAbsoluteElevationRange(Integer, Integer)
810 * @see #getAbsoluteElevationMaximum()
811 */
812 @Transient
813 public Integer getAbsoluteElevationMinimum(){
814 if ( ! hasGatheringEvent() ){
815 return null;
816 }
817 Integer minimum = getGatheringEvent(true).getAbsoluteElevation();
818 if (getGatheringEvent(true).getAbsoluteElevationError() != null){
819 minimum = minimum - getGatheringEvent(true).getAbsoluteElevationError();
820 }
821 return minimum;
822 }
823 /**
824 * @see #getAbsoluteElevation()
825 * @see #getAbsoluteElevationError()
826 * @see #setAbsoluteElevationRange(Integer, Integer)
827 * @see #getAbsoluteElevationMinimum()
828 */
829 @Transient
830 public Integer getAbsoluteElevationMaximum(){
831 if ( ! hasGatheringEvent() ){
832 return null;
833 }
834 Integer maximum = getGatheringEvent(true).getAbsoluteElevation();
835 if (getGatheringEvent(true).getAbsoluteElevationError() != null){
836 maximum = maximum + getGatheringEvent(true).getAbsoluteElevationError();
837 }
838 return maximum;
839 }
840
841
842 /**
843 * This method replaces absoluteElevation and absoulteElevationError by
844 * internally translating minimum and maximum values into
845 * average and error values. As all these values are integer based
846 * it is necessary that the distance is between minimum and maximum is <b>even</b>,
847 * otherwise we will get a rounding error resulting in a maximum that is increased
848 * by 1.
849 * @see #setAbsoluteElevation(Integer)
850 * @see #setAbsoluteElevationError(Integer)
851 * @param minimumElevation minimum of the range
852 * @param maximumElevation maximum of the range
853 */
854 public void setAbsoluteElevationRange(Integer minimumElevation, Integer maximumElevation){
855 if (minimumElevation == null || maximumElevation == null){
856 Integer elevation = minimumElevation;
857 Integer error = 0;
858 if (minimumElevation == null){
859 elevation = maximumElevation;
860 if (elevation == null){
861 error = null;
862 }
863 }
864 getGatheringEvent(true).setAbsoluteElevation(elevation);
865 getGatheringEvent(true).setAbsoluteElevationError(error);
866 }else{
867 if (! isEvenDistance(minimumElevation, maximumElevation) ){
868 throw new IllegalArgumentException("Distance between minimum and maximum elevation must be even but was " + Math.abs(minimumElevation - maximumElevation));
869 }
870 Integer absoluteElevationError = Math.abs(maximumElevation - minimumElevation);
871 absoluteElevationError = absoluteElevationError / 2;
872 Integer absoluteElevation = minimumElevation + absoluteElevationError;
873 getGatheringEvent(true).setAbsoluteElevation(absoluteElevation);
874 getGatheringEvent(true).setAbsoluteElevationError(absoluteElevationError);
875 }
876 }
877
878 /**
879 * @param minimumElevation
880 * @param maximumElevation
881 * @return
882 */
883 private boolean isEvenDistance(Integer minimumElevation, Integer maximumElevation) {
884 Integer diff = ( maximumElevation - minimumElevation);
885 Integer testDiff = (diff /2) *2 ;
886 return (testDiff == diff);
887 }
888
889 //collector
890 public AgentBase getCollector() {
891 return (hasGatheringEvent() ? getGatheringEvent(true).getCollector() : null);
892 }
893 public void setCollector(AgentBase collector){
894 getGatheringEvent(true).setCollector(collector);
895 }
896
897 //collecting method
898 public String getCollectingMethod() {
899 return (hasGatheringEvent() ? getGatheringEvent(true).getCollectingMethod() : null);
900 }
901 public void setCollectingMethod(String collectingMethod) {
902 getGatheringEvent(true).setCollectingMethod(collectingMethod);
903 }
904
905 //distance to ground
906 @Transient
907 public Integer getDistanceToGround() {
908 return (hasGatheringEvent() ? getGatheringEvent(true).getDistanceToGround() : null);
909 }
910 public void setDistanceToGround(Integer distanceToGround) {
911 getGatheringEvent(true).setDistanceToGround(distanceToGround);
912 }
913
914 //distance to water surface
915 @Transient
916 public Integer getDistanceToWaterSurface() {
917 return (hasGatheringEvent() ? getGatheringEvent(true).getDistanceToWaterSurface() : null);
918 }
919 public void setDistanceToWaterSurface(Integer distanceToWaterSurface) {
920 getGatheringEvent(true).setDistanceToWaterSurface(distanceToWaterSurface);
921 }
922
923 //exact location
924 public Point getExactLocation() {
925 return (hasGatheringEvent() ? getGatheringEvent(true).getExactLocation() : null );
926 }
927
928 /**
929 * Returns a sexagesimal representation of the exact location (e.g. 12°59'N, 35°23E).
930 * If the exact location is <code>null</code> the empty string is returned.
931 * @param includeEmptySeconds
932 * @param includeReferenceSystem
933 * @return
934 */
935 public String getExactLocationText(boolean includeEmptySeconds, boolean includeReferenceSystem){
936 return (this.getExactLocation() == null ? "" : this.getExactLocation().toSexagesimalString(includeEmptySeconds, includeReferenceSystem));
937 }
938 public void setExactLocation(Point exactLocation) {
939 getGatheringEvent(true).setExactLocation(exactLocation);
940 }
941 public void setExactLocationByParsing(String longitudeToParse, String latitudeToParse, ReferenceSystem referenceSystem, Integer errorRadius) throws ParseException{
942 Point point = Point.NewInstance(null, null, referenceSystem, errorRadius);
943 point.setLongitudeByParsing(longitudeToParse);
944 point.setLatitudeByParsing(latitudeToParse);
945 setExactLocation(point);
946 }
947
948 //gathering event description
949 public String getGatheringEventDescription() {
950 return (hasGatheringEvent() ? getGatheringEvent(true).getDescription() : null);
951 }
952 public void setGatheringEventDescription(String description) {
953 getGatheringEvent(true).setDescription(description);
954 }
955
956 //gatering period
957 public TimePeriod getGatheringPeriod() {
958 return (hasGatheringEvent() ? getGatheringEvent(true).getTimeperiod() : null);
959 }
960 public void setGatheringPeriod(TimePeriod timeperiod) {
961 getGatheringEvent(true).setTimeperiod(timeperiod);
962 }
963
964 //locality
965 public LanguageString getLocality(){
966 return (hasGatheringEvent() ? getGatheringEvent(true).getLocality() : null);
967 }
968 @Transient
969 public String getLocalityText(){
970 LanguageString locality = getLocality();
971 if(locality != null){
972 return locality.getText();
973 }
974 return null;
975 }
976 @Transient
977 public Language getLocalityLanguage(){
978 LanguageString locality = getLocality();
979 if(locality != null){
980 return locality.getLanguage();
981 }
982 return null;
983 }
984
985 /**
986 * Sets the locality string in the default language
987 * @param locality
988 */
989 public void setLocality(String locality){
990 Language language = Language.DEFAULT();
991 setLocality(locality, language);
992 }
993 public void setLocality(String locality, Language language){
994 LanguageString langString = LanguageString.NewInstance(locality, language);
995 setLocality(langString);
996 }
997 public void setLocality(LanguageString locality){
998 getGatheringEvent(true).setLocality(locality);
999 }
1000
1001 /**
1002 * The gathering event will be used for the field object instead of the old gathering event.<BR>
1003 * <B>This method will override all gathering values (see below).</B>
1004 * @see #getAbsoluteElevation()
1005 * @see #getAbsoluteElevationError()
1006 * @see #getDistanceToGround()
1007 * @see #getDistanceToWaterSurface()
1008 * @see #getExactLocation()
1009 * @see #getGatheringEventDescription()
1010 * @see #getGatheringPeriod()
1011 * @see #getCollectingAreas()
1012 * @see #getCollectingMethod()
1013 * @see #getLocality()
1014 * @see #getCollector()
1015 * @param gatheringEvent
1016 */
1017 public void setGatheringEvent(GatheringEvent gatheringEvent) {
1018 getFieldObservation(true).setGatheringEvent(gatheringEvent);
1019 }
1020 public boolean hasGatheringEvent(){
1021 return (getGatheringEvent(false) != null);
1022 }
1023 @Transient
1024 public GatheringEvent getGatheringEvent() {
1025 return getGatheringEvent(false);
1026 }
1027
1028 public GatheringEvent getGatheringEvent(boolean createIfNotExists) {
1029 if (! hasFieldObservation() && ! createIfNotExists){
1030 return null;
1031 }
1032 if (createIfNotExists && getFieldObservation(true).getGatheringEvent() == null ){
1033 GatheringEvent gatheringEvent = GatheringEvent.NewInstance();
1034 getFieldObservation(true).setGatheringEvent(gatheringEvent);
1035 }
1036 return getFieldObservation(true).getGatheringEvent();
1037 }
1038
1039 // ****************** Field Object ************************************/
1040
1041 /**
1042 * Returns true if a field observation exists (even if all attributes are empty or <code>null<code>.
1043 * @return
1044 */
1045 public boolean hasFieldObject(){
1046 return this.fieldObservation != null;
1047 }
1048
1049 //ecology
1050 @Transient
1051 public String getEcology(){
1052 return getEcology(Language.DEFAULT());
1053 }
1054 public String getEcology(Language language){
1055 LanguageString languageString = getEcologyAll().get(language);
1056 return (languageString == null ? null : languageString.getText());
1057 }
1058 // public String getEcologyPreferred(List<Language> languages){
1059 // LanguageString languageString = getEcologyAll().getPreferredLanguageString(languages);
1060 // return languageString.getText();
1061 // }
1062 public Map<Language, LanguageString> getEcologyAll(){
1063 if (ecology == null){
1064 try {
1065 ecology = initializeFieldObjectTextDataWithSupportTest(Feature.ECOLOGY(), false, false);
1066 } catch (DerivedUnitFacadeNotSupportedException e) {
1067 throw new IllegalStateException(notSupportMessage, e);
1068 }
1069 if (ecology == null){
1070 return new HashMap<Language, LanguageString>();
1071 }
1072 }
1073 return ecology.getMultilanguageText();
1074 }
1075
1076 public void setEcology(String ecology){
1077 setEcology(ecology, null);
1078 }
1079 public void setEcology(String ecologyText, Language language){
1080 if (language == null){
1081 language = Language.DEFAULT();
1082 }
1083 if (ecology == null){
1084 try {
1085 ecology = initializeFieldObjectTextDataWithSupportTest(Feature.ECOLOGY(), true, false);
1086 } catch (DerivedUnitFacadeNotSupportedException e) {
1087 throw new IllegalStateException(notSupportMessage, e);
1088 }
1089 }
1090 if (ecologyText == null){
1091 ecology.removeText(language);
1092 }else{
1093 ecology.putText(ecologyText, language);
1094 }
1095 }
1096 public void removeEcology(Language language){
1097 setEcology(null, language);
1098 }
1099 /**
1100 * Removes ecology for the default language
1101 */
1102 public void removeEcology(){
1103 setEcology(null, null);
1104 }
1105 public void removeEcologyAll(){
1106
1107 }
1108
1109
1110 //plant description
1111 @Transient
1112 public String getPlantDescription(){
1113 return getPlantDescription(null);
1114 }
1115 public String getPlantDescription(Language language){
1116 if (language == null){
1117 language = Language.DEFAULT();
1118 }
1119 LanguageString languageString = getPlantDescriptionAll().get(language);
1120 return (languageString == null ? null : languageString.getText());
1121 }
1122 // public String getPlantDescriptionPreferred(List<Language> languages){
1123 // LanguageString languageString = getPlantDescriptionAll().getPreferredLanguageString(languages);
1124 // return languageString.getText();
1125 // }
1126 public Map<Language, LanguageString> getPlantDescriptionAll(){
1127 if (plantDescription == null){
1128 try {
1129 plantDescription = initializeFieldObjectTextDataWithSupportTest(Feature.DESCRIPTION(), false, false);
1130 } catch (DerivedUnitFacadeNotSupportedException e) {
1131 throw new IllegalStateException(notSupportMessage, e);
1132 }
1133 if (plantDescription == null){
1134 return new HashMap<Language, LanguageString>();
1135 }
1136 }
1137 return plantDescription.getMultilanguageText();
1138 }
1139 public void setPlantDescription(String plantDescription){
1140 setPlantDescription(plantDescription, null);
1141 }
1142 public void setPlantDescription(String plantDescriptionText, Language language){
1143 if (language == null){
1144 language = Language.DEFAULT();
1145 }
1146 if (plantDescription == null){
1147 try {
1148 plantDescription = initializeFieldObjectTextDataWithSupportTest(Feature.DESCRIPTION(), true, false);
1149 } catch (DerivedUnitFacadeNotSupportedException e) {
1150 throw new IllegalStateException(notSupportMessage, e);
1151 }
1152 }
1153 if (plantDescriptionText == null){
1154 plantDescription.removeText(language);
1155 }else{
1156 plantDescription.putText(plantDescriptionText, language);
1157 }
1158 }
1159 public void removePlantDescription(Language language){
1160 setPlantDescription(null, language);
1161 }
1162
1163 //field object definition
1164 public void addFieldObjectDefinition(String text, Language language) {
1165 getFieldObservation(true).addDefinition(text, language);
1166 }
1167
1168 public Map<Language, LanguageString> getFieldObjectDefinition() {
1169 if (! hasFieldObservation()){
1170 return new HashMap<Language, LanguageString>();
1171 }else{
1172 return getFieldObservation(true).getDefinition();
1173 }
1174 }
1175 public String getFieldObjectDefinition(Language language) {
1176 Map<Language, LanguageString> map = getFieldObjectDefinition();
1177 LanguageString languageString = (map == null? null : map.get(language));
1178 if (languageString != null){
1179 return languageString.getText();
1180 }else {
1181 return null;
1182 }
1183 }
1184 public void removeFieldObjectDefinition(Language lang) {
1185 if (hasFieldObservation()){
1186 getFieldObservation(true).removeDefinition(lang);
1187 }
1188 }
1189
1190
1191 //media
1192 public boolean addFieldObjectMedia(Media media) {
1193 try {
1194 return addMedia(media, getFieldObservation(true));
1195 } catch (DerivedUnitFacadeNotSupportedException e) {
1196 throw new IllegalStateException(notSupportMessage, e);
1197 }
1198 }
1199 /**
1200 * Returns true, if an image gallery for the field object exists.<BR>
1201 * Returns also <code>true</code> if the image gallery is empty.
1202 * @return
1203 */
1204 public boolean hasFieldObjectImageGallery(){
1205 if (! hasFieldObject()){
1206 return false;
1207 }else{
1208 return (getImageGallery(fieldObservation, false) != null);
1209 }
1210 }
1211
1212 /**
1213 * @param createIfNotExists
1214 * @return
1215 */
1216 public SpecimenDescription getFieldObjectImageGallery(boolean createIfNotExists){
1217 TextData textData;
1218 try {
1219 textData = initializeFieldObjectTextDataWithSupportTest(Feature.IMAGE(), createIfNotExists, true);
1220 } catch (DerivedUnitFacadeNotSupportedException e) {
1221 throw new IllegalStateException(notSupportMessage, e);
1222 }
1223 if (textData != null){
1224 return CdmBase.deproxy(textData.getInDescription(), SpecimenDescription.class);
1225 }else{
1226 return null;
1227 }
1228 }
1229 /**
1230 * Returns the media for the field object.<BR>
1231 * @return
1232 */
1233 public List<Media> getFieldObjectMedia() {
1234 try {
1235 List<Media> result = getMedia(getFieldObservation(false), false);
1236 return result == null ? new ArrayList<Media>() : result;
1237 } catch (DerivedUnitFacadeNotSupportedException e) {
1238 throw new IllegalStateException(notSupportMessage, e);
1239 }
1240 }
1241 public boolean removeFieldObjectMedia(Media media) {
1242 try {
1243 return removeMedia(media, getFieldObservation(false));
1244 } catch (DerivedUnitFacadeNotSupportedException e) {
1245 throw new IllegalStateException(notSupportMessage, e);
1246 }
1247 }
1248
1249 //field number
1250 public String getFieldNumber() {
1251 if (! hasFieldObservation()){
1252 return null;
1253 }else{
1254 return getFieldObservation(true).getFieldNumber();
1255 }
1256 }
1257 public void setFieldNumber(String fieldNumber) {
1258 getFieldObservation(true).setFieldNumber(fieldNumber);
1259 }
1260
1261
1262 //field notes
1263 public String getFieldNotes() {
1264 if (! hasFieldObservation()){
1265 return null;
1266 }else{
1267 return getFieldObservation(true).getFieldNotes();
1268 }
1269 }
1270 public void setFieldNotes(String fieldNotes) {
1271 getFieldObservation(true).setFieldNotes(fieldNotes);
1272 }
1273
1274
1275 //individual counts
1276 public Integer getIndividualCount() {
1277 return (hasFieldObservation()? getFieldObservation(true).getIndividualCount() : null );
1278 }
1279 public void setIndividualCount(Integer individualCount) {
1280 getFieldObservation(true).setIndividualCount(individualCount);
1281 }
1282
1283 //life stage
1284 public Stage getLifeStage() {
1285 return (hasFieldObservation()? getFieldObservation(true).getLifeStage() : null );
1286 }
1287 public void setLifeStage(Stage lifeStage) {
1288 getFieldObservation(true).setLifeStage(lifeStage);
1289 }
1290
1291 //sex
1292 public Sex getSex() {
1293 return (hasFieldObservation()? getFieldObservation(true).getSex() : null );
1294 }
1295 public void setSex(Sex sex) {
1296 getFieldObservation(true).setSex(sex);
1297 }
1298
1299
1300 //field observation
1301 public boolean hasFieldObservation(){
1302 return (getFieldObservation(false) != null);
1303 }
1304
1305 /**
1306 * Returns the field observation as an object.
1307 * @return
1308 */
1309 @Transient
1310 public FieldObservation getFieldObservation(){
1311 return getFieldObservation(false);
1312 }
1313
1314 /**
1315 * Returns the field observation as an object.
1316 * @return
1317 */
1318 public FieldObservation getFieldObservation(boolean createIfNotExists){
1319 if (fieldObservation == null && createIfNotExists){
1320 fieldObservation = FieldObservation.NewInstance();
1321 fieldObservation.addPropertyChangeListener(getNewEventPropagationListener());
1322 DerivationEvent derivationEvent = getDerivationEvent(true);
1323 derivationEvent.addOriginal(fieldObservation);
1324 }
1325 return this.fieldObservation;
1326 }
1327
1328
1329
1330
1331
1332 //****************** Specimen **************************************************
1333
1334 //Definition
1335 public void addDerivedUnitDefinition(String text, Language language) {
1336 derivedUnit.addDefinition(text, language);
1337 }
1338 public Map<Language, LanguageString> getDerivedUnitDefinitions(){
1339 return this.derivedUnit.getDefinition();
1340 }
1341 public String getDerivedUnitDefinition(Language language) {
1342 Map<Language,LanguageString> languageMap = derivedUnit.getDefinition();
1343 LanguageString languageString = languageMap.get(language);
1344 if (languageString != null){
1345 return languageString.getText();
1346 }else {
1347 return null;
1348 }
1349 }
1350 public void removeDerivedUnitDefinition(Language lang) {
1351 derivedUnit.removeDefinition(lang);
1352 }
1353
1354 //Determination
1355 public void addDetermination(DeterminationEvent determination) {
1356 derivedUnit.addDetermination(determination);
1357 }
1358 @Transient
1359 public Set<DeterminationEvent> getDeterminations() {
1360 return derivedUnit.getDeterminations();
1361 }
1362 public void removeDetermination(DeterminationEvent determination) {
1363 derivedUnit.removeDetermination(determination);
1364 }
1365
1366 //Media
1367 public boolean addDerivedUnitMedia(Media media) {
1368 try {
1369 return addMedia(media, derivedUnit);
1370 } catch (DerivedUnitFacadeNotSupportedException e) {
1371 throw new IllegalStateException(notSupportMessage, e);
1372 }
1373 }
1374 /**
1375 * Returns true, if an image gallery exists for the specimen.<BR>
1376 * Returns also <code>true</code> if the image gallery is empty.
1377 */
1378 public boolean hasDerivedUnitImageGallery(){
1379 return (getImageGallery(derivedUnit, false) != null);
1380 }
1381
1382 public SpecimenDescription getDerivedUnitImageGallery(boolean createIfNotExists){
1383 TextData textData;
1384 try {
1385 textData = inititialzeTextDataWithSupportTest(Feature.IMAGE(), derivedUnit, createIfNotExists, true);
1386 } catch (DerivedUnitFacadeNotSupportedException e) {
1387 throw new IllegalStateException(notSupportMessage, e);
1388 }
1389 if (textData != null){
1390 return CdmBase.deproxy(textData.getInDescription(), SpecimenDescription.class);
1391 }else{
1392 return null;
1393 }
1394 }
1395
1396 /**
1397 * Returns the media for the specimen.<BR>
1398 * @return
1399 */
1400 public List<Media> getDerivedUnitMedia() {
1401 try {
1402 List<Media> result = getMedia(derivedUnit, false);
1403 return result == null ? new ArrayList<Media>() : result;
1404 } catch (DerivedUnitFacadeNotSupportedException e) {
1405 throw new IllegalStateException(notSupportMessage, e);
1406 }
1407 }
1408 public boolean removeDerivedUnitMedia(Media media) {
1409 try {
1410 return removeMedia(media, derivedUnit);
1411 } catch (DerivedUnitFacadeNotSupportedException e) {
1412 throw new IllegalStateException(notSupportMessage, e);
1413 }
1414 }
1415
1416
1417 //Accession Number
1418 @Transient
1419 public String getAccessionNumber() {
1420 return derivedUnit.getAccessionNumber();
1421 }
1422 public void setAccessionNumber(String accessionNumber) {
1423 derivedUnit.setAccessionNumber(accessionNumber);
1424 }
1425
1426 //Catalog Number
1427 public String getCatalogNumber() {
1428 return derivedUnit.getCatalogNumber();
1429 }
1430 public void setCatalogNumber(String catalogNumber) {
1431 derivedUnit.setCatalogNumber(catalogNumber);
1432 }
1433
1434 //Preservation Method
1435
1436 /**
1437 * Only supported by specimen and fossils
1438 * @see #DerivedUnitType
1439 * @return
1440 */
1441 public PreservationMethod getPreservationMethod() throws MethodNotSupportedByDerivedUnitTypeException {
1442 if (derivedUnit.isInstanceOf(Specimen.class)){
1443 return CdmBase.deproxy(derivedUnit, Specimen.class).getPreservation();
1444 }else{
1445 if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()){
1446 throw new MethodNotSupportedByDerivedUnitTypeException("A preservation method is only available in derived units of type 'Specimen' or 'Fossil'");
1447 }else{
1448 return null;
1449 }
1450 }
1451 }
1452 /**
1453 * Only supported by specimen and fossils
1454 * @see #DerivedUnitType
1455 * @return
1456 */
1457 public void setPreservationMethod(PreservationMethod preservation)throws MethodNotSupportedByDerivedUnitTypeException {
1458 if (derivedUnit.isInstanceOf(Specimen.class)){
1459 CdmBase.deproxy(derivedUnit, Specimen.class).setPreservation(preservation);
1460 }else{
1461 if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()){
1462 throw new MethodNotSupportedByDerivedUnitTypeException("A preservation method is only available in derived units of type 'Specimen' or 'Fossil'");
1463 }else{
1464 return;
1465 }
1466 }
1467 }
1468
1469 //Stored under name
1470 public TaxonNameBase getStoredUnder() {
1471 return derivedUnit.getStoredUnder();
1472 }
1473 public void setStoredUnder(TaxonNameBase storedUnder) {
1474 derivedUnit.setStoredUnder(storedUnder);
1475 }
1476
1477 //colletors number
1478 public String getCollectorsNumber() {
1479 return derivedUnit.getCollectorsNumber();
1480 }
1481 public void setCollectorsNumber(String collectorsNumber) {
1482 this.derivedUnit.setCollectorsNumber(collectorsNumber);
1483 }
1484
1485 //title cache
1486 public String getTitleCache() {
1487 if (! derivedUnit.isProtectedTitleCache()){
1488 //always compute title cache anew as long as there are no property change listeners on
1489 //field observation, gathering event etc
1490 derivedUnit.setTitleCache(null, false);
1491 }
1492 return this.derivedUnit.getTitleCache();
1493 }
1494 public void setTitleCache(String titleCache, boolean isProtected) {
1495 this.derivedUnit.setTitleCache(titleCache, isProtected);
1496 }
1497
1498
1499 /**
1500 * Returns the derived unit itself.
1501 * @return the derived unit
1502 */
1503 @Transient
1504 public DerivedUnitBase getDerivedUnit() {
1505 return this.derivedUnit;
1506 }
1507
1508 private boolean hasDerivationEvent(){
1509 return getDerivationEvent() == null ? false : true;
1510 }
1511 private DerivationEvent getDerivationEvent(){
1512 return getDerivationEvent(false);
1513 }
1514 private DerivationEvent getDerivationEvent(boolean createIfNotExists){
1515 DerivationEvent result = derivedUnit.getDerivedFrom();
1516 if (result == null){
1517 result = DerivationEvent.NewInstance();
1518 derivedUnit.setDerivedFrom(result);
1519 }
1520 return result;
1521 }
1522
1523 public String getExsiccatum() {
1524 logger.warn("Exsiccatum method not yet supported. Needs model change");
1525 return null;
1526 }
1527
1528 public String setExsiccatum() throws MethodNotSupportedException{
1529 throw new MethodNotSupportedException("Exsiccatum method not yet supported. Needs model change");
1530 }
1531
1532
1533 // **** sources **/
1534 public void addSource(IdentifiableSource source){
1535 this.derivedUnit.addSource(source);
1536 }
1537 /**
1538 * Creates an orignal source, adds it to the specimen and returns it.
1539 * @param reference
1540 * @param microReference
1541 * @param originalNameString
1542 * @return
1543 */
1544 public IdentifiableSource addSource(ReferenceBase reference, String microReference, String originalNameString){
1545 IdentifiableSource source = IdentifiableSource.NewInstance(reference, microReference);
1546 source.setOriginalNameString(originalNameString);
1547 derivedUnit.addSource(source);
1548 return source;
1549 }
1550
1551 public Set<IdentifiableSource> getSources(){
1552 return derivedUnit.getSources();
1553 }
1554
1555 public void removeSource(IdentifiableSource source){
1556 this.derivedUnit.removeSource(source);
1557 }
1558
1559
1560 /**
1561 * @return the collection
1562 */
1563 public Collection getCollection() {
1564 return derivedUnit.getCollection();
1565 }
1566
1567
1568 /**
1569 * @param collection the collection to set
1570 */
1571 public void setCollection(Collection collection) {
1572 derivedUnit.setCollection(collection);
1573 }
1574
1575 //annotation
1576 public void addAnnotation(Annotation annotation){
1577 this.derivedUnit.addAnnotation(annotation);
1578 }
1579
1580 @Transient
1581 public void getAnnotations(){
1582 this.derivedUnit.getAnnotations();
1583 }
1584
1585 public void removeAnnotation(Annotation annotation){
1586 this.derivedUnit.removeAnnotation(annotation);
1587 }
1588
1589
1590 // ******************************* Events *********************************************
1591
1592 /**
1593 * @return
1594 */
1595 private PropertyChangeListener getNewEventPropagationListener() {
1596 PropertyChangeListener listener = new PropertyChangeListener(){
1597 @Override
1598 public void propertyChange(PropertyChangeEvent event) {
1599 derivedUnit.firePropertyChange(event);
1600 }
1601
1602 };
1603 return listener;
1604 }
1605
1606
1607
1608
1609 //**************** Other Collections ***************************************************
1610
1611 /**
1612 * Creates a duplicate specimen which derives from the same derivation event
1613 * as the facade specimen and adds collection data to it (all data available in
1614 * DerivedUnitBase and Specimen. Data from SpecimenOrObservationBase and above
1615 * are not yet shared at the moment.
1616 * @param collection
1617 * @param catalogNumber
1618 * @param accessionNumber
1619 * @param collectorsNumber
1620 * @param storedUnder
1621 * @param preservation
1622 * @return
1623 */
1624 public Specimen addDuplicate(Collection collection, String catalogNumber, String accessionNumber,
1625 String collectorsNumber, TaxonNameBase storedUnder, PreservationMethod preservation){
1626 Specimen duplicate = Specimen.NewInstance();
1627 duplicate.setDerivedFrom(getDerivationEvent(true));
1628 duplicate.setCollection(collection);
1629 duplicate.setCatalogNumber(catalogNumber);
1630 duplicate.setAccessionNumber(accessionNumber);
1631 duplicate.setCollectorsNumber(collectorsNumber);
1632 duplicate.setStoredUnder(storedUnder);
1633 duplicate.setPreservation(preservation);
1634 return duplicate;
1635 }
1636
1637 public void addDuplicate(DerivedUnitBase duplicateSpecimen){
1638 //TODO check derivedUnitType
1639 getDerivationEvent(true).addDerivative(duplicateSpecimen);
1640 }
1641
1642 @Transient
1643 public Set<Specimen> getDuplicates(){
1644 Set<Specimen> result = new HashSet<Specimen>();
1645 if (hasDerivationEvent()){
1646 for (DerivedUnitBase derivedUnit: getDerivationEvent(true).getDerivatives()){
1647 if (derivedUnit.isInstanceOf(Specimen.class) && ! derivedUnit.equals(this.derivedUnit)){
1648 result.add(CdmBase.deproxy(derivedUnit, Specimen.class));
1649 }
1650 }
1651 }
1652 return result;
1653 }
1654 public void removeDuplicate(Specimen duplicateSpecimen){
1655 if (hasDerivationEvent()){
1656 getDerivationEvent(true).removeDerivative(duplicateSpecimen);
1657 }
1658 }
1659
1660
1661
1662
1663 }