Project

General

Profile

Download (56.8 KB) Statistics
| Branch: | Tag: | Revision:
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
	@Transient
767
	public NamedArea getCountry(){
768
		return  (hasGatheringEvent() ? getGatheringEvent(true).getCountry() : null);
769
	}
770
	
771
	public void setCountry(NamedArea country){
772
		getGatheringEvent(true).setCountry(country);
773
	}
774
	
775
	
776
	//Collecting area
777
	public void addCollectingArea(NamedArea area) {
778
		getGatheringEvent(true).addCollectingArea(area);
779
	}
780
	public void addCollectingAreas(java.util.Collection<NamedArea> areas) {
781
		for (NamedArea area : areas){
782
			getGatheringEvent(true).addCollectingArea(area);
783
		}
784
	}
785
	@Transient
786
	public Set<NamedArea> getCollectingAreas() {
787
		return  (hasGatheringEvent() ? getGatheringEvent(true).getCollectingAreas() : null);
788
	}
789
	public void removeCollectingArea(NamedArea area) {
790
		if (hasGatheringEvent()){
791
			getGatheringEvent(true).removeCollectingArea(area);
792
		}
793
	}
794

    
795
	//absolute elevation  
796
	/** meter above/below sea level of the surface 
797
	 * @see #getAbsoluteElevationError()
798
	 * @see #getAbsoluteElevationRange()
799
	 **/
800
	@Transient
801
	public Integer getAbsoluteElevation() {
802
		return (hasGatheringEvent() ? getGatheringEvent(true).getAbsoluteElevation() : null);
803
	}
804
	public void setAbsoluteElevation(Integer absoluteElevation) {
805
		getGatheringEvent(true).setAbsoluteElevation(absoluteElevation);
806
	}
807

    
808
	//absolute elevation error
809
	@Transient
810
	public Integer getAbsoluteElevationError() {
811
		return (hasGatheringEvent() ? getGatheringEvent(true).getAbsoluteElevationError() : null);
812
	}
813
	public void setAbsoluteElevationError(Integer absoluteElevationError) {
814
		getGatheringEvent(true).setAbsoluteElevationError(absoluteElevationError);
815
	}
816
	
817
	/**
818
	 * @see #getAbsoluteElevation()
819
	 * @see #getAbsoluteElevationError()
820
	 * @see #setAbsoluteElevationRange(Integer, Integer)
821
	 * @see #getAbsoluteElevationMaximum()
822
	 */
823
	@Transient
824
	public Integer getAbsoluteElevationMinimum(){
825
		if ( ! hasGatheringEvent() ){
826
			return null;
827
		}
828
		Integer minimum = getGatheringEvent(true).getAbsoluteElevation();
829
		if (getGatheringEvent(true).getAbsoluteElevationError() != null){
830
			minimum = minimum -  getGatheringEvent(true).getAbsoluteElevationError();
831
		}
832
		return minimum;
833
	}
834
	/**
835
	 * @see #getAbsoluteElevation()
836
	 * @see #getAbsoluteElevationError()
837
	 * @see #setAbsoluteElevationRange(Integer, Integer)
838
	 * @see #getAbsoluteElevationMinimum()
839
	 */
840
	@Transient
841
	public Integer getAbsoluteElevationMaximum(){
842
		if ( ! hasGatheringEvent() ){
843
			return null;
844
		}
845
		Integer maximum = getGatheringEvent(true).getAbsoluteElevation();
846
		if (getGatheringEvent(true).getAbsoluteElevationError() != null){
847
			maximum = maximum +  getGatheringEvent(true).getAbsoluteElevationError();
848
		}
849
		return maximum;
850
	}
851

    
852
	
853
	/**
854
	 * This method replaces absoluteElevation and absoulteElevationError by
855
	 * internally translating minimum and maximum values into
856
	 * average and error values. As all these values are integer based
857
	 * it is necessary that the distance is between minimum and maximum is <b>even</b>,
858
	 * otherwise we will get a rounding error resulting in a maximum that is increased
859
	 * by 1.
860
	 * @see #setAbsoluteElevation(Integer)
861
	 * @see #setAbsoluteElevationError(Integer)
862
	 * @param minimumElevation minimum of the range
863
	 * @param maximumElevation maximum of the range
864
	 */
