Project

General

Profile

Download (76.7 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.commons.lang.StringUtils;
25
import org.apache.log4j.Logger;
26

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

    
63
/**
64
 * This class is a facade to the eu.etaxonomy.cdm.model.occurrence package from
65
 * a specimen based view. It does not support all functionality available in the
66
 * occurrence package.<BR>
67
 * The most significant restriction is that a specimen may derive only from one
68
 * direct derivation event and there must be only one field observation
69
 * (gathering event) it derives from.<BR>
70
 * 
71
 * @author a.mueller
72
 * @date 14.05.2010
73
 */
74
public class DerivedUnitFacade {
75
	@SuppressWarnings("unused")
76
	private static final Logger logger = Logger.getLogger(DerivedUnitFacade.class);
77

    
78
	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 ";
79

    
80
	private static final boolean CREATE = true;
81
	private static final boolean CREATE_NOT = false;
82

    
83
	/**
84
	 * Enum that defines the class the "Specimen" belongs to. Some methods of
85
	 * the facade are not available for certain classes and will throw an
86
	 * Exception when invoking them.
87
	 */
88
	public enum DerivedUnitType {
89
		Specimen("Specimen"), 
90
		Observation("Observation"), 
91
		LivingBeing("Living Being"), 
92
		Fossil("Fossil"), 
93
		DerivedUnit("Derived Unit"),
94
		//Field Observation is experimental, please handle with care (it is the only type which does not
95
		//have a derivedUnit and therefore throws exceptions for all method on derivedUnit attributes
96
		FieldObservation("FieldObservation");
97

    
98
		String representation;
99

    
100
		private DerivedUnitType(String representation) {
101
			this.representation = representation;
102
		}
103

    
104
		/**
105
		 * @return the representation
106
		 */
107
		public String getRepresentation() {
108
			return representation;
109
		}
110

    
111
		public DerivedUnitBase<?> getNewDerivedUnitInstance() {
112
			if (this == DerivedUnitType.Specimen) {
113
				return eu.etaxonomy.cdm.model.occurrence.Specimen.NewInstance();
114
			} else if (this == DerivedUnitType.Observation) {
115
				return eu.etaxonomy.cdm.model.occurrence.Observation.NewInstance();
116
			} else if (this == DerivedUnitType.LivingBeing) {
117
				return eu.etaxonomy.cdm.model.occurrence.LivingBeing.NewInstance();
118
			} else if (this == DerivedUnitType.Fossil) {
119
				return eu.etaxonomy.cdm.model.occurrence.Fossil.NewInstance();
120
			} else if (this == DerivedUnitType.DerivedUnit) {
121
				return eu.etaxonomy.cdm.model.occurrence.DerivedUnit.NewInstance();
122
			} else if (this == DerivedUnitType.FieldObservation) {
123
				return null;
124
			} else {
125
				String message = "Unknown derived unit type %s";
126
				message = String.format(message, this.getRepresentation());
127
				throw new IllegalStateException(message);
128
			}
129
		}
130

    
131
		public static DerivedUnitType valueOf2(String type) {
132
			if (type == null) {
133
				return null;
134
			}
135
			type = type.replace(" ", "").toLowerCase();
136
			if (type.equals("specimen")) {
137
				return Specimen;
138
			} else if (type.equals("livingbeing")) {
139
				return LivingBeing;
140
			} else if (type.equals("observation")) {
141
				return Observation;
142
			} else if (type.equals("fossil")) {
143
				return Fossil;
144
			} else if (type.equals("fieldobservation")) {
145
				return DerivedUnitType.FieldObservation;
146
			} else if (type.equals("unknown")) {
147
				return DerivedUnitType.DerivedUnit;
148
			} else if (type.equals("derivedunit")) {
149
				return DerivedUnitType.DerivedUnit;
150
			}
151
			return null;
152
		}
153
		
154
		public static DerivedUnitType valueOf2(Class<? extends SpecimenOrObservationBase> clazz) {
155
			if (clazz == null) {
156
				return null;
157
			}
158
			if (clazz.equals(Specimen.class)) {
159
				return Specimen;
160
			} else if (clazz.equals(eu.etaxonomy.cdm.model.occurrence.LivingBeing.class)) {
161
				return LivingBeing;
162
			} else if (clazz.equals(eu.etaxonomy.cdm.model.occurrence.Observation.class)) {
163
				return Observation;
164
			} else if (clazz.equals(eu.etaxonomy.cdm.model.occurrence.Fossil.class)) {
165
				return Fossil;
166
			} else if (clazz.equals(FieldObservation.class)) {
167
				return DerivedUnitType.FieldObservation;
168
			} else if (clazz.equals(DerivedUnit.class)) {
169
				return DerivedUnitType.DerivedUnit;
170
			}
171
			return null;
172
		}
173

    
174
	}
175

    
176
	private final DerivedUnitFacadeConfigurator config;
177
	
178
	private Map<PropertyChangeListener, CdmBase> listeners = new HashMap<PropertyChangeListener, CdmBase>();
179

    
180
	// private GatheringEvent gatheringEvent;
181
	private DerivedUnitType type; // needed?
182

    
183
	private FieldObservation fieldObservation;
184

    
185
	private final DerivedUnitBase derivedUnit;
186

    
187
	// media - the text data holding the media
188
	private TextData derivedUnitMediaTextData;
189
	private TextData fieldObjectMediaTextData;
190

    
191
	private TextData ecology;
192
	private TextData plantDescription;
193

    
194
	/**
195
	 * Creates a derived unit facade for a new derived unit of type
196
	 * <code>type</code>.
197
	 * 
198
	 * @param type
199
	 * @return
200
	 */
201
	public static DerivedUnitFacade NewInstance(DerivedUnitType type) {
202
		return new DerivedUnitFacade(type, null, null);
203
	}
204
	
205
	/**
206
	 * Creates a derived unit facade for a new derived unit of type
207
	 * <code>type</code>.
208
	 * 
209
	 * @param type
210
	 * @return
211
	 */
212
	public static DerivedUnitFacade NewInstance(DerivedUnitType type, FieldObservation fieldObservation) {
213
		return new DerivedUnitFacade(type, fieldObservation, null);
214
	}
215

    
216
	/**
217
	 * Creates a derived unit facade for a new derived unit of type
218
	 * <code>type</code>.
219
	 * 
220
	 * @param type
221
	 * @param fieldObservation the field observation to use
222
	 * @param config the facade configurator to use
223
	 * //TODO are there any ambiguities to solve with defining a field observation or a configurator 
224
	 * @return
225
	 */
226
	public static DerivedUnitFacade NewInstance(DerivedUnitType type, FieldObservation fieldObservation, DerivedUnitFacadeConfigurator config) {
227
		return new DerivedUnitFacade(type, fieldObservation, config);
228
	}
229

    
230
	
231
	/**
232
	 * Creates a derived unit facade for a given derived unit using the default
233
	 * configuration.
234
	 * 
235
	 * @param derivedUnit
236
	 * @return
237
	 * @throws DerivedUnitFacadeNotSupportedException
238
	 */
239
	public static DerivedUnitFacade NewInstance(DerivedUnitBase derivedUnit)
240
			throws DerivedUnitFacadeNotSupportedException {
241
		return new DerivedUnitFacade(derivedUnit, null);
242
	}
243

    
244
	public static DerivedUnitFacade NewInstance(DerivedUnitBase derivedUnit,
245
			DerivedUnitFacadeConfigurator config)
246
			throws DerivedUnitFacadeNotSupportedException {
247
		return new DerivedUnitFacade(derivedUnit, config);
248
	}
249

    
250
	// ****************** CONSTRUCTOR ******************************************
251

    
252
	private DerivedUnitFacade(DerivedUnitType type, FieldObservation fieldObservation, DerivedUnitFacadeConfigurator config) {
253
		if (config == null){
254
			config = DerivedUnitFacadeConfigurator.NewInstance();
255
		}
256
		this.config = config;
257
		this.type = type;
258
		// derivedUnit
259
		derivedUnit = type.getNewDerivedUnitInstance();
260
		setFieldObservation(fieldObservation);
261
		if (derivedUnit != null){
262
			setCacheStrategy();
263
		}else{
264
			setFieldObservationCacheStrategy();
265
		}
266
	}
267

    
268
	private DerivedUnitFacade(DerivedUnitBase derivedUnit,
269
			DerivedUnitFacadeConfigurator config)
270
			throws DerivedUnitFacadeNotSupportedException {
271

    
272
		if (config == null) {
273
			config = DerivedUnitFacadeConfigurator.NewInstance();
274
		}
275
		this.config = config;
276

    
277
		// derived unit
278
		this.derivedUnit = derivedUnit;
279
		this.type = DerivedUnitType.valueOf2(this.derivedUnit.getClass());
280
		
281
		// derivation event
282
		if (this.derivedUnit.getDerivedFrom() != null) {
283
			DerivationEvent derivationEvent = getDerivationEvent(CREATE);
284
			// fieldObservation
285
			Set<FieldObservation> fieldOriginals = getFieldObservationsOriginals(
286
					derivationEvent, null);
287
			if (fieldOriginals.size() > 1) {
288
				throw new DerivedUnitFacadeNotSupportedException(
289
						"Specimen must not have more than 1 derivation event");
290
			} else if (fieldOriginals.size() == 0) {
291
				// fieldObservation = FieldObservation.NewInstance();
292
			} else if (fieldOriginals.size() == 1) {
293
				fieldObservation = fieldOriginals.iterator().next();
294
				// ###fieldObservation =
295
				// getInitializedFieldObservation(fieldObservation);
296
				if (config.isFirePropertyChangeEvents()){
297
					addNewEventPropagationListener(fieldObservation);
298
				}
299
			} else {
300
				throw new IllegalStateException("Illegal state");
301
			}
302
		}
303
		
304
		this.derivedUnitMediaTextData = inititializeTextDataWithSupportTest(
305
				Feature.IMAGE(), this.derivedUnit, false, true);
306

    
307
		fieldObjectMediaTextData = initializeFieldObjectTextDataWithSupportTest(
308
				Feature.IMAGE(), false, true);
309

    
310
		// handle derivedUnit.getMedia()
311
		if (derivedUnit.getMedia().size() > 0) {
312
			// TODO better changed model here to allow only one place for images
313
			if (this.config.isMoveDerivedUnitMediaToGallery()) {
314
				Set<Media> mediaSet = derivedUnit.getMedia();
315
				for (Media media : mediaSet) {
316
					this.addDerivedUnitMedia(media);
317
				}
318
				mediaSet.removeAll(getDerivedUnitMedia());
319
			} else {
320
				throw new DerivedUnitFacadeNotSupportedException(
321
						"Specimen may not have direct media. Only (one) image gallery is allowed");
322
			}
323
		}
324

    
325
		// handle fieldObservation.getMedia()
326
		if (fieldObservation != null && fieldObservation.getMedia() != null
327
				&& fieldObservation.getMedia().size() > 0) {
328
			// TODO better changed model here to allow only one place for images
329
			if (this.config.isMoveFieldObjectMediaToGallery()) {
330
				Set<Media> mediaSet = fieldObservation.getMedia();
331
				for (Media media : mediaSet) {
332
					this.addFieldObjectMedia(media);
333
				}
334
				mediaSet.removeAll(getFieldObjectMedia());
335
			} else {
336
				throw new DerivedUnitFacadeNotSupportedException(
337
						"Field object may not have direct media. Only (one) image gallery is allowed");
338
			}
339
		}
340

    
341
		// test if descriptions are supported
342
		ecology = initializeFieldObjectTextDataWithSupportTest(
343
				Feature.ECOLOGY(), false, false);
344
		plantDescription = initializeFieldObjectTextDataWithSupportTest(
345
				Feature.DESCRIPTION(), false, false);
346
		
347
		setCacheStrategy();
348

    
349
	}
350

    
351
	private DerivedUnitBase getInitializedDerivedUnit(
352
			DerivedUnitBase derivedUnit) {
353
		IOccurrenceService occurrenceService = this.config
354
				.getOccurrenceService();
355
		if (occurrenceService == null) {
356
			return derivedUnit;
357
		}
358
		List<String> propertyPaths = this.config.getPropertyPaths();
359
		if (propertyPaths == null) {
360
			return derivedUnit;
361
		}
362
		propertyPaths = getDerivedUnitPropertyPaths(propertyPaths);
363
		DerivedUnitBase result = (DerivedUnitBase) occurrenceService.load(
364
				derivedUnit.getUuid(), propertyPaths);
365
		return result;
366
	}
367

    
368
	/**
369
	 * Initializes the derived unit according to the configuartions property
370
	 * path. If the property path is <code>null</code> or no occurrence service
371
	 * is given the returned object is the same as the input parameter.
372
	 * 
373
	 * @param fieldObservation2
374
	 * @return
375
	 */
376
	private FieldObservation getInitializedFieldObservation(
377
			FieldObservation fieldObservation) {
378
		IOccurrenceService occurrenceService = this.config
379
				.getOccurrenceService();
380
		if (occurrenceService == null) {
381
			return fieldObservation;
382
		}
383
		List<String> propertyPaths = this.config.getPropertyPaths();
384
		if (propertyPaths == null) {
385
			return fieldObservation;
386
		}
387
		propertyPaths = getFieldObjectPropertyPaths(propertyPaths);
388
		FieldObservation result = (FieldObservation) occurrenceService.load(
389
				fieldObservation.getUuid(), propertyPaths);
390
		return result;
391
	}
392

    
393
	/**
394
	 * Transforms the property paths in a way that the facade is handled just
395
	 * like an ordinary CdmBase object.<BR>
396
	 * E.g. a property path "collectinAreas" will be translated into
397
	 * gatheringEvent.collectingAreas
398
	 * 
399
	 * @param propertyPaths
400
	 * @return
401
	 */
402
	private List<String> getFieldObjectPropertyPaths(List<String> propertyPaths) {
403
		List<String> result = new ArrayList<String>();
404
		for (String facadePath : propertyPaths) {
405
			// collecting areas (named area)
406
			if (facadePath.startsWith("collectingAreas")) {
407
				facadePath = "gatheringEvent." + facadePath;
408
				result.add(facadePath);
409
			}
410
			// collector (agentBase)
411
			else if (facadePath.startsWith("collector")) {
412
				facadePath = facadePath.replace("collector",
413
						"gatheringEvent.actor");
414
				result.add(facadePath);
415
			}
416
			// exactLocation (agentBase)
417
			else if (facadePath.startsWith("exactLocation")) {
418
				facadePath = "gatheringEvent." + facadePath;
419
				result.add(facadePath);
420
			}
421
			// gatheringPeriod (TimePeriod)
422
			else if (facadePath.startsWith("gatheringPeriod")) {
423
				facadePath = facadePath.replace("gatheringPeriod",
424
						"gatheringEvent.timeperiod");
425
				result.add(facadePath);
426
			}
427
			// (locality/ localityLanguage , LanguageString)
428
			else if (facadePath.startsWith("locality")) {
429
				facadePath = "gatheringEvent." + facadePath;
430
				result.add(facadePath);
431
			}
432

    
433
			// *********** FIELD OBJECT ************
434
			// fieldObjectDefinitions (Map<language, languageString)
435
			else if (facadePath.startsWith("fieldObjectDefinitions")) {
436
				// TODO or definition ???
437
				facadePath = facadePath.replace("fieldObjectDefinitions",
438
						"description");
439
				result.add(facadePath);
440
			}
441
			// fieldObjectMedia (Media)
442
			else if (facadePath.startsWith("fieldObjectMedia")) {
443
				// TODO ???
444
				facadePath = facadePath.replace("fieldObjectMedia",
445
						"descriptions.elements.media");
446
				result.add(facadePath);
447
			}
448

    
449
			// Gathering Event will always be added
450
			result.add("gatheringEvent");
451

    
452
		}
453

    
454
		/*
455
		 * Gathering Event ==================== - gatheringEvent
456
		 * (GatheringEvent)
457
		 * 
458
		 * Field Object ================= - ecology/ ecologyAll (String) ??? -
459
		 * plant description (like ecology)
460
		 * 
461
		 * - fieldObjectImageGallery (SpecimenDescription) - is automatically
462
		 * initialized via fieldObjectMedia
463
		 */
464

    
465
		return result;
466
	}
467

    
468
	/**
469
	 * Transforms the property paths in a way that the facade is handled just
470
	 * like an ordinary CdmBase object.<BR>
471
	 * E.g. a property path "collectinAreas" will be translated into
472
	 * gatheringEvent.collectingAreas
473
	 * 
474
	 * Not needed (?) as the facade works with REST service property paths
475
	 * without using this method.
476
	 * 
477
	 * @param propertyPaths
478
	 * @return
479
	 */
480
	private List<String> getDerivedUnitPropertyPaths(List<String> propertyPaths) {
481
		List<String> result = new ArrayList<String>();
482
		for (String facadePath : propertyPaths) {
483
			// determinations (DeterminationEvent)
484
			if (facadePath.startsWith("determinations")) {
485
				facadePath = "" + facadePath; // no change
486
				result.add(facadePath);
487
			}
488
			// storedUnder (TaxonNameBase)
489
			else if (facadePath.startsWith("storedUnder")) {
490
				facadePath = "" + facadePath; // no change
491
				result.add(facadePath);
492
			}
493
			// sources (IdentifiableSource)
494
			else if (facadePath.startsWith("sources")) {
495
				facadePath = "" + facadePath; // no change
496
				result.add(facadePath);
497
			}
498
			// collection (Collection)
499
			else if (facadePath.startsWith("collection")) {
500
				facadePath = "" + facadePath; // no change
501
				result.add(facadePath);
502
			}
503
			// (locality/ localityLanguage , LanguageString)
504
			else if (facadePath.startsWith("locality")) {
505
				facadePath = "gatheringEvent." + facadePath;
506
				result.add(facadePath);
507
			}
508

    
509
			// *********** FIELD OBJECT ************
510
			// derivedUnitDefinitions (Map<language, languageString)
511
			else if (facadePath.startsWith("derivedUnitDefinitions")) {
512
				// TODO or definition ???
513
				facadePath = facadePath.replace("derivedUnitDefinitions",
514
						"description");
515
				result.add(facadePath);
516
			}
517

    
518
			// derivedUnitMedia (Media)
519
			else if (facadePath.startsWith("derivedUnitMedia")) {
520
				// TODO ???
521
				facadePath = facadePath.replace("derivedUnitMedia",
522
						"descriptions.elements.media");
523
				result.add(facadePath);
524
			}
525

    
526
		}
527

    
528
		/*
529
		 * //TODO Derived Unit =====================
530
		 * 
531
		 * - derivedUnitImageGallery (SpecimenDescription) - is automatically
532
		 * initialized via derivedUnitMedia
533
		 * 
534
		 * - derivationEvent (DerivationEvent) - will always be initialized -
535
		 * duplicates (??? Specimen???) ???
536
		 */
537

    
538
		return result;
539
	}
540

    
541
	/**
542
	 * 
543
	 */
544
	private void setCacheStrategy() {
545
		if (derivedUnit == null) {
546
			throw new NullPointerException(
547
					"Facade's derviedUnit must not be null to set cache strategy");
548
		}else{
549
			derivedUnit.setCacheStrategy(new DerivedUnitFacadeCacheStrategy());
550
			setFieldObservationCacheStrategy();
551
		}
552
	}
553

    
554
	private void setFieldObservationCacheStrategy() {
555
		if (this.hasFieldObject()){
556
			DerivedUnitFacadeFieldObservationCacheStrategy strategy = new DerivedUnitFacadeFieldObservationCacheStrategy();
557
			this.fieldObservation.setCacheStrategy(strategy);
558
		}
559
	}
560

    
561
	/**
562
	 * @param feature
563
	 * @param createIfNotExists
564
	 * @param isImageGallery
565
	 * @return
566
	 * @throws DerivedUnitFacadeNotSupportedException
567
	 */
568
	private TextData initializeFieldObjectTextDataWithSupportTest(
569
			Feature feature, boolean createIfNotExists, boolean isImageGallery)
570
			throws DerivedUnitFacadeNotSupportedException {
571
		// field object
572
		FieldObservation fieldObject = getFieldObservation(createIfNotExists);
573
		if (fieldObject == null) {
574
			return null;
575
		}
576
		return inititializeTextDataWithSupportTest(feature, fieldObject,
577
				createIfNotExists, isImageGallery);
578
	}
579

    
580
	/**
581
	 * @param feature
582
	 * @param specimen
583
	 * @param createIfNotExists
584
	 * @param isImageGallery
585
	 * @return
586
	 * @throws DerivedUnitFacadeNotSupportedException
587
	 */
588
	private TextData inititializeTextDataWithSupportTest(Feature feature,
589
			SpecimenOrObservationBase specimen, boolean createIfNotExists,
590
			boolean isImageGallery)
591
			throws DerivedUnitFacadeNotSupportedException {
592
		if (feature == null) {
593
			return null;
594
		}
595
		TextData textData = null;
596
		if (createIfNotExists) {
597
			textData = TextData.NewInstance(feature);
598
		}
599

    
600
		Set<SpecimenDescription> descriptions;
601
		if (isImageGallery) {
602
			descriptions = specimen.getSpecimenDescriptionImageGallery();
603
		} else {
604
			descriptions = specimen.getSpecimenDescriptions(false);
605
		}
606
		// no description exists yet for this specimen
607
		if (descriptions.size() == 0) {
608
			if (createIfNotExists) {
609
				SpecimenDescription newSpecimenDescription = SpecimenDescription
610
						.NewInstance(specimen);
611
				newSpecimenDescription.addElement(textData);
612
				newSpecimenDescription.setImageGallery(isImageGallery);
613
				return textData;
614
			} else {
615
				return null;
616
			}
617
		}
618
		// description already exists
619
		Set<DescriptionElementBase> existingTextData = new HashSet<DescriptionElementBase>();
620
		for (SpecimenDescription description : descriptions) {
621
			// collect all existing text data
622
			for (DescriptionElementBase element : description.getElements()) {
623
				if (element.isInstanceOf(TextData.class)
624
						&& (feature.equals(element.getFeature()) || isImageGallery)) {
625
					existingTextData.add(element);
626
				}
627
			}
628
		}
629
		// use existing text data if exactly one exists
630
		if (existingTextData.size() > 1) {
631
			throw new DerivedUnitFacadeNotSupportedException(
632
					"Specimen facade does not support more than one description text data of type "
633
							+ feature.getLabel());
634

    
635
		} else if (existingTextData.size() == 1) {
636
			return CdmBase.deproxy(existingTextData.iterator().next(),
637
					TextData.class);
638
		} else {
639
			if (createIfNotExists) {
640
				SpecimenDescription description = descriptions.iterator()
641
						.next();
642
				description.addElement(textData);
643
			}
644
			return textData;
645
		}
646
	}
647

    
648
	/**
649
	 * Tests if a given image gallery is supported by the derived unit facade.
650
	 * It returns the only text data attached to the given image gallery. If the
651
	 * given image gallery does not have text data attached, it is created and
652
	 * attached.
653
	 * 
654
	 * @param imageGallery
655
	 * @return
656
	 * @throws DerivedUnitFacadeNotSupportedException
657
	 */
658
	private TextData testImageGallery(SpecimenDescription imageGallery)
659
			throws DerivedUnitFacadeNotSupportedException {
660
		if (imageGallery.isImageGallery() == false) {
661
			throw new DerivedUnitFacadeNotSupportedException(
662
					"Image gallery needs to have image gallery flag set");
663
		}
664
		if (imageGallery.getElements().size() > 1) {
665
			throw new DerivedUnitFacadeNotSupportedException(
666
					"Image gallery must not have more then one description element");
667
		}
668
		TextData textData;
669
		if (imageGallery.getElements().size() == 0) {
670
			textData = TextData.NewInstance(Feature.IMAGE());
671
			imageGallery.addElement(textData);
672
		} else {
673
			if (!imageGallery.getElements().iterator().next()
674
					.isInstanceOf(TextData.class)) {
675
				throw new DerivedUnitFacadeNotSupportedException(
676
						"Image gallery must only have TextData as element");
677
			} else {
678
				textData = CdmBase.deproxy(imageGallery.getElements()
679
						.iterator().next(), TextData.class);
680
			}
681
		}
682
		return textData;
683
	}
684

    
685
	// ************************** METHODS
686
	// *****************************************
687

    
688
	private TextData getDerivedUnitImageGalleryTextData(
689
			boolean createIfNotExists)
690
			throws DerivedUnitFacadeNotSupportedException {
691
		if (this.derivedUnitMediaTextData == null && createIfNotExists) {
692
			this.derivedUnitMediaTextData = getImageGalleryTextData(
693
					derivedUnit, "Specimen");
694
		}
695
		return this.derivedUnitMediaTextData;
696
	}
697

    
698
	private TextData getObservationImageGalleryTextData(
699
			boolean createIfNotExists)
700
			throws DerivedUnitFacadeNotSupportedException {
701
		if (this.fieldObjectMediaTextData == null && createIfNotExists) {
702
			this.fieldObjectMediaTextData = getImageGalleryTextData(
703
					fieldObservation, "Field observation");
704
		}
705
		return this.fieldObjectMediaTextData;
706
	}
707

    
708
	/**
709
	 * @param derivationEvent
710
	 * @return
711
	 * @throws DerivedUnitFacadeNotSupportedException
712
	 */
713
	private Set<FieldObservation> getFieldObservationsOriginals(
714
			DerivationEvent derivationEvent,
715
			Set<SpecimenOrObservationBase> recursionAvoidSet)
716
			throws DerivedUnitFacadeNotSupportedException {
717
		if (recursionAvoidSet == null) {
718
			recursionAvoidSet = new HashSet<SpecimenOrObservationBase>();
719
		}
720
		Set<FieldObservation> result = new HashSet<FieldObservation>();
721
		Set<SpecimenOrObservationBase> originals = derivationEvent.getOriginals();
722
		for (SpecimenOrObservationBase original : originals) {
723
			if (original.isInstanceOf(FieldObservation.class)) {
724
				result.add(CdmBase.deproxy(original, FieldObservation.class));
725
			} else if (original.isInstanceOf(DerivedUnitBase.class)) {
726
				// if specimen has already been tested exclude it from further
727
				// recursion
728
				if (recursionAvoidSet.contains(original)) {
729
					continue;
730
				}
731
				DerivedUnitBase derivedUnit = CdmBase.deproxy(original,
732
						DerivedUnitBase.class);
733
				DerivationEvent originalDerivation = derivedUnit.getDerivedFrom();
734
				// Set<DerivationEvent> derivationEvents =
735
				// original.getDerivationEvents();
736
				// for (DerivationEvent originalDerivation : derivationEvents){
737
				Set<FieldObservation> fieldObservations = getFieldObservationsOriginals(
738
						originalDerivation, recursionAvoidSet);
739
				result.addAll(fieldObservations);
740
				// }
741
			} else {
742
				throw new DerivedUnitFacadeNotSupportedException(
743
						"Unhandled specimen or observation base type: "
744
								+ original.getClass().getName());
745
			}
746

    
747
		}
748
		return result;
749
	}
750

    
751
	// *********** MEDIA METHODS ******************************
752

    
753
	// /**
754
	// * Returns the media list for a specimen. Throws an exception if the
755
	// existing specimen descriptions
756
	// * are not supported by this facade.
757
	// * @param specimen the specimen the media belongs to
758
	// * @param specimenExceptionText text describing the specimen for exception
759
	// messages
760
	// * @return
761
	// * @throws DerivedUnitFacadeNotSupportedException
762
	// */
763
	// private List<Media> getImageGalleryMedia(SpecimenOrObservationBase
764
	// specimen, String specimenExceptionText) throws
765
	// DerivedUnitFacadeNotSupportedException{
766
	// List<Media> result;
767
	// SpecimenDescription imageGallery =
768
	// getImageGalleryWithSupportTest(specimen, specimenExceptionText, true);
769
	// TextData textData = getImageTextDataWithSupportTest(imageGallery,
770
	// specimenExceptionText);
771
	// result = textData.getMedia();
772
	// return result;
773
	// }
774

    
775
	/**
776
	 * Returns the media list for a specimen. Throws an exception if the
777
	 * existing specimen descriptions are not supported by this facade.
778
	 * 
779
	 * @param specimen
780
	 *            the specimen the media belongs to
781
	 * @param specimenExceptionText
782
	 *            text describing the specimen for exception messages
783
	 * @return
784
	 * @throws DerivedUnitFacadeNotSupportedException
785
	 */
786
	private TextData getImageGalleryTextData(SpecimenOrObservationBase specimen, String specimenExceptionText)
787
			throws DerivedUnitFacadeNotSupportedException {
788
		TextData result;
789
		SpecimenDescription imageGallery = getImageGalleryWithSupportTest(
790
				specimen, specimenExceptionText, true);
791
		result = getImageTextDataWithSupportTest(imageGallery,
792
				specimenExceptionText);
793
		return result;
794
	}
795

    
796
	/**
797
	 * Returns the image gallery of the according specimen. Throws an exception
798
	 * if the attached image gallerie(s) are not supported by this facade. If no
799
	 * image gallery exists a new one is created if
800
	 * <code>createNewIfNotExists</code> is true and if specimen is not
801
	 * <code>null</code>.
802
	 * 
803
	 * @param specimen
804
	 * @param specimenText
805
	 * @param createNewIfNotExists
806
	 * @return
807
	 * @throws DerivedUnitFacadeNotSupportedException
808
	 */
809
	private SpecimenDescription getImageGalleryWithSupportTest(
810
			SpecimenOrObservationBase<?> specimen, String specimenText,
811
			boolean createNewIfNotExists)
812
			throws DerivedUnitFacadeNotSupportedException {
813
		if (specimen == null) {
814
			return null;
815
		}
816
		SpecimenDescription imageGallery;
817
		if (hasMultipleImageGalleries(specimen)) {
818
			throw new DerivedUnitFacadeNotSupportedException(specimenText
819
					+ " must not have more than 1 image gallery");
820
		} else {
821
			imageGallery = getImageGallery(specimen, createNewIfNotExists);
822
			getImageTextDataWithSupportTest(imageGallery, specimenText);
823
		}
824
		return imageGallery;
825
	}
826

    
827
	/**
828
	 * Returns the media holding text data element of the image gallery. Throws
829
	 * an exception if multiple such text data already exist. Creates a new text
830
	 * data if none exists and adds it to the image gallery. If image gallery is
831
	 * <code>null</code> nothing happens.
832
	 * 
833
	 * @param imageGallery
834
	 * @param textData
835
	 * @return
836
	 * @throws DerivedUnitFacadeNotSupportedException
837
	 */
838
	private TextData getImageTextDataWithSupportTest(
839
			SpecimenDescription imageGallery, String specimenText)
840
			throws DerivedUnitFacadeNotSupportedException {
841
		if (imageGallery == null) {
842
			return null;
843
		}
844
		TextData textData = null;
845
		for (DescriptionElementBase element : imageGallery.getElements()) {
846
			if (element.isInstanceOf(TextData.class)
847
					&& element.getFeature().equals(Feature.IMAGE())) {
848
				if (textData != null) {
849
					throw new DerivedUnitFacadeNotSupportedException(
850
							specimenText
851
									+ " must not have more than 1 image text data element in image gallery");
852
				}
853
				textData = CdmBase.deproxy(element, TextData.class);
854
			}
855
		}
856
		if (textData == null) {
857
			textData = TextData.NewInstance(Feature.IMAGE());
858
			imageGallery.addElement(textData);
859
		}
860
		return textData;
861
	}
862

    
863
	/**
864
	 * Checks, if a specimen belongs to more than one description that is an
865
	 * image gallery
866
	 * 
867
	 * @param derivedUnit
868
	 * @return
869
	 */
870
	private boolean hasMultipleImageGalleries(
871
			SpecimenOrObservationBase<?> derivedUnit) {
872
		int count = 0;
873
		Set<SpecimenDescription> descriptions = derivedUnit
874
				.getSpecimenDescriptions();
875
		for (SpecimenDescription description : descriptions) {
876
			if (description.isImageGallery()) {
877
				count++;
878
			}
879
		}
880
		return (count > 1);
881
	}
882

    
883
	/**
884
	 * Returns the image gallery for a specimen. If there are multiple specimen
885
	 * descriptions marked as image galleries an arbitrary one is chosen. If no
886
	 * image gallery exists, a new one is created if
887
	 * <code>createNewIfNotExists</code> is <code>true</code>.<Br>
888
	 * If specimen is <code>null</code> a null pointer exception is thrown.
889
	 * 
890
	 * @param createNewIfNotExists
891
	 * @return
892
	 */
893
	private SpecimenDescription getImageGallery(SpecimenOrObservationBase<?> specimen, boolean createIfNotExists) {
894
		SpecimenDescription result = null;
895
		Set<SpecimenDescription> descriptions = specimen.getSpecimenDescriptions();
896
		for (SpecimenDescription description : descriptions) {
897
			if (description.isImageGallery()) {
898
				result = description;
899
				break;
900
			}
901
		}
902
		if (result == null && createIfNotExists) {
903
			result = SpecimenDescription.NewInstance(specimen);
904
			result.setImageGallery(true);
905
		}
906
		return result;
907
	}
908

    
909
	/**
910
	 * Adds a media to the specimens image gallery. If media is
911
	 * <code>null</code> nothing happens.
912
	 * 
913
	 * @param media
914
	 * @param specimen
915
	 * @return true if media is not null (as specified by
916
	 *         {@link java.util.Collection#add(Object) Collection.add(E e)}
917
	 * @throws DerivedUnitFacadeNotSupportedException
918
	 */
919
	private boolean addMedia(Media media, SpecimenOrObservationBase<?> specimen) throws DerivedUnitFacadeNotSupportedException {
920
		if (media != null) {
921
			List<Media> mediaList = getMediaList(specimen, true);
922
			if (! mediaList.contains(media)){
923
				return mediaList.add(media);
924
			}else{
925
				return true;
926
			}
927
		} else {
928
			return false;
929
		}
930
	}
931

    
932
	/**
933
	 * Removes a media from the specimens image gallery.
934
	 * 
935
	 * @param media
936
	 * @param specimen
937
	 * @return true if an element was removed as a result of this call (as
938
	 *         specified by {@link java.util.Collection#remove(Object)
939
	 *         Collection.remove(E e)}
940
	 * @throws DerivedUnitFacadeNotSupportedException
941
	 */
942
	private boolean removeMedia(Media media,
943
			SpecimenOrObservationBase<?> specimen)
944
			throws DerivedUnitFacadeNotSupportedException {
945
		List<Media> mediaList = getMediaList(specimen, true);
946
		return mediaList == null ? null : mediaList.remove(media);
947
	}
948

    
949
	private List<Media> getMediaList(SpecimenOrObservationBase<?> specimen, boolean createIfNotExists)
950
			throws DerivedUnitFacadeNotSupportedException {
951
		TextData textData = getMediaTextData(specimen, createIfNotExists);
952
		return textData == null ? null : textData.getMedia();
953
	}
954

    
955
	/**
956
	 * Returns the one media list of a specimen which is part of the only image
957
	 * gallery that this specimen is part of.<BR>
958
	 * If these conditions are not hold an exception is thrwon.
959
	 * 
960
	 * @param specimen
961
	 * @return
962
	 * @throws DerivedUnitFacadeNotSupportedException
963
	 */
964
	// private List<Media> getMedia(SpecimenOrObservationBase<?> specimen)
965
	// throws DerivedUnitFacadeNotSupportedException {
966
	// if (specimen == null){
967
	// return null;
968
	// }
969
	// if (specimen == this.derivedUnit){
970
	// return getDerivedUnitImageGalleryMedia();
971
	// }else if (specimen == this.fieldObservation){
972
	// return getObservationImageGalleryTextData();
973
	// }else{
974
	// return getImageGalleryMedia(specimen, "Undefined specimen ");
975
	// }
976
	// }
977

    
978
	/**
979
	 * Returns the one media list of a specimen which is part of the only image
980
	 * gallery that this specimen is part of.<BR>
981
	 * If these conditions are not hold an exception is thrwon.
982
	 * 
983
	 * @param specimen
984
	 * @return
985
	 * @throws DerivedUnitFacadeNotSupportedException
986
	 */
987
	private TextData getMediaTextData(SpecimenOrObservationBase<?> specimen,
988
			boolean createIfNotExists)
989
			throws DerivedUnitFacadeNotSupportedException {
990
		if (specimen == null) {
991
			return null;
992
		}
993
		if (specimen == this.derivedUnit) {
994
			return getDerivedUnitImageGalleryTextData(createIfNotExists);
995
		} else if (specimen == this.fieldObservation) {
996
			return getObservationImageGalleryTextData(createIfNotExists);
997
		} else {
998
			return getImageGalleryTextData(specimen, "Undefined specimen ");
999
		}
1000
	}
1001

    
1002
	// ****************** GETTER / SETTER / ADDER / REMOVER
1003
	// ***********************/
1004

    
1005
	// ****************** Gathering Event *********************************/
1006

    
1007
	// country
1008
	@Transient
1009
	public NamedArea getCountry() {
1010
		return (hasGatheringEvent() ? getGatheringEvent(true).getCountry()
1011
				: null);
1012
	}
1013

    
1014
	public void setCountry(NamedArea country) {
1015
		getGatheringEvent(true).setCountry(country);
1016
	}
1017

    
1018
	// Collecting area
1019
	public void addCollectingArea(NamedArea area) {
1020
		getGatheringEvent(true).addCollectingArea(area);
1021
	}
1022

    
1023
	public void addCollectingAreas(java.util.Collection<NamedArea> areas) {
1024
		for (NamedArea area : areas) {
1025
			getGatheringEvent(true).addCollectingArea(area);
1026
		}
1027
	}
1028

    
1029
	@Transient
1030
	public Set<NamedArea> getCollectingAreas() {
1031
		return (hasGatheringEvent() ? getGatheringEvent(true)
1032
				.getCollectingAreas() : null);
1033
	}
1034

    
1035
	public void removeCollectingArea(NamedArea area) {
1036
		if (hasGatheringEvent()) {
1037
			getGatheringEvent(true).removeCollectingArea(area);
1038
		}
1039
	}
1040

    
1041
	/**
1042
	 * Returns the correctly formatted <code>absolute elevation</code> information.
1043
	 * If absoluteElevationText is set, this will be returned,
1044
	 * otherwise we absoluteElevation will be returned, followed by absoluteElevationMax 
1045
	 * if existing, separated by " - " 
1046
	 * @return
1047
	 */
1048
	@Transient
1049
	public String absoluteElevationToString() {
1050
		if (! hasGatheringEvent()){
1051
			return null;
1052
		}else{
1053
			GatheringEvent ev = getGatheringEvent(true);
1054
			if (StringUtils.isNotBlank(ev.getAbsoluteElevationText())){
1055
				return ev.getAbsoluteElevationText();
1056
			}else{
1057
				String text = ev.getAbsoluteElevationText();
1058
				Integer min = getAbsoluteElevation();
1059
				Integer max = getAbsoluteElevationMaximum();
1060
				return distanceString(min, max, text);
1061
			}
1062
		}
1063
	}
1064

    
1065
	
1066
	/**
1067
	 * meter above/below sea level of the surface
1068
	 * 
1069
	 * @see #getAbsoluteElevationError()
1070
	 * @see #getAbsoluteElevationRange()
1071
	 **/
1072
	@Transient
1073
	public Integer getAbsoluteElevation() {
1074
		return (hasGatheringEvent() ? getGatheringEvent(true).getAbsoluteElevation() : null);
1075
	}
1076

    
1077
	public void setAbsoluteElevation(Integer absoluteElevation) {
1078
		getGatheringEvent(true).setAbsoluteElevation(absoluteElevation);
1079
	}
1080

    
1081
	public void setAbsoluteElevationMax(Integer absoluteElevationMax) {
1082
		getGatheringEvent(true).setAbsoluteElevationMax(absoluteElevationMax);
1083
	}
1084
	
1085
	public void setAbsoluteElevationText(String absoluteElevationText) {
1086
		getGatheringEvent(true).setAbsoluteElevationText(absoluteElevationText);
1087
	}
1088
	
1089
	/**
1090
	 * @see #getAbsoluteElevation()
1091
	 * @see #getAbsoluteElevationError()
1092
	 * @see #setAbsoluteElevationRange(Integer, Integer)
1093
	 * @see #getAbsoluteElevationMinimum()
1094
	 */
1095
	@Transient
1096
	public Integer getAbsoluteElevationMaximum() {
1097
		if (!hasGatheringEvent()) {
1098
			return null;
1099
		}else{
1100
			return getGatheringEvent(true).getAbsoluteElevationMax();
1101
		}
1102
	}
1103
	
1104
	/**
1105
	 * @see #getAbsoluteElevation()
1106
	 * @see #getAbsoluteElevationError()
1107
	 * @see #setAbsoluteElevationRange(Integer, Integer)
1108
	 * @see #getAbsoluteElevationMinimum()
1109
	 */
1110
	@Transient
1111
	public String getAbsoluteElevationText() {
1112
		if (!hasGatheringEvent()) {
1113
			return null;
1114
		}else{
1115
			return getGatheringEvent(true).getAbsoluteElevationText();
1116
		}
1117
	}
1118

    
1119
	/**
1120
	 * Convenience method to set absolute elevation minimum and maximum.
1121
	 * 
1122
	 * @see #setAbsoluteElevation(Integer)
1123
	 * @see #setAbsoluteElevationMax(Integer)
1124
	 * @param minimumElevation minimum of the range
1125
	 * @param maximumElevation maximum of the range
1126
	 */
1127
	public void setAbsoluteElevationRange(Integer minimumElevation, Integer maximumElevation) {
1128
		getGatheringEvent(true).setAbsoluteElevation(minimumElevation);
1129
		getGatheringEvent(true).setAbsoluteElevationMax(maximumElevation);
1130
	}
1131

    
1132
	// collector
1133
	@Transient
1134
	public AgentBase getCollector() {
1135
		return (hasGatheringEvent() ? getGatheringEvent(true).getCollector()
1136
				: null);
1137
	}
1138

    
1139
	public void setCollector(AgentBase collector) {
1140
		getGatheringEvent(true).setCollector(collector);
1141
	}
1142

    
1143
	// collecting method
1144
	@Transient
1145
	public String getCollectingMethod() {
1146
		return (hasGatheringEvent() ? getGatheringEvent(true).getCollectingMethod() : null);
1147
	}
1148

    
1149
	public void setCollectingMethod(String collectingMethod) {
1150
		getGatheringEvent(true).setCollectingMethod(collectingMethod);
1151
	}
1152

    
1153
	// distance to ground
1154
	
1155
	/**
1156
	 * Returns the correctly formatted <code>distance to ground</code> information.
1157
	 * If distanceToGroundText is not blank, it will be returned,
1158
	 * otherwise distanceToGround will be returned, followed by distanceToGroundMax 
1159
	 * if existing, separated by " - " 
1160
	 * @return
1161
	 */
1162
	@Transient
1163
	public String distanceToGroundToString() {
1164
		if (! hasGatheringEvent()){
1165
			return null;
1166
		}else{
1167
			GatheringEvent ev = getGatheringEvent(true);
1168
			String text = ev.getDistanceToGroundText();
1169
			Double min = getDistanceToGround();
1170
			Double max = getDistanceToGroundMax();
1171
			return distanceString(min, max, text);
1172
		}
1173
	}
1174

    
1175
	@Transient
1176
	public Double getDistanceToGround() {
1177
		return (hasGatheringEvent() ? getGatheringEvent(true).getDistanceToGround() : null);
1178
	}
1179

    
1180
	public void setDistanceToGround(Double distanceToGround) {
1181
		getGatheringEvent(true).setDistanceToGround(distanceToGround);
1182
	}
1183
	
1184
	/**
1185
	 * @see #getDistanceToGround()
1186
	 * @see #getDistanceToGroundRange(Integer, Integer)
1187
	 */
1188
	@Transient
1189
	public Double getDistanceToGroundMax() {
1190
		if (!hasGatheringEvent()) {
1191
			return null;
1192
		}else{
1193
			return getGatheringEvent(true).getDistanceToGroundMax();
1194
		}
1195
	}
1196
	
1197
	public void setDistanceToGroundMax(Double distanceToGroundMax) {
1198
		getGatheringEvent(true).setDistanceToGroundMax(distanceToGroundMax);
1199
	}
1200
	
1201
	/**
1202
	 * @see #getDistanceToGround()
1203
	 * @see #setDistanceToGroundRange(Integer, Integer)
1204
	 */
1205
	@Transient
1206
	public String getDistanceToGroundText() {
1207
		if (!hasGatheringEvent()) {
1208
			return null;
1209
		}else{
1210
			return getGatheringEvent(true).getDistanceToGroundText();
1211
		}
1212
	}
1213
	public void setDistanceToGroundText(String distanceToGroundText) {
1214
		getGatheringEvent(true).setDistanceToGroundText(distanceToGroundText);
1215
	}
1216
	
1217
	/**
1218
	 * Convenience method to set distance to ground minimum and maximum.
1219
	 * 
1220
	 * @see #getDistanceToGround()
1221
	 * @see #getDistanceToGroundMax()
1222
	 * @param minimumDistance minimum of the range
1223
	 * @param maximumDistance maximum of the range
1224
	 */
1225
	public void setDistanceToGroundRange(Double minimumDistance, Double maximumDistance) throws IllegalArgumentException{
1226
		getGatheringEvent(true).setDistanceToGround(minimumDistance);
1227
		getGatheringEvent(true).setDistanceToGroundMax(maximumDistance);
1228
	}
1229
	
1230
	
1231
	/**
1232
	 * Returns the correctly formatted <code>distance to water surface</code> information.
1233
	 * If distanceToWaterSurfaceText is not blank, it will be returned,
1234
	 * otherwise distanceToWaterSurface will be returned, followed by distanceToWatersurfaceMax 
1235
	 * if existing, separated by " - " 
1236
	 * @return
1237
	 */
1238
	@Transient
1239
	public String distanceToWaterSurfaceToString() {
1240
		if (! hasGatheringEvent()){
1241
			return null;
1242
		}else{
1243
			GatheringEvent ev = getGatheringEvent(true);
1244
			String text = ev.getDistanceToWaterSurfaceText();
1245
			Double min = getDistanceToWaterSurface();
1246
			Double max = getDistanceToWaterSurfaceMax();
1247
			return distanceString(min, max, text);
1248
		}
1249
	}
1250
	
1251
	// distance to water surface
1252
	@Transient
1253
	public Double getDistanceToWaterSurface() {
1254
		return (hasGatheringEvent() ? getGatheringEvent(true).getDistanceToWaterSurface() : null);
1255
	}
1256

    
1257
	public void setDistanceToWaterSurface(Double distanceToWaterSurface) {
1258
		getGatheringEvent(true).setDistanceToWaterSurface(distanceToWaterSurface);
1259
	}
1260

    
1261
	/**
1262
	 * @see #getDistanceToWaterSurface()
1263
	 * @see #getDistanceToWaterSurfaceRange(Double, Double)
1264
	 */
1265
	@Transient
1266
	public Double getDistanceToWaterSurfaceMax() {
1267
		if (!hasGatheringEvent()) {
1268
			return null;
1269
		}else{
1270
			return getGatheringEvent(true).getDistanceToWaterSurfaceMax();
1271
		}
1272
	}
1273
	
1274
	public void setDistanceToWaterSurfaceMax(Double distanceToWaterSurfaceMax) {
1275
		getGatheringEvent(true).setDistanceToWaterSurfaceMax(distanceToWaterSurfaceMax);
1276
	}
1277
	
1278
	/**
1279
	 * @see #getDistanceToWaterSurface()
1280
	 * @see #getDistanceToWaterSurfaceRange(Double, Double)
1281
	 */
1282
	@Transient
1283
	public String getDistanceToWaterSurfaceText() {
1284
		if (!hasGatheringEvent()) {
1285
			return null;
1286
		}else{
1287
			return getGatheringEvent(true).getDistanceToWaterSurfaceText();
1288
		}
1289
	}
1290
	public void setDistanceToWaterSurfaceText(String distanceToWaterSurfaceText) {
1291
		getGatheringEvent(true).setDistanceToWaterSurfaceText(distanceToWaterSurfaceText);
1292
	}
1293
	
1294
	/**
1295
	 * Convenience method to set distance to ground minimum and maximum.
1296
	 * 
1297
	 * @see #getDistanceToWaterSurface()
1298
	 * @see #getDistanceToWaterSurfaceMax()
1299
	 * @param minimumDistance minimum of the range
1300
	 * @param maximumDistance maximum of the range
1301
	 */
1302
	public void setDistanceToWaterSurfaceRange(Double minimumDistance, Double maximumDistance) throws IllegalArgumentException{
1303
		getGatheringEvent(true).setDistanceToWaterSurface(minimumDistance);
1304
		getGatheringEvent(true).setDistanceToWaterSurfaceMax(maximumDistance);
1305
	}
1306
	
1307
	
1308
	// exact location
1309
	@Transient
1310
	public Point getExactLocation() {
1311
		return (hasGatheringEvent() ? getGatheringEvent(true).getExactLocation() : null);
1312
	}
1313

    
1314
	/**
1315
	 * Returns a sexagesimal representation of the exact location (e.g.
1316
	 * 12°59'N, 35°23E). If the exact location is <code>null</code> the empty
1317
	 * string is returned.
1318
	 * 
1319
	 * @param includeEmptySeconds
1320
	 * @param includeReferenceSystem
1321
	 * @return
1322
	 */
1323
	public String getExactLocationText(boolean includeEmptySeconds,
1324
			boolean includeReferenceSystem) {
1325
		return (this.getExactLocation() == null ? "" : this.getExactLocation()
1326
				.toSexagesimalString(includeEmptySeconds,
1327
						includeReferenceSystem));
1328
	}
1329

    
1330
	public void setExactLocation(Point exactLocation) {
1331
		getGatheringEvent(true).setExactLocation(exactLocation);
1332
	}
1333

    
1334
	public void setExactLocationByParsing(String longitudeToParse,
1335
			String latitudeToParse, ReferenceSystem referenceSystem,
1336
			Integer errorRadius) throws ParseException {
1337
		Point point = Point.NewInstance(null, null, referenceSystem,
1338
				errorRadius);
1339
		point.setLongitudeByParsing(longitudeToParse);
1340
		point.setLatitudeByParsing(latitudeToParse);
1341
		setExactLocation(point);
1342
	}
1343

    
1344
	// gathering event description
1345
	@Transient
1346
	public String getGatheringEventDescription() {
1347
		return (hasGatheringEvent() ? getGatheringEvent(true).getDescription()
1348
				: null);
1349
	}
1350

    
1351
	public void setGatheringEventDescription(String description) {
1352
		getGatheringEvent(true).setDescription(description);
1353
	}
1354

    
1355
	// gatering period
1356
	@Transient
1357
	public TimePeriod getGatheringPeriod() {
1358
		return (hasGatheringEvent() ? getGatheringEvent(true).getTimeperiod()
1359
				: null);
1360
	}
1361

    
1362
	public void setGatheringPeriod(TimePeriod timeperiod) {
1363
		getGatheringEvent(true).setTimeperiod(timeperiod);
1364
	}
1365

    
1366
	// locality
1367
	@Transient
1368
	public LanguageString getLocality() {
1369
		return (hasGatheringEvent() ? getGatheringEvent(true).getLocality()
1370
				: null);
1371
	}
1372

    
1373
	/**
1374
	 * convienience method for {@link #getLocality()}.
1375
	 * {@link LanguageString#getText() getText()}
1376
	 * 
1377
	 * @return
1378
	 */
1379
	@Transient
1380
	public String getLocalityText() {
1381
		LanguageString locality = getLocality();
1382
		if (locality != null) {
1383
			return locality.getText();
1384
		}
1385
		return null;
1386
	}
1387

    
1388
	/**
1389
	 * convienience method for {@link #getLocality()}.
1390
	 * {@link LanguageString#getLanguage() getLanguage()}
1391
	 * 
1392
	 * @return
1393
	 */
1394
	@Transient
1395
	public Language getLocalityLanguage() {
1396
		LanguageString locality = getLocality();
1397
		if (locality != null) {
1398
			return locality.getLanguage();
1399
		}
1400
		return null;
1401
	}
1402

    
1403
	/**
1404
	 * Sets the locality string in the default language
1405
	 * 
1406
	 * @param locality
1407
	 */
1408
	public void setLocality(String locality) {
1409
		Language language = Language.DEFAULT();
1410
		setLocality(locality, language);
1411
	}
1412

    
1413
	public void setLocality(String locality, Language language) {
1414
		LanguageString langString = LanguageString.NewInstance(locality, language);
1415
		setLocality(langString);
1416
	}
1417

    
1418
	public void setLocality(LanguageString locality) {
1419
		getGatheringEvent(true).setLocality(locality);
1420
	}
1421

    
1422
	/**
1423
	 * The gathering event will be used for the field object instead of the old
1424
	 * gathering event.<BR>
1425
	 * <B>This method will override all gathering values (see below).</B>
1426
	 * 
1427
	 * @see #getAbsoluteElevation()
1428
	 * @see #getAbsoluteElevationError()
1429
	 * @see #getDistanceToGround()
1430
	 * @see #getDistanceToWaterSurface()
1431
	 * @see #getExactLocation()
1432
	 * @see #getGatheringEventDescription()
1433
	 * @see #getGatheringPeriod()
1434
	 * @see #getCollectingAreas()
1435
	 * @see #getCollectingMethod()
1436
	 * @see #getLocality()
1437
	 * @see #getCollector()
1438
	 * @param gatheringEvent
1439
	 */
1440
	public void setGatheringEvent(GatheringEvent gatheringEvent) {
1441
		getFieldObservation(true).setGatheringEvent(gatheringEvent);
1442
	}
1443

    
1444
	public boolean hasGatheringEvent() {
1445
		return (getGatheringEvent(false) != null);
1446
	}
1447

    
1448
	public GatheringEvent innerGatheringEvent() {
1449
		return getGatheringEvent(false);
1450
	}
1451

    
1452
	public GatheringEvent getGatheringEvent(boolean createIfNotExists) {
1453
		if (!hasFieldObservation() && !createIfNotExists) {
1454
			return null;
1455
		}
1456
		if (createIfNotExists && getFieldObservation(true).getGatheringEvent() == null) {
1457
			GatheringEvent gatheringEvent = GatheringEvent.NewInstance();
1458
			getFieldObservation(true).setGatheringEvent(gatheringEvent);
1459
		}
1460
		return getFieldObservation(true).getGatheringEvent();
1461
	}
1462

    
1463
	// ****************** Field Object ************************************/
1464

    
1465
	/**
1466
	 * Returns true if a field observation exists (even if all attributes are
1467
	 * empty or <code>null<code>.
1468
	 * 
1469
	 * @return
1470
	 */
1471
	public boolean hasFieldObject() {
1472
		return this.fieldObservation != null;
1473
	}
1474

    
1475
	// ecology
1476
	@Transient
1477
	public String getEcology() {
1478
		return getEcology(Language.DEFAULT());
1479
	}
1480

    
1481
	public String getEcology(Language language) {
1482
		LanguageString languageString = getEcologyAll().get(language);
1483
		return (languageString == null ? null : languageString.getText());
1484
	}
1485

    
1486
	// public String getEcologyPreferred(List<Language> languages){
1487
	// LanguageString languageString =
1488
	// getEcologyAll().getPreferredLanguageString(languages);
1489
	// return languageString.getText();
1490
	// }
1491
	/**
1492
	 * Returns a copy of the multilanguage text holding the ecology data.
1493
	 * 
1494
	 * @see {@link TextData#getMultilanguageText()}
1495
	 * @return
1496
	 */
1497
	@Transient
1498
	public Map<Language, LanguageString> getEcologyAll() {
1499
		if (ecology == null) {
1500
			try {
1501
				ecology = initializeFieldObjectTextDataWithSupportTest(
1502
						Feature.ECOLOGY(), false, false);
1503
			} catch (DerivedUnitFacadeNotSupportedException e) {
1504
				throw new IllegalStateException(notSupportMessage, e);
1505
			}
1506
			if (ecology == null) {
1507
				return new HashMap<Language, LanguageString>();
1508
			}
1509
		}
1510
		return ecology.getMultilanguageText();
1511
	}
1512

    
1513
	public void setEcology(String ecology) {
1514
		setEcology(ecology, null);
1515
	}
1516

    
1517
	public void setEcology(String ecologyText, Language language) {
1518
		if (language == null) {
1519
			language = Language.DEFAULT();
1520
		}
1521
		if (ecology == null) {
1522
			try {
1523
				ecology = initializeFieldObjectTextDataWithSupportTest(
1524
						Feature.ECOLOGY(), true, false);
1525
			} catch (DerivedUnitFacadeNotSupportedException e) {
1526
				throw new IllegalStateException(notSupportMessage, e);
1527
			}
1528
		}
1529
		if (ecologyText == null) {
1530
			ecology.removeText(language);
1531
		} else {
1532
			ecology.putText(language, ecologyText);
1533
		}
1534
	}
1535

    
1536
	public void removeEcology(Language language) {
1537
		setEcology(null, language);
1538
	}
1539

    
1540
	/**
1541
	 * Removes ecology for the default language
1542
	 */
1543
	public void removeEcology() {
1544
		setEcology(null, null);
1545
	}
1546

    
1547
	public void removeEcologyAll() {
1548

    
1549
	}
1550

    
1551
	// plant description
1552
	@Transient
1553
	public String getPlantDescription() {
1554
		return getPlantDescription(null);
1555
	}
1556

    
1557
	public String getPlantDescription(Language language) {
1558
		if (language == null) {
1559
			language = Language.DEFAULT();
1560
		}
1561
		LanguageString languageString = getPlantDescriptionAll().get(language);
1562
		return (languageString == null ? null : languageString.getText());
1563
	}
1564

    
1565
	// public String getPlantDescriptionPreferred(List<Language> languages){
1566
	// LanguageString languageString =
1567
	// getPlantDescriptionAll().getPreferredLanguageString(languages);
1568
	// return languageString.getText();
1569
	// }
1570
	/**
1571
	 * Returns a copy of the multilanguage text holding the description data.
1572
	 * 
1573
	 * @see {@link TextData#getMultilanguageText()}
1574
	 * @return
1575
	 */
1576
	@Transient
1577
	public Map<Language, LanguageString> getPlantDescriptionAll() {
1578
		if (plantDescription == null) {
1579
			try {
1580
				plantDescription = initializeFieldObjectTextDataWithSupportTest(
1581
						Feature.DESCRIPTION(), false, false);
1582
			} catch (DerivedUnitFacadeNotSupportedException e) {
1583
				throw new IllegalStateException(notSupportMessage, e);
1584
			}
1585
			if (plantDescription == null) {
1586
				return new HashMap<Language, LanguageString>();
1587
			}
1588
		}
1589
		return plantDescription.getMultilanguageText();
1590
	}
1591

    
1592
	public void setPlantDescription(String plantDescription) {
1593
		setPlantDescription(plantDescription, null);
1594
	}
1595

    
1596
	public void setPlantDescription(String plantDescriptionText,
1597
			Language language) {
1598
		if (language == null) {
1599
			language = Language.DEFAULT();
1600
		}
1601
		if (plantDescription == null) {
1602
			try {
1603
				plantDescription = initializeFieldObjectTextDataWithSupportTest(
1604
						Feature.DESCRIPTION(), true, false);
1605
			} catch (DerivedUnitFacadeNotSupportedException e) {
1606
				throw new IllegalStateException(notSupportMessage, e);
1607
			}
1608
		}
1609
		if (plantDescriptionText == null) {
1610
			plantDescription.removeText(language);
1611
		} else {
1612
			plantDescription.putText(language, plantDescriptionText);
1613
		}
1614
	}
1615

    
1616
	public void removePlantDescription(Language language) {
1617
		setPlantDescription(null, language);
1618
	}
1619

    
1620
	// field object definition
1621
	public void addFieldObjectDefinition(String text, Language language) {
1622
		getFieldObservation(true).putDefinition(language, text);
1623
	}
1624

    
1625
	@Transient
1626
	public Map<Language, LanguageString> getFieldObjectDefinition() {
1627
		if (!hasFieldObservation()) {
1628
			return new HashMap<Language, LanguageString>();
1629
		} else {
1630
			return getFieldObservation(true).getDefinition();
1631
		}
1632
	}
1633

    
1634
	public String getFieldObjectDefinition(Language language) {
1635
		Map<Language, LanguageString> map = getFieldObjectDefinition();
1636
		LanguageString languageString = (map == null ? null : map.get(language));
1637
		if (languageString != null) {
1638
			return languageString.getText();
1639
		} else {
1640
			return null;
1641
		}
1642
	}
1643

    
1644
	public void removeFieldObjectDefinition(Language lang) {
1645
		if (hasFieldObservation()) {
1646
			getFieldObservation(true).removeDefinition(lang);
1647
		}
1648
	}
1649

    
1650
	// media
1651
	public boolean addFieldObjectMedia(Media media) {
1652
		try {
1653
			return addMedia(media, getFieldObservation(true));
1654
		} catch (DerivedUnitFacadeNotSupportedException e) {
1655
			throw new IllegalStateException(notSupportMessage, e);
1656
		}
1657
	}
1658

    
1659
	/**
1660
	 * Returns true, if an image gallery for the field object exists.<BR>
1661
	 * Returns also <code>true</code> if the image gallery is empty.
1662
	 * 
1663
	 * @return
1664
	 */
1665
	public boolean hasFieldObjectImageGallery() {
1666
		if (!hasFieldObject()) {
1667
			return false;
1668
		} else {
1669
			return (getImageGallery(fieldObservation, false) != null);
1670
		}
1671
	}
1672

    
1673
	public void setFieldObjectImageGallery(SpecimenDescription imageGallery)
1674
			throws DerivedUnitFacadeNotSupportedException {
1675
		SpecimenDescription existingGallery = getFieldObjectImageGallery(false);
1676

    
1677
		// test attached specimens contain this.derivedUnit
1678
		SpecimenOrObservationBase<?> facadeFieldObservation = innerFieldObservation();
1679
		testSpecimenInImageGallery(imageGallery, facadeFieldObservation);
1680

    
1681
		if (existingGallery != null) {
1682
			if (existingGallery != imageGallery) {
1683
				throw new DerivedUnitFacadeNotSupportedException(
1684
						"DerivedUnitFacade does not allow more than one image gallery");
1685
			} else {
1686
				// do nothing
1687
			}
1688
		} else {
1689
			TextData textData = testImageGallery(imageGallery);
1690
			this.fieldObjectMediaTextData = textData;
1691
		}
1692
	}
1693

    
1694
	/**
1695
	 * Returns the field object image gallery. If no such image gallery exists
1696
	 * and createIfNotExists is true an new one is created. Otherwise null is
1697
	 * returned.
1698
	 * 
1699
	 * @param createIfNotExists
1700
	 * @return
1701
	 */
1702
	public SpecimenDescription getFieldObjectImageGallery(
1703
			boolean createIfNotExists) {
1704
		TextData textData;
1705
		try {
1706
			textData = initializeFieldObjectTextDataWithSupportTest(
1707
					Feature.IMAGE(), createIfNotExists, true);
1708
		} catch (DerivedUnitFacadeNotSupportedException e) {
1709
			throw new IllegalStateException(notSupportMessage, e);
1710
		}
1711
		if (textData != null) {
1712
			return CdmBase.deproxy(textData.getInDescription(),
1713
					SpecimenDescription.class);
1714
		} else {
1715
			return null;
1716
		}
1717
	}
1718

    
1719
	/**
1720
	 * Returns the media for the field object.<BR>
1721
	 * 
1722
	 * @return
1723
	 */
1724
	@Transient
1725
	public List<Media> getFieldObjectMedia() {
1726
		try {
1727
			List<Media> result = getMediaList(getFieldObservation(false), false);
1728
			return result == null ? new ArrayList<Media>() : result;
1729
		} catch (DerivedUnitFacadeNotSupportedException e) {
1730
			throw new IllegalStateException(notSupportMessage, e);
1731
		}
1732
	}
1733

    
1734
	public boolean removeFieldObjectMedia(Media media) {
1735
		try {
1736
			return removeMedia(media, getFieldObservation(false));
1737
		} catch (DerivedUnitFacadeNotSupportedException e) {
1738
			throw new IllegalStateException(notSupportMessage, e);
1739
		}
1740
	}
1741

    
1742
	// field number
1743
	@Transient
1744
	public String getFieldNumber() {
1745
		if (!hasFieldObservation()) {
1746
			return null;
1747
		} else {
1748
			return getFieldObservation(true).getFieldNumber();
1749
		}
1750
	}
1751

    
1752
	public void setFieldNumber(String fieldNumber) {
1753
		getFieldObservation(true).setFieldNumber(fieldNumber);
1754
	}
1755

    
1756
	// primary collector
1757
	@Transient
1758
	public Person getPrimaryCollector() {
1759
		if (!hasFieldObservation()) {
1760
			return null;
1761
		} else {
1762
			return getFieldObservation(true).getPrimaryCollector();
1763
		}
1764
	}
1765

    
1766
	public void setPrimaryCollector(Person primaryCollector) {
1767
		getFieldObservation(true).setPrimaryCollector(primaryCollector);
1768
	}
1769

    
1770
	// field notes
1771
	@Transient
1772
	public String getFieldNotes() {
1773
		if (!hasFieldObservation()) {
1774
			return null;
1775
		} else {
1776
			return getFieldObservation(true).getFieldNotes();
1777
		}
1778
	}
1779

    
1780
	public void setFieldNotes(String fieldNotes) {
1781
		getFieldObservation(true).setFieldNotes(fieldNotes);
1782
	}
1783

    
1784
	// individual counts
1785
	@Transient
1786
	public Integer getIndividualCount() {
1787
		return (hasFieldObservation() ? getFieldObservation(true)
1788
				.getIndividualCount() : null);
1789
	}
1790

    
1791
	public void setIndividualCount(Integer individualCount) {
1792
		getFieldObservation(true).setIndividualCount(individualCount);
1793
	}
1794

    
1795
	// life stage
1796
	@Transient
1797
	public DefinedTerm getLifeStage() {
1798
		return (hasFieldObservation() ? getFieldObservation(true)
1799
				.getLifeStage() : null);
1800
	}
1801

    
1802
	public void setLifeStage(DefinedTerm lifeStage) {
1803
		getFieldObservation(true).setLifeStage(lifeStage);
1804
	}
1805

    
1806
	// sex
1807
	@Transient
1808
	public DefinedTerm getSex() {
1809
		return (hasFieldObservation() ? getFieldObservation(true).getSex()
1810
				: null);
1811
	}
1812

    
1813
	public void setSex(DefinedTerm sex) {
1814
		getFieldObservation(true).setSex(sex);
1815
	}
1816

    
1817
	// field observation
1818
	public boolean hasFieldObservation() {
1819
		return (getFieldObservation(false) != null);
1820
	}
1821

    
1822
	/**
1823
	 * Returns the field observation as an object.
1824
	 * 
1825
	 * @return
1826
	 */
1827
	public FieldObservation innerFieldObservation() {
1828
		return getFieldObservation(false);
1829
	}
1830

    
1831
	/**
1832
	 * Returns the field observation as an object.
1833
	 * 
1834
	 * @return
1835
	 */
1836
	public FieldObservation getFieldObservation(boolean createIfNotExists) {
1837
		if (fieldObservation == null && createIfNotExists) {
1838
			setFieldObservation(FieldObservation.NewInstance());
1839
		}
1840
		return this.fieldObservation;
1841
	}
1842
	
1843

    
1844
	private void setFieldObservation(FieldObservation fieldObservation) {
1845
		this.fieldObservation = fieldObservation;
1846
		if (fieldObservation != null){
1847
			if (config.isFirePropertyChangeEvents()){
1848
				addNewEventPropagationListener(fieldObservation);
1849
			}
1850
			if (derivedUnit != null){
1851
				DerivationEvent derivationEvent = getDerivationEvent(CREATE);
1852
				derivationEvent.addOriginal(fieldObservation);
1853
			}
1854
			setFieldObservationCacheStrategy();
1855
		}
1856
	}
1857

    
1858
	// ****************** Specimen *******************************************
1859

    
1860
	// Definition
1861
	public void addDerivedUnitDefinition(String text, Language language) {
1862
		innerDerivedUnit().putDefinition(language, text);
1863
	}
1864

    
1865
	@Transient
1866
	public Map<Language, LanguageString> getDerivedUnitDefinitions() {
1867
		testDerivedUnit();
1868
		return this.derivedUnit.getDefinition();
1869
	}
1870

    
1871

    
1872
	public String getDerivedUnitDefinition(Language language) {
1873
		testDerivedUnit();
1874
		Map<Language, LanguageString> languageMap = derivedUnit.getDefinition();
1875
		LanguageString languageString = languageMap.get(language);
1876
		if (languageString != null) {
1877
			return languageString.getText();
1878
		} else {
1879
			return null;
1880
		}
1881
	}
1882

    
1883
	public void removeDerivedUnitDefinition(Language lang) {
1884
		testDerivedUnit();
1885
		derivedUnit.removeDefinition(lang);
1886
	}
1887

    
1888
	// Determination
1889
	public void addDetermination(DeterminationEvent determination) {
1890
		testDerivedUnit();
1891
		//TODO implement correct bidirectional mapping in model classes
1892
		determination.setIdentifiedUnit(derivedUnit);
1893
		derivedUnit.addDetermination(determination);
1894
	}
1895

    
1896
	@Transient
1897
	public DeterminationEvent getPreferredDetermination() {
1898
		testDerivedUnit();
1899
		Set<DeterminationEvent> events = derivedUnit.getDeterminations();
1900
		for (DeterminationEvent event : events){
1901
			if (event.getPreferredFlag() == true){
1902
				return event;
1903
			}
1904
		}
1905
		return null;
1906
	}
1907
	
1908
	/**
1909
	 * This method returns the preferred determination.
1910
	 * @see #getOtherDeterminations()
1911
	 * @see #getDeterminations()
1912
	 * @return
1913
	 */
1914
	@Transient
1915
	public void setPreferredDetermination(DeterminationEvent newEvent) {
1916
		testDerivedUnit();
1917
		Set<DeterminationEvent> events = derivedUnit.getDeterminations();
1918
		for (DeterminationEvent event : events){
1919
			if (event.getPreferredFlag() == true){
1920
				event.setPreferredFlag(false);
1921
			}
1922
		}
1923
		newEvent.setPreferredFlag(true);
1924
		events.add(newEvent);
1925
	}
1926
	
1927
	/**
1928
	 * This method returns all determinations except for the preferred one.
1929
	 * @see #getPreferredDetermination()
1930
	 * @see #getDeterminations()
1931
	 * @return
1932
	 */
1933
	@Transient
1934
	public Set<DeterminationEvent> getOtherDeterminations() {
1935
		testDerivedUnit();
1936
		Set<DeterminationEvent> events = derivedUnit.getDeterminations();
1937
		Set<DeterminationEvent> result = new HashSet<DeterminationEvent>();
1938
		for (DeterminationEvent event : events){
1939
			if (event.getPreferredFlag() != true){
1940
				result.add(event);
1941
			}
1942
		}
1943
		return result;
1944
	}
1945
	
1946
	/**
1947
	 * This method returns all determination events. The preferred one {@link #getPreferredDetermination()} 
1948
	 * and all others {@link #getOtherDeterminations()}.
1949
	 * @return
1950
	 */
1951
	@Transient
1952
	public Set<DeterminationEvent> getDeterminations() {
1953
		testDerivedUnit();
1954
		return derivedUnit.getDeterminations();
1955
	}
1956

    
1957
	public void removeDetermination(DeterminationEvent determination) {
1958
		testDerivedUnit();
1959
		derivedUnit.removeDetermination(determination);
1960
	}
1961

    
1962
	// Media
1963
	public boolean addDerivedUnitMedia(Media media) {
1964
		testDerivedUnit();
1965
		try {
1966
			return addMedia(media, derivedUnit);
1967
		} catch (DerivedUnitFacadeNotSupportedException e) {
1968
			throw new IllegalStateException(notSupportMessage, e);
1969
		}
1970
	}
1971

    
1972
	/**
1973
	 * Returns true, if an image gallery exists for the specimen.<BR>
1974
	 * Returns also <code>true</code> if the image gallery is empty.
1975
	 */
1976
	public boolean hasDerivedUnitImageGallery() {
1977
		return (getImageGallery(derivedUnit, false) != null);
1978
	}
1979

    
1980
	public SpecimenDescription getDerivedUnitImageGallery(boolean createIfNotExists) {
1981
		testDerivedUnit();
1982
		TextData textData;
1983
		try {
1984
			textData = inititializeTextDataWithSupportTest(Feature.IMAGE(),
1985
					derivedUnit, createIfNotExists, true);
1986
		} catch (DerivedUnitFacadeNotSupportedException e) {
1987
			throw new IllegalStateException(notSupportMessage, e);
1988
		}
1989
		if (textData != null) {
1990
			return CdmBase.deproxy(textData.getInDescription(),
1991
					SpecimenDescription.class);
1992
		} else {
1993
			return null;
1994
		}
1995
	}
1996

    
1997
	public void setDerivedUnitImageGallery(SpecimenDescription imageGallery)
1998
			throws DerivedUnitFacadeNotSupportedException {
1999
		testDerivedUnit();
2000
		SpecimenDescription existingGallery = getDerivedUnitImageGallery(false);
2001

    
2002
		// test attached specimens contain this.derivedUnit
2003
		SpecimenOrObservationBase facadeDerivedUnit = innerDerivedUnit();
2004
		testSpecimenInImageGallery(imageGallery, facadeDerivedUnit);
2005

    
2006
		if (existingGallery != null) {
2007
			if (existingGallery != imageGallery) {
2008
				throw new DerivedUnitFacadeNotSupportedException(
2009
						"DerivedUnitFacade does not allow more than one image gallery");
2010
			} else {
2011
				// do nothing
2012
			}
2013
		} else {
2014
			TextData textData = testImageGallery(imageGallery);
2015
			this.derivedUnitMediaTextData = textData;
2016
		}
2017
	}
2018

    
2019
	/**
2020
	 * @param imageGallery
2021
	 * @throws DerivedUnitFacadeNotSupportedException
2022
	 */
2023
	private void testSpecimenInImageGallery(SpecimenDescription imageGallery, SpecimenOrObservationBase specimen)
2024
				throws DerivedUnitFacadeNotSupportedException {
2025
		Set<SpecimenOrObservationBase> imageGallerySpecimens = imageGallery.getDescribedSpecimenOrObservations();
2026
		if (imageGallerySpecimens.size() < 1) {
2027
			throw new DerivedUnitFacadeNotSupportedException(
2028
					"Image Gallery has no Specimen attached. Please attache according specimen or field observation.");
2029
		}
2030
		if (!imageGallerySpecimens.contains(specimen)) {
2031
			throw new DerivedUnitFacadeNotSupportedException(
2032
					"Image Gallery has not the facade's field object attached. Please add field object first to image gallery specimenOrObservation list.");
2033
		}
2034
	}
2035

    
2036
	/**
2037
	 * Returns the media for the specimen.<BR>
2038
	 * 
2039
	 * @return
2040
	 */
2041
	@Transient
2042
	public List<Media> getDerivedUnitMedia() {
2043
		testDerivedUnit();
2044
		try {
2045
			List<Media> result = getMediaList(derivedUnit, false);
2046
			return result == null ? new ArrayList<Media>() : result;
2047
		} catch (DerivedUnitFacadeNotSupportedException e) {
2048
			throw new IllegalStateException(notSupportMessage, e);
2049
		}
2050
	}
2051

    
2052
	public boolean removeDerivedUnitMedia(Media media) {
2053
		testDerivedUnit();
2054
		try {
2055
			return removeMedia(media, derivedUnit);
2056
		} catch (DerivedUnitFacadeNotSupportedException e) {
2057
			throw new IllegalStateException(notSupportMessage, e);
2058
		}
2059
	}
2060

    
2061
	// Accession Number
2062
	@Transient
2063
	public String getAccessionNumber() {
2064
		testDerivedUnit();
2065
		return derivedUnit.getAccessionNumber();
2066
	}
2067

    
2068
	public void setAccessionNumber(String accessionNumber) {
2069
		testDerivedUnit();
2070
		derivedUnit.setAccessionNumber(accessionNumber);
2071
	}
2072

    
2073
	@Transient
2074
	public String getCatalogNumber() {
2075
		testDerivedUnit();
2076
		return derivedUnit.getCatalogNumber();
2077
	}
2078

    
2079
	public void setCatalogNumber(String catalogNumber) {
2080
		testDerivedUnit();
2081
		derivedUnit.setCatalogNumber(catalogNumber);
2082
	}
2083

    
2084
	@Transient
2085
	public String getBarcode() {
2086
		testDerivedUnit();
2087
		return derivedUnit.getBarcode();
2088
	}
2089

    
2090
	public void setBarcode(String barcode) {
2091
		testDerivedUnit();
2092
		derivedUnit.setBarcode(barcode);
2093
	}
2094

    
2095
	// Preservation Method
2096

    
2097
	/**
2098
	 * Only supported by specimen and fossils
2099
	 * 
2100
	 * @see #DerivedUnitType
2101
	 * @return
2102
	 */
2103
	@Transient
2104
	public PreservationMethod getPreservationMethod() throws MethodNotSupportedByDerivedUnitTypeException {
2105
		testDerivedUnit();
2106
		if (derivedUnit.isInstanceOf(Specimen.class)) {
2107
			return CdmBase.deproxy(derivedUnit, Specimen.class)
2108
					.getPreservation();
2109
		} else {
2110
			if (this.config
2111
					.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2112
				throw new MethodNotSupportedByDerivedUnitTypeException(
2113
						"A preservation method is only available in derived units of type 'Specimen' or 'Fossil'");
2114
			} else {
2115
				return null;
2116
			}
2117
		}
2118
	}
2119

    
2120
	/**
2121
	 * Only supported by specimen and fossils
2122
	 * 
2123
	 * @see #DerivedUnitType
2124
	 * @return
2125
	 */
2126
	public void setPreservationMethod(PreservationMethod preservation)
2127
			throws MethodNotSupportedByDerivedUnitTypeException {
2128
		testDerivedUnit();
2129
		if (derivedUnit.isInstanceOf(Specimen.class)) {
2130
			CdmBase.deproxy(derivedUnit, Specimen.class).setPreservation(
2131
					preservation);
2132
		} else {
2133
			if (this.config
2134
					.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2135
				throw new MethodNotSupportedByDerivedUnitTypeException(
2136
						"A preservation method is only available in derived units of type 'Specimen' or 'Fossil'");
2137
			} else {
2138
				return;
2139
			}
2140
		}
2141
	}
2142

    
2143
	// Stored under name
2144
	@Transient
2145
	public TaxonNameBase getStoredUnder() {
2146
		testDerivedUnit();
2147
		return derivedUnit.getStoredUnder();
2148
	}
2149

    
2150
	public void setStoredUnder(TaxonNameBase storedUnder) {
2151
		testDerivedUnit();
2152
		derivedUnit.setStoredUnder(storedUnder);
2153
	}
2154

    
2155
	// title cache
2156
	public String getTitleCache() {
2157
		SpecimenOrObservationBase<?> titledUnit = getTitledUnit();
2158
		
2159
		if (!titledUnit.isProtectedTitleCache()) {
2160
			// always compute title cache anew as long as there are no property
2161
			// change listeners on
2162
			// field observation, gathering event etc
2163
			titledUnit.setTitleCache(null, false);
2164
		}
2165
		return titledUnit.getTitleCache();
2166
	}
2167
	
2168
	private SpecimenOrObservationBase<?> getTitledUnit(){
2169
		return (derivedUnit != null )? derivedUnit : fieldObservation;
2170
	}
2171

    
2172
	public boolean isProtectedTitleCache() {
2173
		return getTitledUnit().isProtectedTitleCache();
2174
	}
2175

    
2176
	public void setTitleCache(String titleCache, boolean isProtected) {
2177
		this.getTitledUnit().setTitleCache(titleCache, isProtected);
2178
	}
2179

    
2180
	/**
2181
	 * Returns the derived unit itself.
2182
	 * 
2183
	 * @return the derived unit
2184
	 */
2185
	public DerivedUnitBase innerDerivedUnit() {
2186
		return this.derivedUnit;
2187
	}
2188
	
2189
//	/**
2190
//	 * Returns the derived unit itself.
2191
//	 * 
2192
//	 * @return the derived unit
2193
//	 */
2194
//	public DerivedUnitBase innerDerivedUnit(boolean createIfNotExists) {
2195
//		DerivedUnit result = this.derivedUnit; 
2196
//		if (result == null && createIfNotExists){
2197
//			if (this.fieldObservation == null){
2198
//				String message = "Field observation must exist to create derived unit.";
2199
//				throw new IllegalStateException(message);
2200
//			}else{
2201
//				DerivedUnit = 
2202
//				DerivationEvent derivationEvent = getDerivationEvent(true);
2203
//				derivationEvent.addOriginal(fieldObservation);
2204
//				return this.derivedUnit;
2205
//			}
2206
//		}
2207
//	}
2208

    
2209
	private boolean hasDerivationEvent() {
2210
		return getDerivationEvent() == null ? false : true;
2211
	}
2212

    
2213
	private DerivationEvent getDerivationEvent() {
2214
		return getDerivationEvent(false);
2215
	}
2216

    
2217
	/**
2218
	 * Returns the derivation event. If no derivation event exists and <code>createIfNotExists</code>
2219
	 * is <code>true</code> a new derivation event is created and returned. 
2220
	 * Otherwise <code>null</code> is returned.
2221
	 * @param createIfNotExists
2222
	 */
2223
	private DerivationEvent getDerivationEvent(boolean createIfNotExists) {
2224
		DerivationEvent result = null;
2225
		if (derivedUnit != null){
2226
			result = derivedUnit.getDerivedFrom();
2227
		}else{
2228
			return null;
2229
		}
2230
		if (result == null && createIfNotExists) {
2231
			DerivationEventType type = null;
2232
			if (isAccessioned(derivedUnit)){
2233
				type = DerivationEventType.ACCESSIONING();
2234
			}
2235
			
2236
			result = DerivationEvent.NewInstance(type);
2237
			derivedUnit.setDerivedFrom(result);
2238
		}
2239
		return result;
2240
	}
2241

    
2242
	/**
2243
	 * TODO still unclear which classes do definetly require accessioning.
2244
	 * Only return true for those classes which are clear.
2245
	 * @param derivedUnit
2246
	 * @return
2247
	 */
2248
	private boolean isAccessioned(DerivedUnitBase<?> derivedUnit) {
2249
		if (derivedUnit.isInstanceOf(Specimen.class) ){
2250
			return CdmBase.deproxy(derivedUnit, Specimen.class).getClass().equals(Specimen.class);
2251
		}else{
2252
			return false;
2253
		}
2254
	}
2255

    
2256
	@Transient
2257
	public String getExsiccatum()
2258
			throws MethodNotSupportedByDerivedUnitTypeException {
2259
		testDerivedUnit();
2260
		if (derivedUnit.isInstanceOf(Specimen.class)) {
2261
			return CdmBase.deproxy(derivedUnit, Specimen.class).getExsiccatum();
2262
		} else {
2263
			if (this.config
2264
					.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2265
				throw new MethodNotSupportedByDerivedUnitTypeException(
2266
						"An exsiccatum is only available in derived units of type 'Specimen' or 'Fossil'");
2267
			} else {
2268
				return null;
2269
			}
2270
		}
2271
	}
2272

    
2273
	public void setExsiccatum(String exsiccatum) throws Exception {
2274
		testDerivedUnit();
2275
		if (derivedUnit.isInstanceOf(Specimen.class)) {
2276
			CdmBase.deproxy(derivedUnit, Specimen.class).setExsiccatum(
2277
					exsiccatum);
2278
		} else {
2279
			if (this.config
2280
					.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2281
				throw new MethodNotSupportedByDerivedUnitTypeException(
2282
						"An exsiccatum is only available in derived units of type 'Specimen' or 'Fossil'");
2283
			} else {
2284
				return;
2285
			}
2286
		}
2287
	}
2288

    
2289
	// **** sources **/
2290
	public void addSource(IdentifiableSource source) {
2291
		testDerivedUnit();
2292
		this.derivedUnit.addSource(source);
2293
	}
2294

    
2295
	/**
2296
	 * Creates an {@link IOriginalSource orignal source} or type , 
2297
	 * adds it to the specimen and returns it.
2298
	 * 
2299
	 * @param reference
2300
	 * @param microReference
2301
	 * @param originalNameString
2302
	 * @return
2303
	 */
2304
	public IdentifiableSource addSource(OriginalSourceType type, Reference reference, String microReference, String originalNameString) {
2305
		IdentifiableSource source = IdentifiableSource.NewInstance(type, null, null, reference, microReference);
2306
		source.setOriginalNameString(originalNameString);
2307
		addSource(source);
2308
		return source;
2309
	}
2310

    
2311
	@Transient
2312
	public Set<IdentifiableSource> getSources() {
2313
		testDerivedUnit();
2314
		return derivedUnit.getSources();
2315
	}
2316

    
2317
	public void removeSource(IdentifiableSource source) {
2318
		testDerivedUnit();
2319
		this.derivedUnit.removeSource(source);
2320
	}
2321

    
2322
	/**
2323
	 * @return the collection
2324
	 */
2325
	@Transient
2326
	public Collection getCollection() {
2327
		testDerivedUnit();
2328
		return derivedUnit.getCollection();
2329
	}
2330

    
2331
	/**
2332
	 * @param collection
2333
	 *            the collection to set
2334
	 */
2335
	public void setCollection(Collection collection) {
2336
		testDerivedUnit();
2337
		derivedUnit.setCollection(collection);
2338
	}
2339

    
2340
	// annotation
2341
	public void addAnnotation(Annotation annotation) {
2342
		testDerivedUnit();
2343
		this.derivedUnit.addAnnotation(annotation);
2344
	}
2345

    
2346
	@Transient
2347
	public void getAnnotations() {
2348
		testDerivedUnit();
2349
		this.derivedUnit.getAnnotations();
2350
	}
2351

    
2352
	public void removeAnnotation(Annotation annotation) {
2353
		testDerivedUnit();
2354
		this.derivedUnit.removeAnnotation(annotation);
2355
	}
2356

    
2357
	// ******************************* Events ***************************
2358
	
2359
	//set of events that were currently fired by this facades field observation
2360
	//to avoid recursive fireing of the same event
2361
	private Set<PropertyChangeEvent> fireingEvents = new HashSet<PropertyChangeEvent>();
2362
	
2363
	/**
2364
	 * @return
2365
	 */
2366
	private void addNewEventPropagationListener(CdmBase listeningObject) {
2367
		//if there is already a listener, don't do anything
2368
		for (PropertyChangeListener listener : this.listeners.keySet()){
2369
			if (listeners.get(listener) == listeningObject){
2370
				return;
2371
			}
2372
		}
2373
		//create new listener
2374
		PropertyChangeListener listener = new PropertyChangeListener() {
2375
			@Override
2376
			public void propertyChange(PropertyChangeEvent event) {
2377
				if (derivedUnit != null){
2378
					derivedUnit.firePropertyChange(event);
2379
				}else{
2380
					if (! event.getSource().equals(fieldObservation) && ! fireingEvents.contains(event)  ){
2381
						fireingEvents.add(event);
2382
						fieldObservation.firePropertyChange(event);
2383
						fireingEvents.remove(event);
2384
					}
2385
				}
2386
			}
2387
		};
2388
		//add listener to listening object and to list of listeners
2389
		listeningObject.addPropertyChangeListener(listener);
2390
		listeners.put(listener, listeningObject);
2391
	}
2392

    
2393
	// **************** Other Collections ********************************
2394

    
2395
	/**
2396
	 * Creates a duplicate specimen which derives from the same derivation event
2397
	 * as the facade specimen and adds collection data to it (all data available
2398
	 * in DerivedUnitBase and Specimen. Data from SpecimenOrObservationBase and
2399
	 * above are not yet shared at the moment.
2400
	 * 
2401
	 * @param collection
2402
	 * @param catalogNumber
2403
	 * @param accessionNumber
2404
	 * @param collectorsNumber
2405
	 * @param storedUnder
2406
	 * @param preservation
2407
	 * @return
2408
	 */
2409
	public Specimen addDuplicate(Collection collection, String catalogNumber,
2410
			String accessionNumber, TaxonNameBase storedUnder, PreservationMethod preservation) {
2411
		testDerivedUnit();
2412
		Specimen duplicate = Specimen.NewInstance();
2413
		duplicate.setDerivedFrom(getDerivationEvent(CREATE));
2414
		duplicate.setCollection(collection);
2415
		duplicate.setCatalogNumber(catalogNumber);
2416
		duplicate.setAccessionNumber(accessionNumber);
2417
		duplicate.setStoredUnder(storedUnder);
2418
		duplicate.setPreservation(preservation);
2419
		return duplicate;
2420
	}
2421

    
2422
	public void addDuplicate(DerivedUnitBase duplicateSpecimen) {
2423
		// TODO check derivedUnitType
2424
		testDerivedUnit();
2425
		getDerivationEvent(CREATE).addDerivative(duplicateSpecimen);
2426
	}
2427

    
2428
	@Transient
2429
	public Set<Specimen> getDuplicates() {
2430
		testDerivedUnit();
2431
		Set<Specimen> result = new HashSet<Specimen>();
2432
		if (hasDerivationEvent()) {
2433
			for (DerivedUnitBase derivedUnit : getDerivationEvent(CREATE)
2434
					.getDerivatives()) {
2435
				if (derivedUnit.isInstanceOf(Specimen.class)
2436
						&& !derivedUnit.equals(this.derivedUnit)) {
2437
					result.add(CdmBase.deproxy(derivedUnit, Specimen.class));
2438
				}
2439
			}
2440
		}
2441
		return result;
2442
	}
2443

    
2444
	public void removeDuplicate(Specimen duplicateSpecimen) {
2445
		testDerivedUnit();
2446
		if (hasDerivationEvent()) {
2447
			getDerivationEvent(CREATE).removeDerivative(duplicateSpecimen);
2448
		}
2449
	}
2450
	
2451
	
2452

    
2453
	private void testDerivedUnit() {
2454
		if (derivedUnit == null){
2455
			throw new IllegalStateException("This method is not allowed for this specimen or observation type. Probably you have tried to add specimen(derived unit) information to a field observation");
2456
		}
2457
	}
2458

    
2459
	public void setType(DerivedUnitType type) {
2460
		this.type = type;
2461
	}
2462

    
2463
	public DerivedUnitType getType() {
2464
		return type;
2465
	}
2466

    
2467
	
2468
	/**
2469
	 * Closes this facade. As a minimum this method removes all listeners created by this facade from their 
2470
	 * listening objects.
2471
	 */
2472
	public void close(){
2473
		for (PropertyChangeListener listener : this.listeners.keySet()){
2474
			CdmBase listeningObject = listeners.get(listener);
2475
			listeningObject.removePropertyChangeListener(listener);
2476
		}
2477
	}
2478
	
2479
	
2480
	/**
2481
	 * Computes the correct distance string for given values for min, max and text.
2482
	 * If text is not blank, text is returned, otherwise "min - max" or a single value is returned.
2483
	 * @param min min value as number  
2484
	 * @param max max value as number
2485
	 * @param text text representation of distance
2486
	 * @return the formatted distance string
2487
	 */
2488
	private String distanceString(Number min, Number max, String text) {
2489
		if (StringUtils.isNotBlank(text)){
2490
			return text;
2491
		}else{
2492
			String minStr = min == null? null : String.valueOf(min);
2493
			String maxStr = max == null? null : String.valueOf(max);
2494
			String result = CdmUtils.concat(" " + UTF8.EN_DASH + " ", minStr, maxStr);
2495
			return result;
2496
		}
2497
	}
2498
}
(1-1/6)