Project

General

Profile

Download (62.2 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.text.ParseException;
15
import java.util.ArrayList;
16
import java.util.HashMap;
17
import java.util.HashSet;
18
import java.util.List;
19
import java.util.Map;
20
import java.util.Set;
21

    
22
import javax.persistence.Transient;
23

    
24
import org.apache.log4j.Logger;
25

    
26
import eu.etaxonomy.cdm.api.service.IOccurrenceService;
27
import eu.etaxonomy.cdm.model.agent.AgentBase;
28
import eu.etaxonomy.cdm.model.agent.Person;
29
import eu.etaxonomy.cdm.model.common.Annotation;
30
import eu.etaxonomy.cdm.model.common.CdmBase;
31
import eu.etaxonomy.cdm.model.common.IdentifiableSource;
32
import eu.etaxonomy.cdm.model.common.Language;
33
import eu.etaxonomy.cdm.model.common.LanguageString;
34
import eu.etaxonomy.cdm.model.common.TimePeriod;
35
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
36
import eu.etaxonomy.cdm.model.description.Feature;
37
import eu.etaxonomy.cdm.model.description.Sex;
38
import eu.etaxonomy.cdm.model.description.SpecimenDescription;
39
import eu.etaxonomy.cdm.model.description.Stage;
40
import eu.etaxonomy.cdm.model.description.TextData;
41
import eu.etaxonomy.cdm.model.location.NamedArea;
42
import eu.etaxonomy.cdm.model.location.Point;
43
import eu.etaxonomy.cdm.model.location.ReferenceSystem;
44
import eu.etaxonomy.cdm.model.media.Media;
45
import eu.etaxonomy.cdm.model.name.TaxonNameBase;
46
import eu.etaxonomy.cdm.model.occurrence.Collection;
47
import eu.etaxonomy.cdm.model.occurrence.DerivationEvent;
48
import eu.etaxonomy.cdm.model.occurrence.DerivedUnitBase;
49
import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
50
import eu.etaxonomy.cdm.model.occurrence.FieldObservation;
51
import eu.etaxonomy.cdm.model.occurrence.GatheringEvent;
52
import eu.etaxonomy.cdm.model.occurrence.PreservationMethod;
53
import eu.etaxonomy.cdm.model.occurrence.Specimen;
54
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
55
import eu.etaxonomy.cdm.model.reference.Reference;
56

    
57
/**
58
 * This class is a facade to the eu.etaxonomy.cdm.model.occurrence package from
59
 * a specimen based view. It does not support all functionality available in the
60
 * occurrence package.<BR>
61
 * The most significant restriction is that a specimen may derive only from
62
 * one direct derivation event and there must be only one field observation (gathering event)
63
 * it derives from.<BR> 
64
 * 
65
 * @author a.mueller
66
 * @date 14.05.2010
67
 */
68
public class DerivedUnitFacade {
69
	private static final Logger logger = Logger.getLogger(DerivedUnitFacade.class);
70
	
71
	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 ";
72
	
73
	/**
74
	 * Enum that defines the class the "Specimen" belongs to.
75
	 * Some methods of the facade are not available for certain classes
76
	 * and will throw an Exception when invoking them.
77
	 */
78
	public enum DerivedUnitType{
79
		Specimen ("Specimen"),
80
		Observation("Observation"),
81
		LivingBeing("Living Being"),
82
		Fossil("Fossil"),
83
		DerivedUnit("Derived Unit");
84
		
85
		String representation;
86
		private DerivedUnitType(String representation){
87
			this.representation = representation;
88
		}
89
		
90
		/**
91
		 * @return the representation
92
		 */
93
		public String getRepresentation() {
94
			return representation;
95
		}
96
		
97
		private DerivedUnitBase getNewDerivedUnitInstance(){
98
			if (this == DerivedUnitType.Specimen){
99
				return eu.etaxonomy.cdm.model.occurrence.Specimen.NewInstance();
100
			}else if (this == DerivedUnitType.Observation){
101
				return eu.etaxonomy.cdm.model.occurrence.Observation.NewInstance();
102
			}else if (this == DerivedUnitType.LivingBeing){
103
				return eu.etaxonomy.cdm.model.occurrence.LivingBeing.NewInstance();
104
			}else if (this == DerivedUnitType.Fossil){
105
				return eu.etaxonomy.cdm.model.occurrence.Fossil.NewInstance();
106
			}else if (this == DerivedUnitType.DerivedUnit){
107
				return eu.etaxonomy.cdm.model.occurrence.DerivedUnit.NewInstance();
108
			}else{
109
				throw new IllegalStateException("Unknown derived unit type " +  this.getRepresentation());
110
			}
111
		}
112
		
113
	}
114
	
115
	
116
	private DerivedUnitFacadeConfigurator config;
117
	
118
	//private GatheringEvent gatheringEvent;
119
	private DerivedUnitType type;  //needed?
120
	
121
	private FieldObservation fieldObservation;
122
	
123
	private DerivedUnitBase derivedUnit;
124

    
125
	//media - the text data holding the media
126
	private TextData derivedUnitMediaTextData;
127
	private TextData fieldObjectMediaTextData;
128
	
129
	
130
	private TextData ecology;
131
	private TextData plantDescription;
132
	
133
	
134
	/**
135
	 * Creates a derived unit facade for a new derived unit of type <code>type</code>.
136
	 * @param type
137
	 * @return
138
	 */
139
	public static DerivedUnitFacade NewInstance(DerivedUnitType type){
140
		return new DerivedUnitFacade(type);
141
	}
142

    
143
	/**
144
	 * Creates a derived unit facade for a given derived unit using the default configuration.
145
	 * @param derivedUnit
146
	 * @return
147
	 * @throws DerivedUnitFacadeNotSupportedException
148
	 */
149
	public static DerivedUnitFacade NewInstance(DerivedUnitBase derivedUnit) throws DerivedUnitFacadeNotSupportedException{
150
		return new DerivedUnitFacade(derivedUnit, null);
151
	}
152
	
153
	public static DerivedUnitFacade NewInstance(DerivedUnitBase derivedUnit, DerivedUnitFacadeConfigurator config) throws DerivedUnitFacadeNotSupportedException{
154
		return new DerivedUnitFacade(derivedUnit, config);
155
	}
156

    
157

    
158
	
159
// ****************** CONSTRUCTOR ****************************************************
160
	
161
	private DerivedUnitFacade(DerivedUnitType type){
162
		this.config = DerivedUnitFacadeConfigurator.NewInstance();
163
		
164
		//derivedUnit
165
		derivedUnit = type.getNewDerivedUnitInstance();
166
		setCacheStrategy();
167
	}
168
	
169
	private DerivedUnitFacade(DerivedUnitBase derivedUnit, DerivedUnitFacadeConfigurator config) throws DerivedUnitFacadeNotSupportedException{
170
		
171
		if (config == null){
172
			config = DerivedUnitFacadeConfigurator.NewInstance();
173
		}
174
		this.config = config;
175
		
176
		//derived unit
177
		this.derivedUnit = derivedUnit;
178
		setCacheStrategy();
179
		
180
		//derivation event
181
		if (this.derivedUnit.getDerivedFrom() != null){
182
			DerivationEvent derivationEvent = getDerivationEvent(true);
183
			//fieldObservation
184
			Set<FieldObservation> fieldOriginals = getFieldObservationsOriginals(derivationEvent, null);
185
			if (fieldOriginals.size() > 1){
186
				throw new DerivedUnitFacadeNotSupportedException("Specimen must not have more than 1 derivation event");
187
			}else if (fieldOriginals.size() == 0){
188
				//fieldObservation = FieldObservation.NewInstance();
189
			}else if (fieldOriginals.size() == 1){
190
				fieldObservation = fieldOriginals.iterator().next();
191
				//###fieldObservation = getInitializedFieldObservation(fieldObservation);
192
				fieldObservation.addPropertyChangeListener(getNewEventPropagationListener());
193
			}else{
194
				throw new IllegalStateException("Illegal state");
195
			}	
196
		}
197
		// #### derivedUnit = getInitializedDerivedUnit(derivedUnit);
198

    
199
		//test if unsupported
200
		
201
		//media
202
		//specimen
203
//		String objectTypeExceptionText = "Specimen";
204
//		SpecimenDescription imageGallery = getImageGalleryWithSupportTest(derivedUnit, objectTypeExceptionText, false);
205
//		getImageTextDataWithSupportTest(imageGallery, objectTypeExceptionText);
206
		this.derivedUnitMediaTextData = inititializeTextDataWithSupportTest(Feature.IMAGE(), this.derivedUnit, false, true);
207
		
208
		//field observation
209
//		objectTypeExceptionText = "Field observation";
210
//		imageGallery = getImageGalleryWithSupportTest(fieldObservation, objectTypeExceptionText, false);
211
//		getImageTextDataWithSupportTest(imageGallery, objectTypeExceptionText);
212
		fieldObjectMediaTextData = initializeFieldObjectTextDataWithSupportTest(Feature.IMAGE(), false, true);
213
		
214
		//handle derivedUnit.getMedia()
215
		if (derivedUnit.getMedia().size() > 0){
216
			//TODO better changed model here to allow only one place for images
217
			if (this.config.isMoveDerivedUnitMediaToGallery()){
218
				Set<Media> mediaSet = derivedUnit.getMedia();
219
				for (Media media : mediaSet){
220
					this.addDerivedUnitMedia(media);
221
				}
222
				mediaSet.removeAll(getDerivedUnitMedia());
223
			}else{
224
				throw new DerivedUnitFacadeNotSupportedException("Specimen may not have direct media. Only (one) image gallery is allowed");
225
			}
226
		}
227
		
228
		//handle fieldObservation.getMedia()
229
		if (fieldObservation != null && fieldObservation.getMedia() != null && fieldObservation.getMedia().size() > 0){
230
			//TODO better changed model here to allow only one place for images
231
			if (this.config.isMoveFieldObjectMediaToGallery()){
232
				Set<Media> mediaSet = fieldObservation.getMedia();
233
				for (Media media : mediaSet){
234
					this.addFieldObjectMedia(media);
235
				}
236
				mediaSet.removeAll(getFieldObjectMedia());
237
			}else{
238
				throw new DerivedUnitFacadeNotSupportedException("Field object may not have direct media. Only (one) image gallery is allowed");
239
			}
240
		}
241
		
242
		//test if descriptions are supported
243
		ecology = initializeFieldObjectTextDataWithSupportTest(Feature.ECOLOGY(), false, false);
244
		plantDescription = initializeFieldObjectTextDataWithSupportTest(Feature.DESCRIPTION(), false, false);
245
	}
246
	
247

    
248
	private DerivedUnitBase getInitializedDerivedUnit(DerivedUnitBase derivedUnit) {
249
		IOccurrenceService occurrenceService = this.config.getOccurrenceService();
250
		if (occurrenceService == null){
251
			return derivedUnit;
252
		}
253
		List<String> propertyPaths = this.config.getPropertyPaths();
254
		if (propertyPaths == null){
255
			return derivedUnit;
256
		}
257
		propertyPaths = getDerivedUnitPropertyPaths(propertyPaths);
258
		DerivedUnitBase result = (DerivedUnitBase)occurrenceService.load(derivedUnit.getUuid(), propertyPaths);
259
		return result;
260
	}
261

    
262
	/**
263
	 * Initializes the derived unit according to the configuartions property path.
264
	 * If the property path is <code>null</code> or no occurrence service is given the
265
	 * returned object is the same as the input parameter.
266
	 * @param fieldObservation2
267
	 * @return
268
	 */
269
	private FieldObservation getInitializedFieldObservation(FieldObservation fieldObservation) {
270
		IOccurrenceService occurrenceService = this.config.getOccurrenceService();
271
		if (occurrenceService == null){
272
			return fieldObservation;
273
		}
274
		List<String> propertyPaths = this.config.getPropertyPaths();
275
		if (propertyPaths == null){
276
			return fieldObservation;
277
		}
278
		propertyPaths = getFieldObjectPropertyPaths(propertyPaths);
279
		FieldObservation result = (FieldObservation)occurrenceService.load(fieldObservation.getUuid(), propertyPaths);
280
		return result;
281
	}
282

    
283
	/**
284
	 * Transforms the property paths in a way that the facade is handled just like an 
285
	 * ordinary CdmBase object.<BR>
286
	 * E.g. a property path "collectinAreas" will be translated into gatheringEvent.collectingAreas
287
	 * @param propertyPaths
288
	 * @return
289
	 */
290
	private List<String> getFieldObjectPropertyPaths(List<String> propertyPaths) {
291
		List<String> result = new ArrayList<String>();
292
		for (String facadePath : propertyPaths){
293
			// collecting areas (named area)
294
			if (facadePath.startsWith("collectingAreas")){
295
				facadePath = "gatheringEvent." + facadePath;
296
				result.add(facadePath);
297
			}
298
			// collector (agentBase)
299
			else if (facadePath.startsWith("collector")){
300
				facadePath = facadePath.replace("collector", "gatheringEvent.actor");
301
				result.add(facadePath);
302
			}
303
			// exactLocation (agentBase)
304
			else if (facadePath.startsWith("exactLocation")){
305
				facadePath = "gatheringEvent." + facadePath;
306
				result.add(facadePath);
307
			}
308
			// gatheringPeriod (TimePeriod)
309
			else if (facadePath.startsWith("gatheringPeriod")){
310
				facadePath = facadePath.replace("gatheringPeriod", "gatheringEvent.timeperiod");
311
				result.add(facadePath);
312
			}
313
			// (locality/ localityLanguage , LanguageString)
314
			else if (facadePath.startsWith("locality")){
315
				facadePath = "gatheringEvent." + facadePath;
316
				result.add(facadePath);
317
			}
318
			
319
			//*********** FIELD OBJECT ************
320
			// fieldObjectDefinitions (Map<language, languageString)
321
			else if (facadePath.startsWith("fieldObjectDefinitions")){
322
				// TODO or definition ???
323
				facadePath = facadePath.replace("fieldObjectDefinitions", "description");
324
				result.add(facadePath);
325
			}
326
			// fieldObjectMedia  (Media)
327
			else if (facadePath.startsWith("fieldObjectMedia")){
328
				// TODO ??? 
329
				facadePath = facadePath.replace("fieldObjectMedia", "descriptions.elements.media");
330
				result.add(facadePath);
331
			}
332
			
333
			//Gathering Event will always be added
334
			result.add("gatheringEvent");
335
			
336
		}
337
		
338
/*
339
		Gathering Event
340
		====================
341
		- gatheringEvent (GatheringEvent)
342

    
343
		Field Object
344
		=================
345
		- ecology/ ecologyAll (String)  ???
346
		- plant description (like ecology)
347
		
348
		- fieldObjectImageGallery (SpecimenDescription)  - is automatically initialized via fieldObjectMedia
349

    
350
*/
351
		
352
		return result;
353
	}
354
	
355
	/**
356
	 * Transforms the property paths in a way that the facade is handled just like an 
357
	 * ordinary CdmBase object.<BR>
358
	 * E.g. a property path "collectinAreas" will be translated into gatheringEvent.collectingAreas
359
	 * 
360
	 * Not needed (?) as the facade works with REST service property paths without using this method.
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
		if (derivedUnit == null){
429
			throw new NullPointerException("Facade's derviedUnit must not be null to set cache strategy");
430
		}
431
		derivedUnit.setCacheStrategy(new DerivedUnitFacadeCacheStrategy());
432
	}
433

    
434

    
435
	/**
436
	 * @param feature
437
	 * @param createIfNotExists
438
	 * @param isImageGallery
439
	 * @return
440
	 * @throws DerivedUnitFacadeNotSupportedException
441
	 */
442
	private TextData initializeFieldObjectTextDataWithSupportTest(Feature feature, boolean createIfNotExists, boolean isImageGallery) throws DerivedUnitFacadeNotSupportedException {
443
		//field object
444
		FieldObservation fieldObject = getFieldObservation(createIfNotExists) ;
445
		if (fieldObject == null){
446
			return null;
447
		}
448
		return inititializeTextDataWithSupportTest(feature, fieldObject, createIfNotExists, isImageGallery);
449
	}
450

    
451

    
452
	/**
453
	 * @param feature
454
	 * @param specimen
455
	 * @param createIfNotExists
456
	 * @param isImageGallery
457
	 * @return
458
	 * @throws DerivedUnitFacadeNotSupportedException
459
	 */
460
	private TextData inititializeTextDataWithSupportTest(Feature feature, SpecimenOrObservationBase specimen, boolean createIfNotExists, 
461
				boolean isImageGallery) throws DerivedUnitFacadeNotSupportedException {
462
		if (feature == null ){
463
			return null;
464
		}
465
		TextData textData = null;
466
		if (createIfNotExists){
467
			textData = TextData.NewInstance(feature);
468
		}
469
		
470
		Set<SpecimenDescription> descriptions;
471
		if (isImageGallery){
472
			descriptions = specimen.getSpecimenDescriptionImageGallery();
473
		}else{
474
			descriptions = specimen.getSpecimenDescriptions(false);
475
		}
476
		if (descriptions.size() == 0){
477
			if (createIfNotExists){
478
				SpecimenDescription newSpecimenDescription = SpecimenDescription.NewInstance(specimen);
479
				newSpecimenDescription.addElement(textData);
480
				return textData;
481
			}else{
482
				return null;
483
			}
484
		}
485
		Set<DescriptionElementBase> existingTextData = new HashSet<DescriptionElementBase>();
486
		for (SpecimenDescription description : descriptions){
487
			for (DescriptionElementBase element: description.getElements()){
488
				if (element.isInstanceOf(TextData.class) && ( feature.equals(element.getFeature() )|| isImageGallery ) ){
489
					existingTextData.add(element);
490
				}
491
			}
492
		}
493
		if (existingTextData.size() > 1){
494
			throw new DerivedUnitFacadeNotSupportedException("Specimen facade does not support more than one description text data of type " + feature.getLabel());
495
			
496
		}else if (existingTextData.size() == 1){
497
			return CdmBase.deproxy(existingTextData.iterator().next(), TextData.class);
498
		}else{
499
			SpecimenDescription description = descriptions.iterator().next();
500
			description.addElement(textData);
501
			return textData;
502
		}
503
	}
504
	
505

    
506
	/**
507
	 * Tests if a given image gallery is supported by the derived unit facade.
508
	 * It returns the only text data attached to the given image gallery.
509
	 * If the given image gallery does not have text data attached, it is created and attached.
510
	 * @param imageGallery
511
	 * @return
512
	 * @throws DerivedUnitFacadeNotSupportedException
513
	 */
514
	private TextData testImageGallery(SpecimenDescription imageGallery) throws DerivedUnitFacadeNotSupportedException {
515
		if (imageGallery.isImageGallery() == false){
516
			throw new DerivedUnitFacadeNotSupportedException("Image gallery needs to have image gallery flag set");
517
		}
518
		if (imageGallery.getElements().size() > 1){
519
			throw new DerivedUnitFacadeNotSupportedException("Image gallery must not have more then one description element");
520
		}
521
		TextData textData;
522
		if (imageGallery.getElements().size() == 0){
523
			textData = TextData.NewInstance(Feature.IMAGE());
524
			imageGallery.addElement(textData);
525
		}else{
526
			if (! imageGallery.getElements().iterator().next().isInstanceOf(TextData.class)){
527
				throw new DerivedUnitFacadeNotSupportedException("Image gallery must only have TextData as element");
528
			}else{
529
				textData = CdmBase.deproxy(imageGallery.getElements().iterator().next(), TextData.class);
530
			}
531
		}
532
		return textData;
533
	}
534

    
535
//************************** METHODS *****************************************	
536

    
537
	private TextData getDerivedUnitImageGalleryTextData(boolean createIfNotExists) throws DerivedUnitFacadeNotSupportedException{
538
		if (this.derivedUnitMediaTextData == null && createIfNotExists){
539
			this.derivedUnitMediaTextData = getImageGalleryTextData(derivedUnit, "Specimen");
540
		}
541
		return this.derivedUnitMediaTextData;
542
	}
543
	
544
	private TextData getObservationImageGalleryTextData(boolean createIfNotExists) throws DerivedUnitFacadeNotSupportedException{
545
		if (this.fieldObjectMediaTextData == null && createIfNotExists){
546
			this.fieldObjectMediaTextData = getImageGalleryTextData(fieldObservation, "Field observation");
547
		}
548
		return this.fieldObjectMediaTextData;
549
	}
550

    
551
	
552
	
553
	/**
554
	 * @param derivationEvent2
555
	 * @return
556
	 * @throws DerivedUnitFacadeNotSupportedException 
557
	 */
558
	private Set<FieldObservation> getFieldObservationsOriginals(DerivationEvent derivationEvent, Set<SpecimenOrObservationBase> recursionAvoidSet) throws DerivedUnitFacadeNotSupportedException {
559
		if (recursionAvoidSet == null){
560
			recursionAvoidSet = new HashSet<SpecimenOrObservationBase>();
561
		}
562
		Set<FieldObservation> result = new HashSet<FieldObservation>();
563
		Set<SpecimenOrObservationBase> originals = derivationEvent.getOriginals();
564
		for (SpecimenOrObservationBase original : originals){
565
			if (original.isInstanceOf(FieldObservation.class)){
566
				result.add(CdmBase.deproxy(original, FieldObservation.class));
567
			}else if (original.isInstanceOf(DerivedUnitBase.class)){
568
				//if specimen has already been tested exclude it from further recursion
569
				if (recursionAvoidSet.contains(original)){
570
					continue;
571
				}
572
				DerivedUnitBase derivedUnit = CdmBase.deproxy(original, DerivedUnitBase.class);
573
				DerivationEvent originalDerivation = derivedUnit.getDerivedFrom();
574
//				Set<DerivationEvent> derivationEvents = original.getDerivationEvents(); 
575
//				for (DerivationEvent originalDerivation : derivationEvents){
576
					Set<FieldObservation> fieldObservations = getFieldObservationsOriginals(originalDerivation, recursionAvoidSet);
577
					result.addAll(fieldObservations);
578
//				}
579
			}else{
580
				throw new DerivedUnitFacadeNotSupportedException("Unhandled specimen or observation base type: " + original.getClass().getName() );
581
			}
582
			
583
		}
584
		return result;
585
	}
586
	
587
	//*********** MEDIA METHODS ******************************
588
	
589
//	/**
590
//	 * Returns the media list for a specimen. Throws an exception if the existing specimen descriptions
591
//	 * are not supported by this facade.
592
//	 * @param specimen the specimen the media belongs to
593
//	 * @param specimenExceptionText text describing the specimen for exception messages
594
//	 * @return
595
//	 * @throws DerivedUnitFacadeNotSupportedException
596
//	 */
597
//	private List<Media> getImageGalleryMedia(SpecimenOrObservationBase specimen, String specimenExceptionText) throws DerivedUnitFacadeNotSupportedException{
598
//		List<Media> result;
599
//		SpecimenDescription imageGallery = getImageGalleryWithSupportTest(specimen, specimenExceptionText, true);
600
//		TextData textData = getImageTextDataWithSupportTest(imageGallery, specimenExceptionText);
601
//		result = textData.getMedia();
602
//		return result;
603
//	}
604
	
605
	/**
606
	 * Returns the media list for a specimen. Throws an exception if the existing specimen descriptions
607
	 * are not supported by this facade.
608
	 * @param specimen the specimen the media belongs to
609
	 * @param specimenExceptionText text describing the specimen for exception messages
610
	 * @return
611
	 * @throws DerivedUnitFacadeNotSupportedException
612
	 */
613
	private TextData getImageGalleryTextData(SpecimenOrObservationBase specimen, String specimenExceptionText) throws DerivedUnitFacadeNotSupportedException{
614
		TextData result;
615
		SpecimenDescription imageGallery = getImageGalleryWithSupportTest(specimen, specimenExceptionText, true);
616
		result = getImageTextDataWithSupportTest(imageGallery, specimenExceptionText);
617
		return result;
618
	}
619
	
620
	
621
	/**
622
	 * Returns the image gallery of the according specimen. Throws an exception if the attached
623
	 * image gallerie(s) are not supported by this facade.
624
	 * If no image gallery exists a new one is created if <code>createNewIfNotExists</code> is true and
625
	 * if specimen is not <code>null</code>.
626
	 * @param specimen
627
	 * @param specimenText
628
	 * @param createNewIfNotExists
629
	 * @return
630
	 * @throws DerivedUnitFacadeNotSupportedException
631
	 */
632
	private SpecimenDescription getImageGalleryWithSupportTest(SpecimenOrObservationBase<?> specimen, String specimenText, boolean createNewIfNotExists) throws DerivedUnitFacadeNotSupportedException{
633
		if (specimen == null){
634
			return null;
635
		}
636
		SpecimenDescription imageGallery;
637
		if (hasMultipleImageGalleries(specimen)){
638
			throw new DerivedUnitFacadeNotSupportedException( specimenText + " must not have more than 1 image gallery");
639
		}else{
640
			imageGallery = getImageGallery(specimen, createNewIfNotExists);
641
			getImageTextDataWithSupportTest(imageGallery, specimenText);
642
		}
643
		return imageGallery;
644
	}
645
	
646
	/**
647
	 * Returns the media holding text data element of the image gallery. Throws an exception if multiple
648
	 * such text data already exist.
649
	 * Creates a new text data if none exists and adds it to the image gallery.
650
	 * If image gallery is <code>null</code> nothing happens.
651
	 * @param imageGallery
652
	 * @param textData
653
	 * @return
654
	 * @throws DerivedUnitFacadeNotSupportedException 
655
	 */
656
	private TextData getImageTextDataWithSupportTest(SpecimenDescription imageGallery, String specimenText) throws DerivedUnitFacadeNotSupportedException {
657
		if (imageGallery == null){
658
			return null;
659
		}
660
		TextData textData = null;
661
		for (DescriptionElementBase element: imageGallery.getElements()){
662
			if (element.isInstanceOf(TextData.class) && element.getFeature().equals(Feature.IMAGE())){
663
				if (textData != null){
664
					throw new DerivedUnitFacadeNotSupportedException( specimenText + " must not have more than 1 image text data element in image gallery");
665
				}
666
				textData = CdmBase.deproxy(element, TextData.class);
667
			}
668
		}
669
		if (textData == null){
670
			textData = TextData.NewInstance(Feature.IMAGE());
671
			imageGallery.addElement(textData);
672
		}
673
		return textData;
674
	}
675

    
676
	/**
677
	 * Checks, if a specimen belongs to more than one description that is an image gallery
678
	 * @param derivedUnit
679
	 * @return
680
	 */
681
	private boolean hasMultipleImageGalleries(SpecimenOrObservationBase<?> derivedUnit){
682
		int count = 0;
683
		Set<SpecimenDescription> descriptions= derivedUnit.getSpecimenDescriptions();
684
		for (SpecimenDescription description : descriptions){
685
			if (description.isImageGallery()){
686
				count++;
687
			}
688
		}
689
		return (count > 1);
690
	}
691

    
692
	
693
	/**
694
	 * Returns the image gallery for a specimen. If there are multiple specimen descriptions
695
	 * marked as image galleries an arbitrary one is chosen.
696
	 * If no image gallery exists, a new one is created if <code>createNewIfNotExists</code>
697
	 * is <code>true</code>.<Br>
698
	 * If specimen is <code>null</code> a null pointer exception is thrown.
699
	 * @param createNewIfNotExists
700
	 * @return
701
	 */
702
	private SpecimenDescription getImageGallery(SpecimenOrObservationBase<?> specimen, boolean createIfNotExists) {
703
		SpecimenDescription result = null;
704
		Set<SpecimenDescription> descriptions= specimen.getSpecimenDescriptions();
705
		for (SpecimenDescription description : descriptions){
706
			if (description.isImageGallery()){
707
				result = description;
708
				break;
709
			}
710
		}
711
		if (result == null && createIfNotExists){
712
			result = SpecimenDescription.NewInstance(specimen);
713
			result.setImageGallery(true);
714
		}
715
		return result;
716
	}
717
	
718
	/**
719
	 * Adds a media to the specimens image gallery. If media is <code>null</code> nothing happens.
720
	 * @param media
721
	 * @param specimen
722
	 * @return true if media is not null (as specified by {@link java.util.Collection#add(Object) Collection.add(E e)} 
723
	 * @throws DerivedUnitFacadeNotSupportedException
724
	 */
725
	private boolean addMedia(Media media, SpecimenOrObservationBase<?> specimen) throws DerivedUnitFacadeNotSupportedException {
726
		if (media != null){
727
			List<Media> mediaList = getMedia(specimen, true);
728
			return mediaList.add(media);
729
		}else{
730
			return false;
731
		}
732
	}
733

    
734
	/**
735
	 * Removes a media from the specimens image gallery.
736
	 * @param media
737
	 * @param specimen
738
	 * @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)} 
739
	 * @throws DerivedUnitFacadeNotSupportedException
740
	 */
741
	private boolean removeMedia(Media media, SpecimenOrObservationBase<?> specimen) throws DerivedUnitFacadeNotSupportedException {
742
		List<Media> mediaList = getMedia(specimen, true);
743
		return mediaList == null ? null : mediaList.remove(media);
744
	}
745

    
746
	private List<Media> getMedia(SpecimenOrObservationBase<?> specimen, boolean createIfNotExists) throws DerivedUnitFacadeNotSupportedException {
747
		TextData textData = getMediaTextData(specimen, createIfNotExists);
748
		return textData == null ? null : textData.getMedia();
749
	}
750
	
751
	/**
752
	 * Returns the one media list of a specimen which is part of the only image gallery that 
753
	 * this specimen is part of.<BR>
754
	 * If these conditions are not hold an exception is thrwon.
755
	 * @param specimen
756
	 * @return
757
	 * @throws DerivedUnitFacadeNotSupportedException
758
	 */
759
//	private List<Media> getMedia(SpecimenOrObservationBase<?> specimen) throws DerivedUnitFacadeNotSupportedException {
760
//		if (specimen == null){
761
//			return null;
762
//		}
763
//		if (specimen == this.derivedUnit){
764
//			return getDerivedUnitImageGalleryMedia();
765
//		}else if (specimen == this.fieldObservation){
766
//			return getObservationImageGalleryTextData();
767
//		}else{
768
//			return getImageGalleryMedia(specimen, "Undefined specimen ");
769
//		}
770
//	}
771
	
772
	/**
773
	 * Returns the one media list of a specimen which is part of the only image gallery that 
774
	 * this specimen is part of.<BR>
775
	 * If these conditions are not hold an exception is thrwon.
776
	 * @param specimen
777
	 * @return
778
	 * @throws DerivedUnitFacadeNotSupportedException
779
	 */
780
	private TextData getMediaTextData(SpecimenOrObservationBase<?> specimen, boolean createIfNotExists) throws DerivedUnitFacadeNotSupportedException {
781
		if (specimen == null){
782
			return null;
783
		}
784
		if (specimen == this.derivedUnit){
785
			return getDerivedUnitImageGalleryTextData(createIfNotExists);
786
		}else if (specimen == this.fieldObservation){
787
			return getObservationImageGalleryTextData(createIfNotExists);
788
		}else{
789
			return getImageGalleryTextData(specimen, "Undefined specimen ");
790
		}
791
	}
792
	
793
	
794
//****************** GETTER / SETTER / ADDER / REMOVER ***********************/	
795
	
796
// ****************** Gathering Event *********************************/
797
	
798
	//country
799
	@Transient
800
	public NamedArea getCountry(){
801
		return  (hasGatheringEvent() ? getGatheringEvent(true).getCountry() : null);
802
	}
803
	
804
	public void setCountry(NamedArea country){
805
		getGatheringEvent(true).setCountry(country);
806
	}
807
	
808
	
809
	//Collecting area
810
	public void addCollectingArea(NamedArea area) {
811
		getGatheringEvent(true).addCollectingArea(area);
812
	}
813
	public void addCollectingAreas(java.util.Collection<NamedArea> areas) {
814
		for (NamedArea area : areas){
815
			getGatheringEvent(true).addCollectingArea(area);
816
		}
817
	}
818
	@Transient
819
	public Set<NamedArea> getCollectingAreas() {
820
		return  (hasGatheringEvent() ? getGatheringEvent(true).getCollectingAreas() : null);
821
	}
822
	public void removeCollectingArea(NamedArea area) {
823
		if (hasGatheringEvent()){
824
			getGatheringEvent(true).removeCollectingArea(area);
825
		}
826
	}
827

    
828
	//absolute elevation  
829
	/** meter above/below sea level of the surface 
830
	 * @see #getAbsoluteElevationError()
831
	 * @see #getAbsoluteElevationRange()
832
	 **/
833
	@Transient
834
	public Integer getAbsoluteElevation() {
835
		return (hasGatheringEvent() ? getGatheringEvent(true).getAbsoluteElevation() : null);
836
	}
837
	public void setAbsoluteElevation(Integer absoluteElevation) {
838
		getGatheringEvent(true).setAbsoluteElevation(absoluteElevation);
839
	}
840

    
841
	//absolute elevation error
842
	@Transient
843
	public Integer getAbsoluteElevationError() {
844
		return (hasGatheringEvent() ? getGatheringEvent(true).getAbsoluteElevationError() : null);
845
	}
846
	public void setAbsoluteElevationError(Integer absoluteElevationError) {
847
		getGatheringEvent(true).setAbsoluteElevationError(absoluteElevationError);
848
	}
849
	
850
	/**
851
	 * @see #getAbsoluteElevation()
852
	 * @see #getAbsoluteElevationError()
853
	 * @see #setAbsoluteElevationRange(Integer, Integer)
854
	 * @see #getAbsoluteElevationMaximum()
855
	 */
856
	@Transient
857
	public Integer getAbsoluteElevationMinimum(){
858
		if ( ! hasGatheringEvent() ){
859
			return null;
860
		}
861
		Integer minimum = getGatheringEvent(true).getAbsoluteElevation();
862
		if (getGatheringEvent(true).getAbsoluteElevationError() != null){
863
			minimum = minimum -  getGatheringEvent(true).getAbsoluteElevationError();
864
		}
865
		return minimum;
866
	}
867
	/**
868
	 * @see #getAbsoluteElevation()
869
	 * @see #getAbsoluteElevationError()
870
	 * @see #setAbsoluteElevationRange(Integer, Integer)
871
	 * @see #getAbsoluteElevationMinimum()
872
	 */
873
	@Transient
874
	public Integer getAbsoluteElevationMaximum(){
875
		if ( ! hasGatheringEvent() ){
876
			return null;
877
		}
878
		Integer maximum = getGatheringEvent(true).getAbsoluteElevation();
879
		if (getGatheringEvent(true).getAbsoluteElevationError() != null){
880
			maximum = maximum +  getGatheringEvent(true).getAbsoluteElevationError();
881
		}
882
		return maximum;
883
	}
884

    
885
	
886
	/**
887
	 * This method replaces absoluteElevation and absoulteElevationError by
888
	 * internally translating minimum and maximum values into
889
	 * average and error values. As all these values are integer based
890
	 * it is necessary that the distance is between minimum and maximum is <b>even</b>,
891
	 * otherwise we will get a rounding error resulting in a maximum that is increased
892
	 * by 1.
893
	 * @see #setAbsoluteElevation(Integer)
894
	 * @see #setAbsoluteElevationError(Integer)
895
	 * @param minimumElevation minimum of the range
896
	 * @param maximumElevation maximum of the range
897
	 */
898
	public void setAbsoluteElevationRange(Integer minimumElevation, Integer maximumElevation){
899
		if (minimumElevation == null || maximumElevation == null){
900
			Integer elevation = minimumElevation;
901
			Integer error = 0;
902
			if (minimumElevation == null){
903
				elevation = maximumElevation;
904
				if (elevation == null){
905
					error = null;
906
				}
907
			}
908
			getGatheringEvent(true).setAbsoluteElevation(elevation);
909
			getGatheringEvent(true).setAbsoluteElevationError(error);
910
		}else{
911
			if (! isEvenDistance(minimumElevation, maximumElevation) ){
912
				throw new IllegalArgumentException("Distance between minimum and maximum elevation must be even but was " + Math.abs(minimumElevation - maximumElevation));
913
			}
914
			Integer absoluteElevationError = Math.abs(maximumElevation - minimumElevation);
915
			absoluteElevationError = absoluteElevationError / 2;
916
			Integer absoluteElevation = minimumElevation + absoluteElevationError;
917
			getGatheringEvent(true).setAbsoluteElevation(absoluteElevation);
918
			getGatheringEvent(true).setAbsoluteElevationError(absoluteElevationError);
919
		}
920
	}
921

    
922
	/**
923
	 * @param minimumElevation
924
	 * @param maximumElevation
925
	 * @return
926
	 */
927
	private boolean isEvenDistance(Integer minimumElevation, Integer maximumElevation) {
928
		Integer diff = ( maximumElevation - minimumElevation);
929
		Integer testDiff = (diff /2) *2 ;
930
		return (testDiff == diff);
931
	}
932

    
933
	//collector
934
	@Transient
935
	public AgentBase getCollector() {
936
		return  (hasGatheringEvent() ? getGatheringEvent(true).getCollector() : null);
937
	}
938
	public void setCollector(AgentBase collector){
939
		getGatheringEvent(true).setCollector(collector);
940
	}
941

    
942
	//collecting method
943
	@Transient
944
	public String getCollectingMethod() {
945
		return  (hasGatheringEvent() ? getGatheringEvent(true).getCollectingMethod() : null);
946
	}
947
	public void setCollectingMethod(String collectingMethod) {
948
		getGatheringEvent(true).setCollectingMethod(collectingMethod);
949
	}
950

    
951
	//distance to ground
952
	@Transient
953
	public Integer getDistanceToGround() {
954
		return  (hasGatheringEvent() ? getGatheringEvent(true).getDistanceToGround() : null);
955
	}
956
	public void setDistanceToGround(Integer distanceToGround) {
957
		getGatheringEvent(true).setDistanceToGround(distanceToGround);
958
	}
959

    
960
	//distance to water surface
961
	@Transient
962
	public Integer getDistanceToWaterSurface() {
963
		return  (hasGatheringEvent() ? getGatheringEvent(true).getDistanceToWaterSurface() : null);
964
	}
965
	public void setDistanceToWaterSurface(Integer distanceToWaterSurface) {
966
		getGatheringEvent(true).setDistanceToWaterSurface(distanceToWaterSurface);
967
	}
968

    
969
	//exact location
970
	@Transient
971
	public Point getExactLocation() {
972
		return  (hasGatheringEvent() ? getGatheringEvent(true).getExactLocation() : null );
973
	}
974
	
975
	/**
976
	 * Returns a sexagesimal representation of the exact location (e.g. 12°59'N, 35°23E).
977
	 * If the exact location is <code>null</code> the empty string is returned.
978
	 * @param includeEmptySeconds
979
	 * @param includeReferenceSystem
980
	 * @return
981
	 */
982
	public String getExactLocationText(boolean includeEmptySeconds, boolean includeReferenceSystem){
983
		return (this.getExactLocation() == null ? "" : this.getExactLocation().toSexagesimalString(includeEmptySeconds, includeReferenceSystem));
984
	}
985
	public void setExactLocation(Point exactLocation) {
986
		getGatheringEvent(true).setExactLocation(exactLocation);
987
	}
988
	public void setExactLocationByParsing(String longitudeToParse, String latitudeToParse, ReferenceSystem referenceSystem, Integer errorRadius) throws ParseException{
989
		Point point = Point.NewInstance(null, null, referenceSystem, errorRadius);
990
		point.setLongitudeByParsing(longitudeToParse);
991
		point.setLatitudeByParsing(latitudeToParse);
992
		setExactLocation(point);
993
	}
994
	
995
	//gathering event description
996
	@Transient
997
	public String getGatheringEventDescription() {
998
		return  (hasGatheringEvent() ? getGatheringEvent(true).getDescription() : null);
999
	}
1000
	public void setGatheringEventDescription(String description) {
1001
		getGatheringEvent(true).setDescription(description);
1002
	}
1003

    
1004
	//gatering period
1005
	@Transient
1006
	public TimePeriod getGatheringPeriod() {
1007
		return (hasGatheringEvent() ? getGatheringEvent(true).getTimeperiod() : null);
1008
	}
1009
	public void setGatheringPeriod(TimePeriod timeperiod) {
1010
		getGatheringEvent(true).setTimeperiod(timeperiod);
1011
	}
1012

    
1013
	//locality
1014
	@Transient
1015
	public LanguageString getLocality(){
1016
		return (hasGatheringEvent() ? getGatheringEvent(true).getLocality() : null);
1017
	}
1018
	/**
1019
	 * convienience method for {@link #getLocality()}.{@link LanguageString#getText() getText()}
1020
	 * @return
1021
	 */
1022
	@Transient
1023
	public String getLocalityText(){
1024
		LanguageString locality = getLocality();
1025
		if(locality != null){
1026
			return locality.getText();
1027
		}
1028
		return null;
1029
	}
1030
	/**
1031
	 * convienience method for {@link #getLocality()}.{@link LanguageString#getLanguage() getLanguage()}
1032
	 * @return
1033
	 */
1034
	@Transient
1035
	public Language getLocalityLanguage(){
1036
		LanguageString locality = getLocality();
1037
		if(locality != null){
1038
			return locality.getLanguage();
1039
		}
1040
		return null;
1041
	}
1042
	
1043
	/**
1044
	 * Sets the locality string in the default language
1045
	 * @param locality
1046
	 */
1047
	public void setLocality(String locality){
1048
		Language language = Language.DEFAULT();
1049
		setLocality(locality, language);
1050
	}
1051
	public void setLocality(String locality, Language language){
1052
		LanguageString langString = LanguageString.NewInstance(locality, language);
1053
		setLocality(langString);
1054
	}
1055
	public void setLocality(LanguageString locality){
1056
		getGatheringEvent(true).setLocality(locality);
1057
	}
1058
	
1059
	/**
1060
	 * The gathering event will be used for the field object instead of the old gathering event.<BR>
1061
	 * <B>This method will override all gathering values (see below).</B>
1062
	 * @see #getAbsoluteElevation()
1063
	 * @see #getAbsoluteElevationError()
1064
	 * @see #getDistanceToGround()
1065
	 * @see #getDistanceToWaterSurface()
1066
	 * @see #getExactLocation()
1067
	 * @see #getGatheringEventDescription()
1068
	 * @see #getGatheringPeriod()
1069
	 * @see #getCollectingAreas()
1070
	 * @see #getCollectingMethod()
1071
	 * @see #getLocality()
1072
	 * @see #getCollector()	
1073
	 * @param gatheringEvent
1074
	 */
1075
	public void setGatheringEvent(GatheringEvent gatheringEvent) {
1076
		getFieldObservation(true).setGatheringEvent(gatheringEvent);
1077
	}
1078
	public boolean hasGatheringEvent(){
1079
		return (getGatheringEvent(false) != null);
1080
	}
1081
	
1082
	public GatheringEvent innerGatheringEvent() {
1083
		return getGatheringEvent(false);
1084
	}
1085
	
1086
	public GatheringEvent getGatheringEvent(boolean createIfNotExists) {
1087
		if (! hasFieldObservation() && ! createIfNotExists){
1088
			return null;
1089
		}
1090
		if (createIfNotExists && getFieldObservation(true).getGatheringEvent() == null ){
1091
			GatheringEvent gatheringEvent = GatheringEvent.NewInstance();
1092
			getFieldObservation(true).setGatheringEvent(gatheringEvent);
1093
		}
1094
		return getFieldObservation(true).getGatheringEvent();
1095
	}
1096
	
1097
// ****************** Field Object ************************************/
1098
	
1099
	/**
1100
	 * Returns true if a field observation exists (even if all attributes are empty or <code>null<code>.
1101
	 * @return
1102
	 */
1103
	public boolean hasFieldObject(){
1104
		return this.fieldObservation != null;
1105
	}
1106
	
1107
	//ecology
1108
	@Transient
1109
	public String getEcology(){
1110
		return getEcology(Language.DEFAULT());
1111
	}
1112
	public String getEcology(Language language){
1113
		LanguageString languageString = getEcologyAll().get(language);
1114
		return (languageString == null ? null : languageString.getText());
1115
	}
1116
//	public String getEcologyPreferred(List<Language> languages){
1117
//		LanguageString languageString = getEcologyAll().getPreferredLanguageString(languages);
1118
//		return languageString.getText();
1119
//	}
1120
	/**
1121
	 * Returns a copy of the multilanguage text holding the ecology data.
1122
	 * @see {@link TextData#getMultilanguageText()}
1123
	 * @return
1124
	 */
1125
	@Transient
1126
	public Map<Language, LanguageString> getEcologyAll(){
1127
		if (ecology == null){
1128
			try {
1129
				ecology = initializeFieldObjectTextDataWithSupportTest(Feature.ECOLOGY(), false, false);
1130
			} catch (DerivedUnitFacadeNotSupportedException e) {
1131
				throw new IllegalStateException(notSupportMessage, e);
1132
			}
1133
			if (ecology == null){
1134
				return new HashMap<Language, LanguageString>();
1135
			}
1136
		}
1137
		return ecology.getMultilanguageText();
1138
	}
1139
	
1140
	public void setEcology(String ecology){
1141
		setEcology(ecology, null);
1142
	}
1143
	public void setEcology(String ecologyText, Language language){
1144
		if (language == null){
1145
			language = Language.DEFAULT();
1146
		}
1147
		if (ecology == null){
1148
			try {
1149
				ecology = initializeFieldObjectTextDataWithSupportTest(Feature.ECOLOGY(), true, false);
1150
			} catch (DerivedUnitFacadeNotSupportedException e) {
1151
				throw new IllegalStateException(notSupportMessage, e);
1152
			}
1153
		}
1154
		if (ecologyText == null){
1155
			ecology.removeText(language);
1156
		}else{
1157
			ecology.putText(language, ecologyText);
1158
		}
1159
	}
1160
	public void removeEcology(Language language){
1161
		setEcology(null, language);
1162
	}
1163
	/**
1164
	 * Removes ecology for the default language
1165
	 */
1166
	public void removeEcology(){
1167
		setEcology(null, null);
1168
	}
1169
	public void removeEcologyAll(){
1170
		
1171
	}
1172

    
1173
	
1174
	//plant description
1175
	@Transient
1176
	public String getPlantDescription(){
1177
		return getPlantDescription(null);
1178
	}
1179
	public String getPlantDescription(Language language){
1180
		if (language == null){
1181
			language = Language.DEFAULT();
1182
		}
1183
		LanguageString languageString = getPlantDescriptionAll().get(language);
1184
		return (languageString == null ? null : languageString.getText());
1185
	}
1186
//	public String getPlantDescriptionPreferred(List<Language> languages){
1187
//		LanguageString languageString = getPlantDescriptionAll().getPreferredLanguageString(languages);
1188
//		return languageString.getText();
1189
//	}
1190
	/**
1191
	 * Returns a copy of the multilanguage text holding the description data.
1192
	 * @see {@link TextData#getMultilanguageText()}
1193
	 * @return
1194
	 */
1195
	@Transient
1196
	public Map<Language, LanguageString> getPlantDescriptionAll(){
1197
		if (plantDescription == null){
1198
			try {
1199
				plantDescription = initializeFieldObjectTextDataWithSupportTest(Feature.DESCRIPTION(), false, false);
1200
			} catch (DerivedUnitFacadeNotSupportedException e) {
1201
				throw new IllegalStateException(notSupportMessage, e);
1202
			}
1203
			if (plantDescription == null){
1204
				return new HashMap<Language, LanguageString>();
1205
			}
1206
		}
1207
		return plantDescription.getMultilanguageText();
1208
	}
1209
	public void setPlantDescription(String plantDescription){
1210
		setPlantDescription(plantDescription, null);
1211
	}
1212
	public void setPlantDescription(String plantDescriptionText, Language language){
1213
		if (language == null){
1214
			language = Language.DEFAULT();
1215
		}
1216
		if (plantDescription == null){
1217
			try {
1218
				plantDescription = initializeFieldObjectTextDataWithSupportTest(Feature.DESCRIPTION(), true, false);
1219
			} catch (DerivedUnitFacadeNotSupportedException e) {
1220
				throw new IllegalStateException(notSupportMessage, e);
1221
			}
1222
		}
1223
		if (plantDescriptionText == null){
1224
			plantDescription.removeText(language);
1225
		}else{
1226
			plantDescription.putText(language, plantDescriptionText);
1227
		}
1228
	}
1229
	public void removePlantDescription(Language language){
1230
		setPlantDescription(null, language);
1231
	}
1232
	
1233
	//field object definition
1234
	public void addFieldObjectDefinition(String text, Language language) {
1235
		getFieldObservation(true).addDefinition(text, language);
1236
	}
1237
	@Transient
1238
	public Map<Language, LanguageString> getFieldObjectDefinition() {
1239
		if (! hasFieldObservation()){
1240
			return new HashMap<Language, LanguageString>();
1241
		}else{
1242
			return getFieldObservation(true).getDefinition();
1243
		}
1244
	}
1245
	public String getFieldObjectDefinition(Language language) {
1246
		Map<Language, LanguageString> map = getFieldObjectDefinition();
1247
		LanguageString languageString = (map == null? null : map.get(language));
1248
		if (languageString != null){
1249
			return languageString.getText();
1250
		}else {
1251
			return null;
1252
		}
1253
	}
1254
	public void removeFieldObjectDefinition(Language lang) {
1255
		if (hasFieldObservation()){
1256
			getFieldObservation(true).removeDefinition(lang);
1257
		}
1258
	}
1259
	
1260

    
1261
	//media
1262
	public boolean addFieldObjectMedia(Media media)  {
1263
		try {
1264
			return addMedia(media, getFieldObservation(true));
1265
		} catch (DerivedUnitFacadeNotSupportedException e) {
1266
			throw new IllegalStateException(notSupportMessage, e);
1267
		}
1268
	}
1269
	/**
1270
	 * Returns true, if an image gallery for the field object exists.<BR>
1271
	 * Returns also <code>true</code> if the image gallery is empty. 
1272
	 * @return
1273
	 */
1274
	public boolean hasFieldObjectImageGallery(){
1275
		if (! hasFieldObject()){
1276
			return false;
1277
		}else{
1278
			return (getImageGallery(fieldObservation, false) != null);
1279
		}
1280
	}
1281
	
1282
	public void setFieldObjectImageGallery(SpecimenDescription imageGallery) throws DerivedUnitFacadeNotSupportedException{
1283
		SpecimenDescription existingGallery = getFieldObjectImageGallery(false);
1284
		
1285
		//test attached specimens contain this.derivedUnit
1286
		SpecimenOrObservationBase<?> facadeFieldObservation = innerFieldObservation();
1287
		testSpecimenInImageGallery(imageGallery, facadeFieldObservation);
1288
		
1289
		if (existingGallery != null){
1290
			if (existingGallery != imageGallery){
1291
				throw new DerivedUnitFacadeNotSupportedException("DerivedUnitFacade does not allow more than one image gallery");
1292
			}else{
1293
				//do nothing
1294
			}
1295
		}else {
1296
			TextData textData = testImageGallery(imageGallery);
1297
			this.fieldObjectMediaTextData = textData;
1298
		}
1299
	}
1300

    
1301
	
1302
	/**
1303
	 * Returns the field object image gallery. If no such image gallery exists and
1304
	 * createIfNotExists is true an new one is created. Otherwise null is returned.
1305
	 * @param createIfNotExists
1306
	 * @return
1307
	 */
1308
	public SpecimenDescription getFieldObjectImageGallery(boolean createIfNotExists){
1309
		TextData textData;
1310
		try {
1311
			textData = initializeFieldObjectTextDataWithSupportTest(Feature.IMAGE(), createIfNotExists, true);
1312
		} catch (DerivedUnitFacadeNotSupportedException e) {
1313
			throw new IllegalStateException(notSupportMessage, e);
1314
		}
1315
		if (textData != null){
1316
			return CdmBase.deproxy(textData.getInDescription(), SpecimenDescription.class);
1317
		}else{
1318
			return null;
1319
		}
1320
	}
1321
	/**
1322
	 * Returns the media for the field object.<BR>
1323
	 * @return
1324
	 */
1325
	@Transient
1326
	public List<Media> getFieldObjectMedia() {
1327
		try {
1328
			List<Media> result = getMedia(getFieldObservation(false), false);
1329
			return result == null ? new ArrayList<Media>() : result;
1330
		} catch (DerivedUnitFacadeNotSupportedException e) {
1331
			throw new IllegalStateException(notSupportMessage, e);
1332
		}
1333
	}
1334
	public boolean removeFieldObjectMedia(Media media) {
1335
		try {
1336
			return removeMedia(media, getFieldObservation(false));
1337
		} catch (DerivedUnitFacadeNotSupportedException e) {
1338
			throw new IllegalStateException(notSupportMessage, e);
1339
		}
1340
	}
1341

    
1342
	//field number
1343
	@Transient
1344
	public String getFieldNumber() {
1345
		if (! hasFieldObservation()){
1346
			return null;
1347
		}else{
1348
			return getFieldObservation(true).getFieldNumber();
1349
		}
1350
	}
1351
	public void setFieldNumber(String fieldNumber) {
1352
		getFieldObservation(true).setFieldNumber(fieldNumber);
1353
	}
1354

    
1355
	//primary collector
1356
	@Transient
1357
	public Person getPrimaryCollector() {
1358
		if (! hasFieldObservation()){
1359
			return null;
1360
		}else{
1361
			return getFieldObservation(true).getPrimaryCollector();
1362
		}
1363
	}
1364
	public void setPrimaryCollector(Person primaryCollector) {
1365
		getFieldObservation(true).setPrimaryCollector(primaryCollector);
1366
	}
1367
	
1368
	
1369
	
1370
	//field notes
1371
	@Transient
1372
	public String getFieldNotes() {
1373
		if (! hasFieldObservation()){
1374
			return null;
1375
		}else{
1376
			return getFieldObservation(true).getFieldNotes();
1377
		}
1378
	}
1379
	public void setFieldNotes(String fieldNotes) {
1380
		getFieldObservation(true).setFieldNotes(fieldNotes);
1381
	}
1382

    
1383

    
1384
	//individual counts
1385
	@Transient
1386
	public Integer getIndividualCount() {
1387
		return (hasFieldObservation()? getFieldObservation(true).getIndividualCount() : null );
1388
	}
1389
	public void setIndividualCount(Integer individualCount) {
1390
		getFieldObservation(true).setIndividualCount(individualCount);
1391
	}
1392

    
1393
	//life stage
1394
	@Transient
1395
	public Stage getLifeStage() {
1396
		return (hasFieldObservation()? getFieldObservation(true).getLifeStage() : null );
1397
	}
1398
	public void setLifeStage(Stage lifeStage) {
1399
		getFieldObservation(true).setLifeStage(lifeStage);
1400
	}
1401

    
1402
	//sex
1403
	@Transient
1404
	public Sex getSex() {
1405
		return (hasFieldObservation()? getFieldObservation(true).getSex() : null );
1406
	}
1407
	public void setSex(Sex sex) {
1408
		getFieldObservation(true).setSex(sex);
1409
	}
1410
	
1411
	
1412
	//field observation
1413
	public boolean hasFieldObservation(){
1414
		return (getFieldObservation(false) != null);
1415
	}
1416
	
1417
	/**
1418
	 * Returns the field observation as an object.
1419
	 * @return
1420
	 */
1421
	public FieldObservation innerFieldObservation(){
1422
		return getFieldObservation(false);
1423
	}
1424

    
1425
	/**
1426
	 * Returns the field observation as an object.
1427
	 * @return
1428
	 */
1429
	public FieldObservation getFieldObservation(boolean createIfNotExists){
1430
		if (fieldObservation == null && createIfNotExists){
1431
			fieldObservation = FieldObservation.NewInstance();
1432
			fieldObservation.addPropertyChangeListener(getNewEventPropagationListener());
1433
			DerivationEvent derivationEvent = getDerivationEvent(true);
1434
			derivationEvent.addOriginal(fieldObservation);
1435
		}
1436
		return this.fieldObservation;
1437
	}
1438

    
1439
	
1440

    
1441

    
1442
	
1443
//****************** Specimen **************************************************	
1444
	
1445
	//Definition
1446
	public void addDerivedUnitDefinition(String text, Language language) {
1447
		derivedUnit.addDefinition(text, language);
1448
	}
1449
	@Transient
1450
	public Map<Language, LanguageString> getDerivedUnitDefinitions(){
1451
		return this.derivedUnit.getDefinition();
1452
	}
1453
	public String getDerivedUnitDefinition(Language language) {
1454
		Map<Language,LanguageString> languageMap = derivedUnit.getDefinition();
1455
		LanguageString languageString = languageMap.get(language);
1456
		if (languageString != null){
1457
			return languageString.getText();
1458
		}else {
1459
			return null;
1460
		}
1461
	}
1462
	public void removeDerivedUnitDefinition(Language lang) {
1463
		derivedUnit.removeDefinition(lang);
1464
	}
1465

    
1466
	//Determination
1467
	public void addDetermination(DeterminationEvent determination) {
1468
		derivedUnit.addDetermination(determination);
1469
	}
1470
	@Transient
1471
	public Set<DeterminationEvent> getDeterminations() {
1472
		return derivedUnit.getDeterminations();
1473
	}
1474
	public void removeDetermination(DeterminationEvent determination) {
1475
		derivedUnit.removeDetermination(determination);
1476
	}
1477
	
1478
	//Media
1479
	public boolean addDerivedUnitMedia(Media media)  {
1480
		try {
1481
			return addMedia(media, derivedUnit);
1482
		} catch (DerivedUnitFacadeNotSupportedException e) {
1483
			throw new IllegalStateException(notSupportMessage, e);
1484
		}
1485
	}
1486
	/**
1487
	 * Returns true, if an image gallery exists for the specimen.<BR>
1488
	 * Returns also <code>true</code> if the image gallery is empty. 
1489
	 */
1490
	public boolean hasDerivedUnitImageGallery(){
1491
		return (getImageGallery(derivedUnit, false) != null);
1492
	}
1493
	
1494
	public SpecimenDescription getDerivedUnitImageGallery(boolean createIfNotExists){
1495
		TextData textData;
1496
		try {
1497
			textData = inititializeTextDataWithSupportTest(Feature.IMAGE(), derivedUnit, createIfNotExists, true);
1498
		} catch (DerivedUnitFacadeNotSupportedException e) {
1499
			throw new IllegalStateException(notSupportMessage, e);
1500
		}
1501
		if (textData != null){
1502
			return CdmBase.deproxy(textData.getInDescription(), SpecimenDescription.class);
1503
		}else{
1504
			return null;
1505
		}
1506
	}
1507
	public void setDerivedUnitImageGallery(SpecimenDescription imageGallery) throws DerivedUnitFacadeNotSupportedException{
1508
		SpecimenDescription existingGallery = getDerivedUnitImageGallery(false);
1509
		
1510
		//test attached specimens contain this.derivedUnit
1511
		SpecimenOrObservationBase facadeDerivedUnit = innerDerivedUnit();
1512
		testSpecimenInImageGallery(imageGallery, facadeDerivedUnit);
1513
		
1514
		if (existingGallery != null){
1515
			if (existingGallery != imageGallery){
1516
				throw new DerivedUnitFacadeNotSupportedException("DerivedUnitFacade does not allow more than one image gallery");
1517
			}else{
1518
				//do nothing
1519
			}
1520
		}else {
1521
			TextData textData = testImageGallery(imageGallery);
1522
			this.derivedUnitMediaTextData = textData;
1523
		}
1524
	}
1525

    
1526
	/**
1527
	 * @param imageGallery
1528
	 * @throws DerivedUnitFacadeNotSupportedException
1529
	 */
1530
	private void testSpecimenInImageGallery(SpecimenDescription imageGallery, SpecimenOrObservationBase specimen) throws DerivedUnitFacadeNotSupportedException {
1531
		Set<SpecimenOrObservationBase> imageGallerySpecimens = imageGallery.getDescribedSpecimenOrObservations();
1532
		if (imageGallerySpecimens.size() < 1){
1533
			throw new DerivedUnitFacadeNotSupportedException("Image Gallery has no Specimen attached. Please attache according specimen or field observation.");
1534
		}
1535
		if (! imageGallerySpecimens.contains(specimen)){
1536
			throw new DerivedUnitFacadeNotSupportedException("Image Gallery has not the facade's field object attached. Please add field object first to image gallery specimenOrObservation list.");
1537
		}
1538
	}
1539
	
1540
	/**
1541
	 * Returns the media for the specimen.<BR>
1542
	 * @return
1543
	 */
1544
	@Transient
1545
	public List<Media> getDerivedUnitMedia() {
1546
		try {
1547
			List<Media> result = getMedia(derivedUnit, false);
1548
			return result == null ? new ArrayList<Media>() : result;
1549
		} catch (DerivedUnitFacadeNotSupportedException e) {
1550
			throw new IllegalStateException(notSupportMessage, e);
1551
		}
1552
	}
1553
	public boolean removeDerivedUnitMedia(Media media) {
1554
		try {
1555
			return removeMedia(media, derivedUnit);
1556
		} catch (DerivedUnitFacadeNotSupportedException e) {
1557
			throw new IllegalStateException(notSupportMessage, e);
1558
		}
1559
	}
1560

    
1561
	
1562
	//Accession Number
1563
	@Transient
1564
	public String getAccessionNumber() {
1565
		return derivedUnit.getAccessionNumber();
1566
	}
1567
	public void setAccessionNumber(String accessionNumber) {
1568
		derivedUnit.setAccessionNumber(accessionNumber);
1569
	}
1570

    
1571
	@Transient
1572
	public String getCatalogNumber() {
1573
		return derivedUnit.getCatalogNumber();
1574
	}
1575
	public void setCatalogNumber(String catalogNumber) {
1576
		derivedUnit.setCatalogNumber(catalogNumber);
1577
	}
1578

    
1579
	@Transient
1580
	public String getBarcode() {
1581
		return derivedUnit.getBarcode();
1582
	}
1583
	public void setBarcode(String barcode) {
1584
		derivedUnit.setBarcode(barcode);
1585
	}
1586

    
1587
	
1588
	//Preservation Method
1589
	
1590
	/**
1591
	 * Only supported by specimen and fossils
1592
	 * @see #DerivedUnitType
1593
	 * @return
1594
	 */
1595
	@Transient
1596
	public PreservationMethod getPreservationMethod() throws MethodNotSupportedByDerivedUnitTypeException {
1597
		if (derivedUnit.isInstanceOf(Specimen.class)){
1598
			return CdmBase.deproxy(derivedUnit, Specimen.class).getPreservation();
1599
		}else{
1600
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()){
1601
				throw new MethodNotSupportedByDerivedUnitTypeException("A preservation method is only available in derived units of type 'Specimen' or 'Fossil'");
1602
			}else{
1603
				return null;
1604
			}
1605
		}
1606
	}
1607
	/**
1608
	 * Only supported by specimen and fossils
1609
	 * @see #DerivedUnitType
1610
	 * @return
1611
	 */
1612
	public void setPreservationMethod(PreservationMethod preservation)throws MethodNotSupportedByDerivedUnitTypeException  {
1613
		if (derivedUnit.isInstanceOf(Specimen.class)){
1614
			CdmBase.deproxy(derivedUnit, Specimen.class).setPreservation(preservation);
1615
		}else{
1616
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()){
1617
				throw new MethodNotSupportedByDerivedUnitTypeException("A preservation method is only available in derived units of type 'Specimen' or 'Fossil'");
1618
			}else{
1619
				return;
1620
			}
1621
		}
1622
	}