865
	public void setAbsoluteElevationRange(Integer minimumElevation, Integer maximumElevation){
866
		if (minimumElevation == null || maximumElevation == null){
867
			Integer elevation = minimumElevation;
868
			Integer error = 0;
869
			if (minimumElevation == null){
870
				elevation = maximumElevation;
871
				if (elevation == null){
872
					error = null;
873
				}
874
			}
875
			getGatheringEvent(true).setAbsoluteElevation(elevation);
876
			getGatheringEvent(true).setAbsoluteElevationError(error);
877
		}else{
878
			if (! isEvenDistance(minimumElevation, maximumElevation) ){
879
				throw new IllegalArgumentException("Distance between minimum and maximum elevation must be even but was " + Math.abs(minimumElevation - maximumElevation));
880
			}
881
			Integer absoluteElevationError = Math.abs(maximumElevation - minimumElevation);
882
			absoluteElevationError = absoluteElevationError / 2;
883
			Integer absoluteElevation = minimumElevation + absoluteElevationError;
884
			getGatheringEvent(true).setAbsoluteElevation(absoluteElevation);
885
			getGatheringEvent(true).setAbsoluteElevationError(absoluteElevationError);
886
		}
887
	}
888

    
889
	/**
890
	 * @param minimumElevation
891
	 * @param maximumElevation
892
	 * @return
893
	 */
894
	private boolean isEvenDistance(Integer minimumElevation, Integer maximumElevation) {
895
		Integer diff = ( maximumElevation - minimumElevation);
896
		Integer testDiff = (diff /2) *2 ;
897
		return (testDiff == diff);
898
	}
899

    
900
	//collector
901
	@Transient
902
	public AgentBase getCollector() {
903
		return  (hasGatheringEvent() ? getGatheringEvent(true).getCollector() : null);
904
	}
905
	public void setCollector(AgentBase collector){
906
		getGatheringEvent(true).setCollector(collector);
907
	}
908

    
909
	//collecting method
910
	@Transient
911
	public String getCollectingMethod() {
912
		return  (hasGatheringEvent() ? getGatheringEvent(true).getCollectingMethod() : null);
913
	}
914
	public void setCollectingMethod(String collectingMethod) {
915
		getGatheringEvent(true).setCollectingMethod(collectingMethod);
916
	}
917

    
918
	//distance to ground
919
	@Transient
920
	public Integer getDistanceToGround() {
921
		return  (hasGatheringEvent() ? getGatheringEvent(true).getDistanceToGround() : null);
922
	}
923
	public void setDistanceToGround(Integer distanceToGround) {
924
		getGatheringEvent(true).setDistanceToGround(distanceToGround);
925
	}
926

    
927
	//distance to water surface
928
	@Transient
929
	public Integer getDistanceToWaterSurface() {
930
		return  (hasGatheringEvent() ? getGatheringEvent(true).getDistanceToWaterSurface() : null);
931
	}
932
	public void setDistanceToWaterSurface(Integer distanceToWaterSurface) {
933
		getGatheringEvent(true).setDistanceToWaterSurface(distanceToWaterSurface);
934
	}
935

    
936
	//exact location
937
	@Transient
938
	public Point getExactLocation() {
939
		return  (hasGatheringEvent() ? getGatheringEvent(true).getExactLocation() : null );
940
	}
941
	
942
	/**
943
	 * Returns a sexagesimal representation of the exact location (e.g. 12?59'N, 35?23E).
944
	 * If the exact location is <code>null</code> the empty string is returned.
945
	 * @param includeEmptySeconds
946
	 * @param includeReferenceSystem
947
	 * @return
948
	 */
949
	public String getExactLocationText(boolean includeEmptySeconds, boolean includeReferenceSystem){
950
		return (this.getExactLocation() == null ? "" : this.getExactLocation().toSexagesimalString(includeEmptySeconds, includeReferenceSystem));
951
	}
952
	public void setExactLocation(Point exactLocation) {
953
		getGatheringEvent(true).setExactLocation(exactLocation);
954
	}
955
	public void setExactLocationByParsing(String longitudeToParse, String latitudeToParse, ReferenceSystem referenceSystem, Integer errorRadius) throws ParseException{
956
		Point point = Point.NewInstance(null, null, referenceSystem, errorRadius);
957
		point.setLongitudeByParsing(longitudeToParse);
958
		point.setLatitudeByParsing(latitudeToParse);
959
		setExactLocation(point);
960
	}
961
	
962
	//gathering event description
963
	@Transient
964
	public String getGatheringEventDescription() {
965
		return  (hasGatheringEvent() ? getGatheringEvent(true).getDescription() : null);
966
	}
967
	public void setGatheringEventDescription(String description) {
968
		getGatheringEvent(true).setDescription(description);
969
	}
970

    
971
	//gatering period
972
	@Transient
973
	public TimePeriod getGatheringPeriod() {
974
		return (hasGatheringEvent() ? getGatheringEvent(true).getTimeperiod() : null);
975
	}
