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