1623

    
1624
	//Stored under name
1625
	@Transient
1626
	public TaxonNameBase getStoredUnder() {
1627
		return derivedUnit.getStoredUnder();
1628
	}
1629
	public void setStoredUnder(TaxonNameBase storedUnder) {
1630
		derivedUnit.setStoredUnder(storedUnder);
1631
	}
1632

    
1633
	//colletors number
1634
	@Transient
1635
	public String getCollectorsNumber() {
1636
		return derivedUnit.getCollectorsNumber();
1637
	}
1638
	public void setCollectorsNumber(String collectorsNumber) {
1639
		this.derivedUnit.setCollectorsNumber(collectorsNumber);
1640
	}
1641

    
1642
	//title cache
1643
	public String getTitleCache() {
1644
		if (! derivedUnit.isProtectedTitleCache()){
1645
			//always compute title cache anew as long as there are no property change listeners on 
1646
			//field observation, gathering event etc 
1647
			derivedUnit.setTitleCache(null, false);
1648
		}
1649
		return this.derivedUnit.getTitleCache();
1650
	}
1651
	public boolean isProtectedTitleCache(){
1652
		return derivedUnit.isProtectedTitleCache();
1653
	}
1654
	public void setTitleCache(String titleCache, boolean isProtected) {
1655
		this.derivedUnit.setTitleCache(titleCache, isProtected);
1656
	}