976
	public void setGatheringPeriod(TimePeriod timeperiod) {
977
		getGatheringEvent(true).setTimeperiod(timeperiod);
978
	}
979

    
980
	//locality
981
	@Transient
982
	public LanguageString getLocality(){
983
		return (hasGatheringEvent() ? getGatheringEvent(true).getLocality() : null);
984
	}
985
	@Transient
986
	public String getLocalityText(){
987
		LanguageString locality = getLocality();
988
		if(locality != null){
989
			return locality.getText();
990
		}
991
		return null;
992
	}
993
	@Transient
994
	public Language getLocalityLanguage(){
995
		LanguageString locality = getLocality();
996
		if(locality != null){
997
			return locality.getLanguage();
998
		}
999
		return null;
1000
	}
1001
	
1002
	/**
1003
	 * Sets the locality string in the default language
1004
	 * @param locality
1005
	 */
1006
	public void setLocality(String locality){
1007
		Language language = Language.DEFAULT();
1008
		setLocality(locality, language);
1009
	}
1010
	public void setLocality(String locality, Language language){
1011
		LanguageString langString = LanguageString.NewInstance(locality, language);
1012
		setLocality(langString);
1013
	}
1014
	public void setLocality(LanguageString locality){
1015
		getGatheringEvent(true).setLocality(locality);
1016
	}
1017
	
1018
	/**
1019
	 * The gathering event will be used for the field object instead of the old gathering event.<BR>
1020
	 * <B>This method will override all gathering values (see below).</B>
1021
	 * @see #getAbsoluteElevation()
1022
	 * @see #getAbsoluteElevationError()
1023
	 * @see #getDistanceToGround()
1024
	 * @see #getDistanceToWaterSurface()
1025
	 * @see #getExactLocation()
1026
	 * @see #getGatheringEventDescription()
1027
	 * @see #getGatheringPeriod()
1028
	 * @see #getCollectingAreas()
1029
	 * @see #getCollectingMethod()
1030
	 * @see #getLocality()
1031
	 * @see #getCollector()	
1032
	 * @param gatheringEvent
1033
	 */
1034
	public void setGatheringEvent(GatheringEvent gatheringEvent) {
1035
		getFieldObservation(true).setGatheringEvent(gatheringEvent);
1036
	}
1037
	public boolean hasGatheringEvent(){
1038
		return (getGatheringEvent(false) != null);
1039
	}
1040
	@Transient
1041
	public GatheringEvent getGatheringEvent() {
1042
		return getGatheringEvent(false);
1043
	}
1044
	
1045
	public GatheringEvent getGatheringEvent(boolean createIfNotExists) {
1046
		if (! hasFieldObservation() && ! createIfNotExists){
1047
			return null;
1048
		}
1049
		if (createIfNotExists && getFieldObservation(true).getGatheringEvent() == null ){
1050
			GatheringEvent gatheringEvent = GatheringEvent.NewInstance();
1051
			getFieldObservation(true).setGatheringEvent(gatheringEvent);
1052
		}
1053
		return getFieldObservation(true).getGatheringEvent();
1054
	}
1055
	
1056
// ****************** Field Object ************************************/
1057
	
1058
	/**
1059
	 * Returns true if a field observation exists (even if all attributes are empty or <code>null<code>.
1060
	 * @return
1061
	 */
1062
	public boolean hasFieldObject(){
1063
		return this.fieldObservation != null;
1064
	}
1065
	
1066
	//ecology
1067
	@Transient
1068
	public String getEcology(){
1069
		return getEcology(Language.DEFAULT());
1070
	}
1071
	public String getEcology(Language language){
1072
		LanguageString languageString = getEcologyAll().get(language);
1073
		return (languageString == null ? null : languageString.getText());
1074
	}
1075
//	public String getEcologyPreferred(List<Language> languages){
1076
//		LanguageString languageString = getEcologyAll().getPreferredLanguageString(languages);
1077
//		return languageString.getText();
1078
//	}
1079
	@Transient
1080
	public Map<Language, LanguageString> getEcologyAll(){
1081
		if (ecology == null){
1082
			try {
1083
				ecology = initializeFieldObjectTextDataWithSupportTest(Feature.ECOLOGY(), false, false);
1084
			} catch (DerivedUnitFacadeNotSupportedException e) {
1085
				throw new IllegalStateException(notSupportMessage, e);
1086
			}
1087
			if (ecology == null){
1088
				return new HashMap<Language, LanguageString>();
1089
			}
1090
		}
1091
		return ecology.getMultilanguageText();
1092
	}
1093
	
1094
	public void setEcology(String ecology){
1095
		setEcology(ecology, null);
1096
	}
1097
	public void setEcology(String ecologyText, Language language){
1098
		if (language == null){
1099
			language = Language.DEFAULT();
1100
		}
1101
		if (ecology == null){
1102
			try {
1103
				ecology = initializeFieldObjectTextDataWithSupportTest(Feature.ECOLOGY(), true, false);
1104
			} catch (DerivedUnitFacadeNotSupportedException e) {
1105
				throw new IllegalStateException(notSupportMessage, e);
1106
			}
1107
		}
1108
		if (ecologyText == null){
1109
			ecology.removeText(language);
1110
		}else{
1111
			ecology.putText(ecologyText, language);
1112
		}
1113
	}
1114
	public void removeEcology(Language language){
1115
		setEcology(null, language);
1116
	}
1117
	/**
1118
	 * Removes ecology for the default language
1119
	 */
1120
	public void removeEcology(){
1121
		setEcology(null, null);
1122
	}
1123
	public void removeEcologyAll(){
1124
		
1125
	}
1126

    
1127
	
1128
	//plant description
1129
	@Transient
1130
	public String getPlantDescription(){
1131
		return getPlantDescription(null);
1132
	}
1133
	public String getPlantDescription(Language language){
1134
		if (language == null){
1135
			language = Language.DEFAULT();
1136
		}
1137
		LanguageString languageString = getPlantDescriptionAll().get(language);
1138
		return (languageString == null ? null : languageString.getText());
1139
	}
1140
//	public String getPlantDescriptionPreferred(List<Language> languages){
1141
//		LanguageString languageString = getPlantDescriptionAll().getPreferredLanguageString(languages);
1142
//		return languageString.getText();
1143
//	}
1144
	@Transient
1145
	public Map<Language, LanguageString> getPlantDescriptionAll(){
1146
		if (plantDescription == null){
1147
			try {
1148
				plantDescription = initializeFieldObjectTextDataWithSupportTest(Feature.DESCRIPTION(), false, false);
1149
			} catch (DerivedUnitFacadeNotSupportedException e) {
1150
				throw new IllegalStateException(notSupportMessage, e);
1151
			}
1152
			if (plantDescription == null){
1153
				return new HashMap<Language, LanguageString>();
1154
			}
1155
		}
1156
		return plantDescription.getMultilanguageText();
1157
	}
1158
	public void setPlantDescription(String plantDescription){
1159
		setPlantDescription(plantDescription, null);
1160
	}
1161
	public void setPlantDescription(String plantDescriptionText, Language language){
1162
		if (language == null){
1163
			language = Language.DEFAULT();
1164
		}
1165
		if (plantDescription == null){
1166
			try {
1167
				plantDescription = initializeFieldObjectTextDataWithSupportTest(Feature.DESCRIPTION(), true, false);
1168
			} catch (DerivedUnitFacadeNotSupportedException e) {
1169
				throw new IllegalStateException(notSupportMessage, e);
1170
			}
1171
		}
1172
		if (plantDescriptionText == null){
1173
			plantDescription.removeText(language);
1174
		}else{
1175
			plantDescription.putText(plantDescriptionText, language);
1176
		}
1177
	}
1178
	public void removePlantDescription(Language language){
1179
		setPlantDescription(null, language);
1180
	}
1181
	
1182
	//field object definition
1183
	public void addFieldObjectDefinition(String text, Language language) {
1184
		getFieldObservation(true).addDefinition(text, language);
1185
	}
1186
	@Transient
1187
	public Map<Language, LanguageString> getFieldObjectDefinition() {
1188
		if (! hasFieldObservation()){
1189
			return new HashMap<Language, LanguageString>();
1190
		}else{
1191
			return getFieldObservation(true).getDefinition();
1192
		}
1193
	}
1194
	public String getFieldObjectDefinition(Language language) {
1195
		Map<Language, LanguageString> map = getFieldObjectDefinition();
1196
		LanguageString languageString = (map == null? null : map.get(language));
1197
		if (languageString != null){
1198
			return languageString.getText();
1199
		}else {
1200
			return null;
1201
		}
1202
	}
1203
	public void removeFieldObjectDefinition(Language lang) {
1204
		if (hasFieldObservation()){
1205
			getFieldObservation(true).removeDefinition(lang);
1206
		}
1207
	}
1208
	
1209

    
1210
	//media
1211
	public boolean addFieldObjectMedia(Media media)  {
1212
		try {
1213
			return addMedia(media, getFieldObservation(true));
1214
		} catch (DerivedUnitFacadeNotSupportedException e) {
1215
			throw new IllegalStateException(notSupportMessage, e);
1216
		}
1217
	}