1657

    
1658

    
1659
	/**
1660
	 * Returns the derived unit itself.
1661
	 * @return the derived unit
1662
	 */
1663
	public DerivedUnitBase innerDerivedUnit() {
1664
		return this.derivedUnit;
1665
	}
1666
	
1667
	private boolean hasDerivationEvent(){
1668
		return getDerivationEvent() == null ? false : true;
1669
	}
1670
	private DerivationEvent getDerivationEvent(){
1671
		return getDerivationEvent(false);
1672
	}
1673
	private DerivationEvent getDerivationEvent(boolean createIfNotExists){
1674
		DerivationEvent result = derivedUnit.getDerivedFrom();
1675
		if (result == null){
1676
			result = DerivationEvent.NewInstance();
1677
			derivedUnit.setDerivedFrom(result);
1678
		}
1679
		return result;
1680
	}
1681
	@Transient
1682
	public String getExsiccatum() throws MethodNotSupportedByDerivedUnitTypeException {
1683
		if (derivedUnit.isInstanceOf(Specimen.class)){
1684
			return CdmBase.deproxy(derivedUnit, Specimen.class).getExsiccatum();
1685
		}else{
1686
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()){
1687
				throw new MethodNotSupportedByDerivedUnitTypeException("An exsiccatum is only available in derived units of type 'Specimen' or 'Fossil'");
1688
			}else{
1689
				return null;
1690
			}
1691
		}