1218
	/**
1219
	 * Returns true, if an image gallery for the field object exists.<BR>
1220
	 * Returns also <code>true</code> if the image gallery is empty. 
1221
	 * @return
1222
	 */
1223
	public boolean hasFieldObjectImageGallery(){
1224
		if (! hasFieldObject()){
1225
			return false;
1226
		}else{
1227
			return (getImageGallery(fieldObservation, false) != null);
1228
		}
1229
	}
1230
	
1231
	/**
1232
	 * @param createIfNotExists
1233
	 * @return
1234
	 */
1235
	public SpecimenDescription getFieldObjectImageGallery(boolean createIfNotExists){
1236
		TextData textData;
1237
		try {
1238
			textData = initializeFieldObjectTextDataWithSupportTest(Feature.IMAGE(), createIfNotExists, true);
1239
		} catch (DerivedUnitFacadeNotSupportedException e) {
1240
			throw new IllegalStateException(notSupportMessage, e);
1241
		}
1242
		if (textData != null){
1243
			return CdmBase.deproxy(textData.getInDescription(), SpecimenDescription.class);
1244
		}else{
1245
			return null;
1246
		}
1247
	}
1248
	/**
1249
	 * Returns the media for the field object.<BR>
1250
	 * @return
1251
	 */
1252
	@Transient
1253
	public List<Media> getFieldObjectMedia() {
1254
		try {
1255
			List<Media> result = getMedia(getFieldObservation(false), false);
1256
			return result == null ? new ArrayList<Media>() : result;
1257
		} catch (DerivedUnitFacadeNotSupportedException e) {
1258
			throw new IllegalStateException(notSupportMessage, e);
1259
		}
1260
	}
1261
	public boolean removeFieldObjectMedia(Media media) {
1262
		try {
1263
			return removeMedia(media, getFieldObservation(false));
1264
		} catch (DerivedUnitFacadeNotSupportedException e) {
1265
			throw new IllegalStateException(notSupportMessage, e);
1266
		}
1267
	}
1268

    
1269
	//field number
1270
	@Transient
1271
	public String getFieldNumber() {
1272
		if (! hasFieldObservation()){
1273
			return null;
1274
		}else{
1275
			return getFieldObservation(true).getFieldNumber();
1276
		}
1277
	}
1278
	public void setFieldNumber(String fieldNumber) {
1279
		getFieldObservation(true).setFieldNumber(fieldNumber);
1280
	}
1281

    
1282
	
1283
	//field notes
1284
	@Transient
1285
	public String getFieldNotes() {
1286
		if (! hasFieldObservation()){
1287
			return null;
1288
		}else{
1289
			return getFieldObservation(true).getFieldNotes();
1290
		}
1291
	}
1292
	public void setFieldNotes(String fieldNotes) {
1293
		getFieldObservation(true).setFieldNotes(fieldNotes);
1294
	}
1295

    
1296

    
1297
	//individual counts
1298
	@Transient
1299
	public Integer getIndividualCount() {
1300
		return (hasFieldObservation()? getFieldObservation(true).getIndividualCount() : null );
1301
	}
1302
	public void setIndividualCount(Integer individualCount) {
1303
		getFieldObservation(true).setIndividualCount(individualCount);
1304
	}
1305

    
1306
	//life stage
1307
	@Transient
1308
	public Stage getLifeStage() {
1309
		return (hasFieldObservation()? getFieldObservation(true).getLifeStage() : null );
1310
	}
1311
	public void setLifeStage(Stage lifeStage) {
1312
		getFieldObservation(true).setLifeStage(lifeStage);
1313
	}
1314

    
1315
	//sex
1316
	@Transient
1317
	public Sex getSex() {
1318
		return (hasFieldObservation()? getFieldObservation(true).getSex() : null );
1319
	}
1320
	public void setSex(Sex sex) {
1321
		getFieldObservation(true).setSex(sex);
1322
	}
1323
	
1324
	
1325
	//field observation
1326
	public boolean hasFieldObservation(){
1327
		return (getFieldObservation(false) != null);
1328
	}
1329
	
1330
	/**
1331
	 * Returns the field observation as an object.
1332
	 * @return
1333
	 */
1334
	@Transient
1335
	public FieldObservation getFieldObservation(){
1336
		return getFieldObservation(false);
1337
	}
1338

    
1339
	/**
1340
	 * Returns the field observation as an object.
1341
	 * @return
1342
	 */
1343
	public FieldObservation getFieldObservation(boolean createIfNotExists){
1344
		if (fieldObservation == null && createIfNotExists){
1345
			fieldObservation = FieldObservation.NewInstance();
1346
			fieldObservation.addPropertyChangeListener(getNewEventPropagationListener());
1347
			DerivationEvent derivationEvent = getDerivationEvent(true);
1348
			derivationEvent.addOriginal(fieldObservation);
1349
		}
1350
		return this.fieldObservation;
1351
	}
1352

    
1353
	
1354

    
1355

    
1356
	
1357
//****************** Specimen **************************************************	
1358
	
1359
	//Definition
1360
	public void addDerivedUnitDefinition(String text, Language language) {
1361
		derivedUnit.addDefinition(text, language);
1362
	}
1363
	@Transient
1364
	public Map<Language, LanguageString> getDerivedUnitDefinitions(){
1365
		return this.derivedUnit.getDefinition();
1366
	}
1367
	public String getDerivedUnitDefinition(Language language) {
1368
		Map<Language,LanguageString> languageMap = derivedUnit.getDefinition();
1369
		LanguageString languageString = languageMap.get(language);
1370
		if (languageString != null){
1371
			return languageString.getText();
1372
		}else {
1373
			return null;
1374
		}
1375
	}
1376
	public void removeDerivedUnitDefinition(Language lang) {
1377
		derivedUnit.removeDefinition(lang);
1378
	}
1379

    
1380
	//Determination
1381
	public void addDetermination(DeterminationEvent determination) {
1382
		derivedUnit.addDetermination(determination);
1383
	}
1384
	@Transient
1385
	public Set<DeterminationEvent> getDeterminations() {
1386
		return derivedUnit.getDeterminations();
1387
	}
1388
	public void removeDetermination(DeterminationEvent determination) {
1389
		derivedUnit.removeDetermination(determination);
1390
	}
1391
	
1392
	//Media
1393
	public boolean addDerivedUnitMedia(Media media)  {
1394
		try {
1395
			return addMedia(media, derivedUnit);
1396
		} catch (DerivedUnitFacadeNotSupportedException e) {
1397
			throw new IllegalStateException(notSupportMessage, e);
1398
		}
1399
	}
1400
	/**
1401
	 * Returns true, if an image gallery exists for the specimen.<BR>
1402
	 * Returns also <code>true</code> if the image gallery is empty. 
1403
	 */
1404
	public boolean hasDerivedUnitImageGallery(){
1405
		return (getImageGallery(derivedUnit, false) != null);
1406
	}
1407
	
1408
	public SpecimenDescription getDerivedUnitImageGallery(boolean createIfNotExists){
1409
		TextData textData;
1410
		try {
1411
			textData = inititialzeTextDataWithSupportTest(Feature.IMAGE(), derivedUnit, createIfNotExists, true);
1412
		} catch (DerivedUnitFacadeNotSupportedException e) {
1413
			throw new IllegalStateException(notSupportMessage, e);
1414
		}
1415
		if (textData != null){
1416
			return CdmBase.deproxy(textData.getInDescription(), SpecimenDescription.class);
1417
		}else{
1418
			return null;
1419
		}
1420
	}
1421
	
1422
	/**
1423
	 * Returns the media for the specimen.<BR>
1424
	 * @return
1425
	 */
1426
	@Transient
1427
	public List<Media> getDerivedUnitMedia() {
1428
		try {
1429
			List<Media> result = getMedia(derivedUnit, false);
1430
			return result == null ? new ArrayList<Media>() : result;
1431
		} catch (DerivedUnitFacadeNotSupportedException e) {
1432
			throw new IllegalStateException(notSupportMessage, e);
1433
		}
1434
	}
1435
	public boolean removeDerivedUnitMedia(Media media) {
1436
		try {
1437
			return removeMedia(media, derivedUnit);
1438
		} catch (DerivedUnitFacadeNotSupportedException e) {
1439
			throw new IllegalStateException(notSupportMessage, e);
1440
		}
1441
	}
1442

    
1443
	
1444
	//Accession Number
1445
	@Transient
1446
	public String getAccessionNumber() {
1447
		return derivedUnit.getAccessionNumber();
1448
	}
1449
	public void setAccessionNumber(String accessionNumber) {
1450
		derivedUnit.setAccessionNumber(accessionNumber);
1451
	}
1452

    
1453
	@Transient
1454
	public String getCatalogNumber() {
1455
		return derivedUnit.getCatalogNumber();
1456
	}
1457
	public void setCatalogNumber(String catalogNumber) {
1458
		derivedUnit.setCatalogNumber(catalogNumber);
1459
	}
1460

    
1461
	@Transient