1692
	}
1693
	
1694
	public void setExsiccatum(String exsiccatum) throws Exception{
1695
		if (derivedUnit.isInstanceOf(Specimen.class)){
1696
			CdmBase.deproxy(derivedUnit, Specimen.class).setExsiccatum(exsiccatum);
1697
		}else{
1698
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()){
1699
				throw new MethodNotSupportedByDerivedUnitTypeException("An exsiccatum is only available in derived units of type 'Specimen' or 'Fossil'");
1700
			}else{
1701
				return;
1702
			}
1703
		}
1704
	}
1705
	
1706
	
1707
	// **** sources **/
1708
	public void addSource(IdentifiableSource source){
1709
		this.derivedUnit.addSource(source);
1710
	}
1711
	/**
1712
	 * Creates an orignal source, adds it to the specimen and returns it.
1713
	 * @param reference
1714
	 * @param microReference
1715
	 * @param originalNameString
1716
	 * @return
1717
	 */
1718
	public IdentifiableSource addSource(Reference reference, String microReference, String originalNameString){
1719
		IdentifiableSource source = IdentifiableSource.NewInstance(reference, microReference);
1720
		source.setOriginalNameString(originalNameString);
1721
		derivedUnit.addSource(source);
1722
		return source;
1723
	}
1724

    
1725
	@Transient