1462
	public String getBarcode() {
1463
		return derivedUnit.getBarcode();
1464
	}
1465
	public void setBarcode(String barcode) {
1466
		derivedUnit.setCatalogNumber(barcode);
1467
	}
1468

    
1469
	
1470
	//Preservation Method
1471
	
1472
	/**
1473
	 * Only supported by specimen and fossils
1474
	 * @see #DerivedUnitType
1475
	 * @return
1476
	 */
1477
	@Transient
1478
	public PreservationMethod getPreservationMethod() throws MethodNotSupportedByDerivedUnitTypeException {
1479
		if (derivedUnit.isInstanceOf(Specimen.class)){
1480
			return CdmBase.deproxy(derivedUnit, Specimen.class).getPreservation();
1481
		}else{
1482
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()){
1483
				throw new MethodNotSupportedByDerivedUnitTypeException("A preservation method is only available in derived units of type 'Specimen' or 'Fossil'");
1484
			}else{
1485
				return null;
1486
			}
1487
		}
1488
	}
1489
	/**
1490
	 * Only supported by specimen and fossils
1491
	 * @see #DerivedUnitType
1492
	 * @return
1493
	 */
1494
	public void setPreservationMethod(PreservationMethod preservation)throws MethodNotSupportedByDerivedUnitTypeException  {
1495
		if (derivedUnit.isInstanceOf(Specimen.class)){
1496
			CdmBase.deproxy(derivedUnit, Specimen.class).setPreservation(preservation);
1497
		}else{
1498
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()){
1499
				throw new MethodNotSupportedByDerivedUnitTypeException("A preservation method is only available in derived units of type 'Specimen' or 'Fossil'");
1500
			}else{
1501
				return;
1502
			}
1503
		}
1504
	}
1505

    
1506
	//Stored under name
1507
	@Transient
1508
	public TaxonNameBase getStoredUnder() {
1509
		return derivedUnit.getStoredUnder();
1510
	}
1511
	public void setStoredUnder(TaxonNameBase storedUnder) {
1512
		derivedUnit.setStoredUnder(storedUnder);
1513
	}
1514

    
1515
	//colletors number
1516
	@Transient
1517
	public String getCollectorsNumber() {
1518
		return derivedUnit.getCollectorsNumber();
1519
	}
1520
	public void setCollectorsNumber(String collectorsNumber) {
1521
		this.derivedUnit.setCollectorsNumber(collectorsNumber);
1522
	}
1523

    
1524
	//title cache
1525
	public String getTitleCache() {
1526
		if (! derivedUnit.isProtectedTitleCache()){
1527
			//always compute title cache anew as long as there are no property change listeners on 
1528
			//field observation, gathering event etc 
1529
			derivedUnit.setTitleCache(null, false);
1530
		}
1531
		return this.derivedUnit.getTitleCache();
1532
	}
1533
	public void setTitleCache(String titleCache, boolean isProtected) {
1534
		this.derivedUnit.setTitleCache(titleCache, isProtected);
1535
	}
1536

    
1537

    
1538
	/**
1539
	 * Returns the derived unit itself.
1540
	 * @return the derived unit
1541
	 */
1542
	@Transient
1543
	public DerivedUnitBase getDerivedUnit() {
1544
		return this.derivedUnit;
1545
	}
1546
	
1547
	private boolean hasDerivationEvent(){
1548
		return getDerivationEvent() == null ? false : true;
1549
	}
1550
	private DerivationEvent getDerivationEvent(){
1551
		return getDerivationEvent(false);
1552
	}
1553
	private DerivationEvent getDerivationEvent(boolean createIfNotExists){
1554
		DerivationEvent result = derivedUnit.getDerivedFrom();
1555
		if (result == null){
1556
			result = DerivationEvent.NewInstance();
1557
			derivedUnit.setDerivedFrom(result);
1558
		}
1559
		return result;
1560
	}
1561
	@Transient
1562
	public String getExsiccatum() {
1563
		logger.warn("Exsiccatum method not yet supported. Needs model change");
1564
		return null;
1565
	}
1566
	
1567
	public String setExsiccatum() throws MethodNotSupportedException{
1568
		throw new MethodNotSupportedException("Exsiccatum method not yet supported. Needs model change");
1569
	}
1570
	
1571
	
1572
	// **** sources **/
1573
	public void addSource(IdentifiableSource source){
1574
		this.derivedUnit.addSource(source);
1575
	}
1576
	/**
1577
	 * Creates an orignal source, adds it to the specimen and returns it.
1578
	 * @param reference
1579
	 * @param microReference
1580
	 * @param originalNameString
1581
	 * @return
1582
	 */
1583
	public IdentifiableSource addSource(ReferenceBase reference, String microReference, String originalNameString){
1584
		IdentifiableSource source = IdentifiableSource.NewInstance(reference, microReference);
1585
		source.setOriginalNameString(originalNameString);
1586
		derivedUnit.addSource(source);
1587
		return source;
1588
	}
1589

    
1590
	@Transient
1591
	public Set<IdentifiableSource> getSources(){
1592
		return derivedUnit.getSources();
1593
	}
1594

    
1595
	public void removeSource(IdentifiableSource source){
1596
		this.derivedUnit.removeSource(source);
1597
	}
1598
	
1599
	
1600
	/**
1601
	 * @return the collection
1602
	 */
1603
	@Transient
1604
	public Collection getCollection() {
1605
		return derivedUnit.getCollection();
1606
	}
1607

    
1608

    
1609
	/**
1610
	 * @param collection the collection to set
1611
	 */
1612
	public void setCollection(Collection collection) {
1613
		derivedUnit.setCollection(collection);
1614
	}
1615
	
1616
	//annotation	
1617
	public void addAnnotation(Annotation annotation){
1618
		this.derivedUnit.addAnnotation(annotation);
1619
	}
1620

    
1621
	@Transient
1622
	public void getAnnotations(){
1623
		this.derivedUnit.getAnnotations();
1624
	}
1625
	
1626
	public void removeAnnotation(Annotation annotation){
1627
		this.derivedUnit.removeAnnotation(annotation);
1628
	}
1629
	
1630
	
1631
// ******************************* Events *********************************************
1632
	
1633
	/**
1634
	 * @return
1635
	 */
1636
	private PropertyChangeListener getNewEventPropagationListener() {
1637
		PropertyChangeListener listener = new PropertyChangeListener(){
1638
			@Override
1639
			public void propertyChange(PropertyChangeEvent event) {
1640
				derivedUnit.firePropertyChange(event);
1641
			}
1642
			
1643
		};
1644
		return listener;
1645
	}
1646

    
1647
		
1648
	
1649

    
1650
//**************** Other Collections ***************************************************	
1651
	
1652
	/**
1653
	 * Creates a duplicate specimen which derives from the same derivation event
1654
	 * as the facade specimen and adds collection data to it (all data available in
1655
	 * DerivedUnitBase and Specimen. Data from SpecimenOrObservationBase and above
1656
	 * are not yet shared at the moment. 
1657
	 * @param collection
1658
	 * @param catalogNumber
1659
	 * @param accessionNumber
1660
	 * @param collectorsNumber
1661
	 * @param storedUnder
1662
	 * @param preservation
1663
	 * @return
1664
	 */
1665
	public Specimen addDuplicate(Collection collection, String catalogNumber, String accessionNumber, 
1666
				String collectorsNumber, TaxonNameBase storedUnder, PreservationMethod preservation){
1667
		Specimen duplicate = Specimen.NewInstance();
1668
		duplicate.setDerivedFrom(getDerivationEvent(true));
1669
		duplicate.setCollection(collection);
1670
		duplicate.setCatalogNumber(catalogNumber);
1671
		duplicate.setAccessionNumber(accessionNumber);
1672
		duplicate.setCollectorsNumber(collectorsNumber);
1673
		duplicate.setStoredUnder(storedUnder);
1674
		duplicate.setPreservation(preservation);
1675
		return duplicate;
1676
	}
1677
	
1678
	public void addDuplicate(DerivedUnitBase duplicateSpecimen){
1679
		//TODO check derivedUnitType
1680
		getDerivationEvent(true).addDerivative(duplicateSpecimen);  
1681
	}
1682
	
1683
	@Transient
1684
	public Set<Specimen> getDuplicates(){
1685
		Set<Specimen> result = new HashSet<Specimen>();
1686
		if (hasDerivationEvent()){
1687
			for (DerivedUnitBase derivedUnit: getDerivationEvent(true).getDerivatives()){
1688
				if (derivedUnit.isInstanceOf(Specimen.class) && ! derivedUnit.equals(this.derivedUnit)){
1689
					result.add(CdmBase.deproxy(derivedUnit, Specimen.class));
1690
				}
1691
			}
1692
		}
1693
		return result;
1694
	}
1695
	public void removeDuplicate(Specimen duplicateSpecimen){
1696
		if (hasDerivationEvent()){
1697
			getDerivationEvent(true).removeDerivative(duplicateSpecimen);
1698
		}
1699
	}
1700
	
1701
	
1702
	
1703
	
1704
}
(1-1/5)