1726
	public Set<IdentifiableSource> getSources(){
1727
		return derivedUnit.getSources();
1728
	}
1729

    
1730
	public void removeSource(IdentifiableSource source){
1731
		this.derivedUnit.removeSource(source);
1732
	}
1733
	
1734
	
1735
	/**
1736
	 * @return the collection
1737
	 */
1738
	@Transient
1739
	public Collection getCollection() {
1740
		return derivedUnit.getCollection();
1741
	}
1742

    
1743

    
1744
	/**
1745
	 * @param collection the collection to set
1746
	 */
1747
	public void setCollection(Collection collection) {
1748
		derivedUnit.setCollection(collection);
1749
	}
1750
	
1751
	//annotation	
1752
	public void addAnnotation(Annotation annotation){
1753
		this.derivedUnit.addAnnotation(annotation);
1754
	}
1755

    
1756
	@Transient
1757
	public void getAnnotations(){
1758
		this.derivedUnit.getAnnotations();
1759
	}
1760
	
1761
	public void removeAnnotation(Annotation annotation){
1762
		this.derivedUnit.removeAnnotation(annotation);
1763
	}
1764
	
1765
	
1766
// ******************************* Events *********************************************
1767
	
1768
	/**
1769
	 * @return
1770
	 */
1771
	private PropertyChangeListener getNewEventPropagationListener() {
1772
		PropertyChangeListener listener = new PropertyChangeListener(){
1773
			@Override
1774
			public void propertyChange(PropertyChangeEvent event) {
1775
				derivedUnit.firePropertyChange(event);
1776
			}
1777
			
1778
		};
1779
		return listener;
1780
	}
1781

    
1782
		
1783
	
1784

    
1785
//**************** Other Collections ***************************************************	
1786
	
1787
	/**
1788
	 * Creates a duplicate specimen which derives from the same derivation event
1789
	 * as the facade specimen and adds collection data to it (all data available in
1790
	 * DerivedUnitBase and Specimen. Data from SpecimenOrObservationBase and above
1791
	 * are not yet shared at the moment. 
1792
	 * @param collection
1793
	 * @param catalogNumber
1794
	 * @param accessionNumber
1795
	 * @param collectorsNumber
1796
	 * @param storedUnder
1797
	 * @param preservation
1798
	 * @return
1799
	 */
1800
	public Specimen addDuplicate(Collection collection, String catalogNumber, String accessionNumber, 
1801
				String collectorsNumber, TaxonNameBase storedUnder, PreservationMethod preservation){
1802
		Specimen duplicate = Specimen.NewInstance();
1803
		duplicate.setDerivedFrom(getDerivationEvent(true));
1804
		duplicate.setCollection(collection);
1805
		duplicate.setCatalogNumber(catalogNumber);
1806
		duplicate.setAccessionNumber(accessionNumber);
1807
		duplicate.setCollectorsNumber(collectorsNumber);
1808
		duplicate.setStoredUnder(storedUnder);
1809
		duplicate.setPreservation(preservation);
1810
		return duplicate;
1811
	}
1812
	
1813
	public void addDuplicate(DerivedUnitBase duplicateSpecimen){
1814
		//TODO check derivedUnitType
1815
		getDerivationEvent(true).addDerivative(duplicateSpecimen);  
1816
	}
1817
	
1818
	@Transient
1819
	public Set<Specimen> getDuplicates(){
1820
		Set<Specimen> result = new HashSet<Specimen>();
1821
		if (hasDerivationEvent()){
1822
			for (DerivedUnitBase derivedUnit: getDerivationEvent(true).getDerivatives()){
1823
				if (derivedUnit.isInstanceOf(Specimen.class) && ! derivedUnit.equals(this.derivedUnit)){
1824
					result.add(CdmBase.deproxy(derivedUnit, Specimen.class));
1825
				}
1826
			}
1827
		}
1828
		return result;
1829
	}
1830
	public void removeDuplicate(Specimen duplicateSpecimen){
1831
		if (hasDerivationEvent()){
1832
			getDerivationEvent(true).removeDerivative(duplicateSpecimen);
1833
		}
1834
	}
1835
	
1836
	
1837
	
1838
	
1839
}
(1-1/5)