Project

General

Profile

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

    
23
import javax.persistence.Transient;
24

    
25
import org.apache.commons.lang.StringUtils;
26
import org.apache.log4j.Logger;
27

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

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

    
79
	@SuppressWarnings("unused")
80
	private static final Logger logger = Logger.getLogger(DerivedUnitFacade.class);
81

    
82
	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 ";
83

    
84
	private static final boolean CREATE = true;
85
	private static final boolean CREATE_NOT = false;
86

    
87
	private final DerivedUnitFacadeConfigurator config;
88

    
89
	private final Map<PropertyChangeListener, CdmBase> listeners = new HashMap<PropertyChangeListener, CdmBase>();
90

    
91
	// Either fieldUnit or derivedUnit must not be null.
92
	private FieldUnit fieldUnit;
93
	private final DerivedUnit derivedUnit;
94

    
95
	// media - the text data holding the media
96
	private TextData derivedUnitMediaTextData;
97
	private TextData fieldObjectMediaTextData;
98

    
99
	private TextData ecology;
100
	private TextData plantDescription;
101

    
102
	/**
103
	 * Creates a derived unit facade for a new derived unit of type
104
	 * <code>type</code>.
105
	 *
106
	 * @param type
107
	 * @return
108
	 */
109
	public static DerivedUnitFacade NewInstance(SpecimenOrObservationType type) {
110
		return new DerivedUnitFacade(type, null, null);
111
	}
112

    
113
	/**
114
	 * Creates a derived unit facade for a new derived unit of type
115
	 * <code>type</code>.
116
	 *
117
	 * @param type
118
	 * @return
119
	 */
120
	public static DerivedUnitFacade NewInstance(SpecimenOrObservationType type, FieldUnit fieldUnit) {
121
		return new DerivedUnitFacade(type, fieldUnit, null);
122
	}
123

    
124
	/**
125
	 * Creates a derived unit facade for a new derived unit of type
126
	 * <code>type</code>.
127
	 *
128
	 * @param type
129
	 * @param fieldUnit the field unit to use
130
	 * @param config the facade configurator to use
131
	 * //TODO are there any ambiguities to solve with defining a field unit or a configurator
132
	 * @return
133
	 */
134
	public static DerivedUnitFacade NewInstance(SpecimenOrObservationType type, FieldUnit fieldUnit, DerivedUnitFacadeConfigurator config) {
135
		return new DerivedUnitFacade(type, fieldUnit, config);
136
	}
137

    
138

    
139
	/**
140
	 * Creates a derived unit facade for a given derived unit using the default
141
	 * configuration.
142
	 *
143
	 * @param derivedUnit
144
	 * @return
145
	 * @throws DerivedUnitFacadeNotSupportedException
146
	 */
147
	public static DerivedUnitFacade NewInstance(DerivedUnit derivedUnit)
148
			throws DerivedUnitFacadeNotSupportedException {
149
		return new DerivedUnitFacade(derivedUnit, null);
150
	}
151

    
152
	public static DerivedUnitFacade NewInstance(DerivedUnit derivedUnit,
153
			DerivedUnitFacadeConfigurator config)
154
			throws DerivedUnitFacadeNotSupportedException {
155
		return new DerivedUnitFacade(derivedUnit, config);
156
	}
157

    
158
	// ****************** CONSTRUCTOR ******************************************
159

    
160
	private DerivedUnitFacade(SpecimenOrObservationType type, FieldUnit fieldUnit, DerivedUnitFacadeConfigurator config) {
161
		if (config == null){
162
			config = DerivedUnitFacadeConfigurator.NewInstance();
163
		}
164
		this.config = config;
165
		// derivedUnit
166
		derivedUnit = getNewDerivedUnitInstance(type);
167
		//TODO parameter checking should be solved in a more generic way if we start using other entity facades
168
		if(derivedUnit==null && fieldUnit==null && type.isFieldUnit()){
169
		    this.fieldUnit = getFieldUnit(CREATE);
170
		}
171
		setFieldUnit(fieldUnit);
172
		if (derivedUnit != null){
173
			setCacheStrategy();
174
		}else{
175
			setFieldUnitCacheStrategy();
176
		}
177
	}
178

    
179
	private DerivedUnit getNewDerivedUnitInstance(SpecimenOrObservationType type) {
180
		if (type.isFieldUnit()){
181
			return null;
182
		}else if(type.isAnyDerivedUnit()){
183
			return DerivedUnit.NewInstance(type);
184
		} else {
185
			String message = "Unknown specimen or observation type %s";
186
			message = String.format(message, type.getMessage());
187
			throw new IllegalStateException(message);
188
		}
189
	}
190

    
191
	private DerivedUnitFacade(DerivedUnit derivedUnit, DerivedUnitFacadeConfigurator config)
192
			throws DerivedUnitFacadeNotSupportedException {
193

    
194
	    if(derivedUnit==null){
195
	        throw new IllegalArgumentException("DerivedUnit must not be null");
196
	    }
197

    
198
		if (config == null) {
199
			config = DerivedUnitFacadeConfigurator.NewInstance();
200
		}
201
		this.config = config;
202

    
203
		// derived unit
204
		this.derivedUnit = derivedUnit;
205

    
206
		// derivation event
207
		if (this.derivedUnit.getDerivedFrom() != null) {
208
			DerivationEvent derivationEvent = getDerivationEvent(CREATE);
209
			// fieldUnit
210
			Set<FieldUnit> fieldOriginals = getFieldUnitOriginals(derivationEvent, null);
211
			if (fieldOriginals.size() > 1) {
212
				throw new DerivedUnitFacadeNotSupportedException(
213
						"Specimen must not have more than 1 derivation event");
214
			} else if (fieldOriginals.size() == 0) {
215
				// fieldUnit = FieldUnit.NewInstance();
216
			} else if (fieldOriginals.size() == 1) {
217
				fieldUnit = fieldOriginals.iterator().next();
218
				// ###fieldUnit =
219
				// getInitializedFieldUnit(fieldUnit);
220
				if (config.isFirePropertyChangeEvents()){
221
					addNewEventPropagationListener(fieldUnit);
222
				}
223
			} else {
224
				throw new IllegalStateException("Illegal state");
225
			}
226
		}
227

    
228
		this.derivedUnitMediaTextData = inititializeTextDataWithSupportTest(Feature.IMAGE(), this.derivedUnit, false, true);
229

    
230
		fieldObjectMediaTextData = initializeFieldObjectTextDataWithSupportTest(Feature.IMAGE(), false, true);
231

    
232

    
233
//direct media have been removed from specimenorobservationbase #3597
234
//		// handle derivedUnit.getMedia()
235
//		if (derivedUnit.getMedia().size() > 0) {
236
//			// TODO better changed model here to allow only one place for images
237
//			if (this.config.isMoveDerivedUnitMediaToGallery()) {
238
//				Set<Media> mediaSet = derivedUnit.getMedia();
239
//				for (Media media : mediaSet) {
240
//					this.addDerivedUnitMedia(media);
241
//				}
242
//				mediaSet.removeAll(getDerivedUnitMedia());
243
//			} else {
244
//				throw new DerivedUnitFacadeNotSupportedException(
245
//						"Specimen may not have direct media. Only (one) image gallery is allowed");
246
//			}
247
//		}
248
//
249
//		// handle fieldUnit.getMedia()
250
//		if (fieldUnit != null && fieldUnit.getMedia() != null
251
//				&& fieldUnit.getMedia().size() > 0) {
252
//			// TODO better changed model here to allow only one place for images
253
//			if (this.config.isMoveFieldObjectMediaToGallery()) {
254
//				Set<Media> mediaSet = fieldUnit.getMedia();
255
//				for (Media media : mediaSet) {
256
//					this.addFieldObjectMedia(media);
257
//				}
258
//				mediaSet.removeAll(getFieldObjectMedia());
259
//			} else {
260
//				throw new DerivedUnitFacadeNotSupportedException(
261
//						"Field object may not have direct media. Only (one) image gallery is allowed");
262
//			}
263
//		}
264

    
265
		// test if descriptions are supported
266
		ecology = initializeFieldObjectTextDataWithSupportTest(
267
				Feature.ECOLOGY(), false, false);
268
		plantDescription = initializeFieldObjectTextDataWithSupportTest(
269
				Feature.DESCRIPTION(), false, false);
270

    
271
		setCacheStrategy();
272

    
273
	}
274

    
275
	private DerivedUnit getInitializedDerivedUnit(
276
			DerivedUnit derivedUnit) {
277
		IOccurrenceService occurrenceService = this.config
278
				.getOccurrenceService();
279
		if (occurrenceService == null) {
280
			return derivedUnit;
281
		}
282
		List<String> propertyPaths = this.config.getPropertyPaths();
283
		if (propertyPaths == null) {
284
			return derivedUnit;
285
		}
286
		propertyPaths = getDerivedUnitPropertyPaths(propertyPaths);
287
		DerivedUnit result = (DerivedUnit) occurrenceService.load(
288
				derivedUnit.getUuid(), propertyPaths);
289
		return result;
290
	}
291

    
292
	/**
293
	 * Initializes the derived unit according to the configuartions property
294
	 * path. If the property path is <code>null</code> or no occurrence service
295
	 * is given the returned object is the same as the input parameter.
296
	 *
297
	 * @param fieldUnit
298
	 * @return
299
	 */
300
	private FieldUnit getInitializedFieldUnit(FieldUnit fieldUnit) {
301
		IOccurrenceService occurrenceService = this.config
302
				.getOccurrenceService();
303
		if (occurrenceService == null) {
304
			return fieldUnit;
305
		}
306
		List<String> propertyPaths = this.config.getPropertyPaths();
307
		if (propertyPaths == null) {
308
			return fieldUnit;
309
		}
310
		propertyPaths = getFieldObjectPropertyPaths(propertyPaths);
311
		FieldUnit result = (FieldUnit) occurrenceService.load(
312
				fieldUnit.getUuid(), propertyPaths);
313
		return result;
314
	}
315

    
316
	/**
317
	 * Transforms the property paths in a way that the facade is handled just
318
	 * like an ordinary CdmBase object.<BR>
319
	 * E.g. a property path "collectinAreas" will be translated into
320
	 * gatheringEvent.collectingAreas
321
	 *
322
	 * @param propertyPaths
323
	 * @return
324
	 */
325
	private List<String> getFieldObjectPropertyPaths(List<String> propertyPaths) {
326
		List<String> result = new ArrayList<String>();
327
		for (String facadePath : propertyPaths) {
328
			// collecting areas (named area)
329
			if (facadePath.startsWith("collectingAreas")) {
330
				facadePath = "gatheringEvent." + facadePath;
331
				result.add(facadePath);
332
			}
333
			// collector (agentBase)
334
			else if (facadePath.startsWith("collector")) {
335
				facadePath = facadePath.replace("collector",
336
						"gatheringEvent.actor");
337
				result.add(facadePath);
338
			}
339
			// exactLocation (agentBase)
340
			else if (facadePath.startsWith("exactLocation")) {
341
				facadePath = "gatheringEvent." + facadePath;
342
				result.add(facadePath);
343
			}
344
			// gatheringPeriod (TimePeriod)
345
			else if (facadePath.startsWith("gatheringPeriod")) {
346
				facadePath = facadePath.replace("gatheringPeriod",
347
						"gatheringEvent.timeperiod");
348
				result.add(facadePath);
349
			}
350
			// (locality/ localityLanguage , LanguageString)
351
			else if (facadePath.startsWith("locality")) {
352
				facadePath = "gatheringEvent." + facadePath;
353
				result.add(facadePath);
354
			}
355

    
356
			// *********** FIELD OBJECT ************
357
			// fieldObjectDefinitions (Map<language, languageString)
358
			else if (facadePath.startsWith("fieldObjectDefinitions")) {
359
				// TODO or definition ???
360
				facadePath = facadePath.replace("fieldObjectDefinitions",
361
						"description");
362
				result.add(facadePath);
363
			}
364
			// fieldObjectMedia (Media)
365
			else if (facadePath.startsWith("fieldObjectMedia")) {
366
				// TODO ???
367
				facadePath = facadePath.replace("fieldObjectMedia",
368
						"descriptions.elements.media");
369
				result.add(facadePath);
370
			}
371

    
372
			// Gathering Event will always be added
373
			result.add("gatheringEvent");
374

    
375
		}
376

    
377
		/*
378
		 * Gathering Event ==================== - gatheringEvent
379
		 * (GatheringEvent)
380
		 *
381
		 * Field Object ================= - ecology/ ecologyAll (String) ??? -
382
		 * plant description (like ecology)
383
		 *
384
		 * - fieldObjectImageGallery (SpecimenDescription) - is automatically
385
		 * initialized via fieldObjectMedia
386
		 */
387

    
388
		return result;
389
	}
390

    
391
	/**
392
	 * Transforms the property paths in a way that the facade is handled just
393
	 * like an ordinary CdmBase object.<BR>
394
	 * E.g. a property path "collectinAreas" will be translated into
395
	 * gatheringEvent.collectingAreas
396
	 *
397
	 * Not needed (?) as the facade works with REST service property paths
398
	 * without using this method.
399
	 *
400
	 * @param propertyPaths
401
	 * @return
402
	 */
403
	private List<String> getDerivedUnitPropertyPaths(List<String> propertyPaths) {
404
		List<String> result = new ArrayList<String>();
405
		for (String facadePath : propertyPaths) {
406
			// determinations (DeterminationEvent)
407
			if (facadePath.startsWith("determinations")) {
408
				facadePath = "" + facadePath; // no change
409
				result.add(facadePath);
410
			}
411
			// storedUnder (TaxonNameBase)
412
			else if (facadePath.startsWith("storedUnder")) {
413
				facadePath = "" + facadePath; // no change
414
				result.add(facadePath);
415
			}
416
			// sources (IdentifiableSource)
417
			else if (facadePath.startsWith("sources")) {
418
				facadePath = "" + facadePath; // no change
419
				result.add(facadePath);
420
			}
421
			// collection (Collection)
422
			else if (facadePath.startsWith("collection")) {
423
				facadePath = "" + facadePath; // no change
424
				result.add(facadePath);
425
			}
426
			// (locality/ localityLanguage , LanguageString)
427
			else if (facadePath.startsWith("locality")) {
428
				facadePath = "gatheringEvent." + facadePath;
429
				result.add(facadePath);
430
			}
431

    
432
			// *********** FIELD OBJECT ************
433
			// derivedUnitDefinitions (Map<language, languageString)
434
			else if (facadePath.startsWith("derivedUnitDefinitions")) {
435
				// TODO or definition ???
436
				facadePath = facadePath.replace("derivedUnitDefinitions",
437
						"description");
438
				result.add(facadePath);
439
			}
440

    
441
			// derivedUnitMedia (Media)
442
			else if (facadePath.startsWith("derivedUnitMedia")) {
443
				// TODO ???
444
				facadePath = facadePath.replace("derivedUnitMedia",
445
						"descriptions.elements.media");
446
				result.add(facadePath);
447
			}
448

    
449
		}
450

    
451
		/*
452
		 * //TODO Derived Unit =====================
453
		 *
454
		 * - derivedUnitImageGallery (SpecimenDescription) - is automatically
455
		 * initialized via derivedUnitMedia
456
		 *
457
		 * - derivationEvent (DerivationEvent) - will always be initialized -
458
		 * duplicates (??? Specimen???) ???
459
		 */
460

    
461
		return result;
462
	}
463

    
464
	/**
465
	 *
466
	 */
467
	private void setCacheStrategy() {
468
		if (derivedUnit == null) {
469
			throw new NullPointerException(
470
					"Facade's derviedUnit must not be null to set cache strategy");
471
		}else{
472
			derivedUnit.setCacheStrategy(new DerivedUnitFacadeCacheStrategy());
473
			setFieldUnitCacheStrategy();
474
		}
475
	}
476

    
477
	private void setFieldUnitCacheStrategy() {
478
		if (this.hasFieldObject()){
479
			DerivedUnitFacadeFieldUnitCacheStrategy strategy = new DerivedUnitFacadeFieldUnitCacheStrategy();
480
			this.fieldUnit.setCacheStrategy(strategy);
481
		}
482
	}
483

    
484
	/**
485
	 * @param feature
486
	 * @param createIfNotExists
487
	 * @param isImageGallery
488
	 * @return
489
	 * @throws DerivedUnitFacadeNotSupportedException
490
	 */
491
	private TextData initializeFieldObjectTextDataWithSupportTest(
492
			Feature feature, boolean createIfNotExists, boolean isImageGallery)
493
			throws DerivedUnitFacadeNotSupportedException {
494
		// field object
495
		FieldUnit fieldObject = getFieldUnit(createIfNotExists);
496
		if (fieldObject == null) {
497
			return null;
498
		}
499
		return inititializeTextDataWithSupportTest(feature, fieldObject,
500
				createIfNotExists, isImageGallery);
501
	}
502

    
503
	/**
504
	 * @param feature
505
	 * @param specimen
506
	 * @param createIfNotExists
507
	 * @param isImageGallery
508
	 * @return
509
	 * @throws DerivedUnitFacadeNotSupportedException
510
	 */
511
	private TextData inititializeTextDataWithSupportTest(Feature feature,
512
			SpecimenOrObservationBase<?> specimen, boolean createIfNotExists,
513
			boolean isImageGallery)
514
			throws DerivedUnitFacadeNotSupportedException {
515
		if (feature == null) {
516
			return null;
517
		}
518
		TextData textData = null;
519
		if (createIfNotExists) {
520
			textData = TextData.NewInstance(feature);
521
		}
522

    
523
		Set<SpecimenDescription> descriptions;
524
		if (isImageGallery) {
525
			descriptions = specimen.getSpecimenDescriptionImageGallery();
526
		} else {
527
			descriptions = specimen.getSpecimenDescriptions(false);
528
		}
529
		// no description exists yet for this specimen
530
		if (descriptions.size() == 0) {
531
			if (createIfNotExists) {
532
				SpecimenDescription newSpecimenDescription = SpecimenDescription
533
						.NewInstance(specimen);
534
				newSpecimenDescription.addElement(textData);
535
				newSpecimenDescription.setImageGallery(isImageGallery);
536
				return textData;
537
			} else {
538
				return null;
539
			}
540
		}
541
		// description already exists
542
		Set<DescriptionElementBase> existingTextData = new HashSet<DescriptionElementBase>();
543
		for (SpecimenDescription description : descriptions) {
544
			// collect all existing text data
545
			for (DescriptionElementBase element : description.getElements()) {
546
				if (element.isInstanceOf(TextData.class)
547
						&& (feature.equals(element.getFeature()) || isImageGallery)) {
548
					existingTextData.add(element);
549
				}
550
			}
551
		}
552
		// use existing text data if exactly one exists
553
		if (existingTextData.size() > 1) {
554
			throw new DerivedUnitFacadeNotSupportedException(
555
					"Specimen facade does not support more than one description text data of type "
556
							+ feature.getLabel());
557

    
558
		} else if (existingTextData.size() == 1) {
559
			return CdmBase.deproxy(existingTextData.iterator().next(),
560
					TextData.class);
561
		} else {
562
			if (createIfNotExists) {
563
				SpecimenDescription description = descriptions.iterator()
564
						.next();
565
				description.addElement(textData);
566
			}
567
			return textData;
568
		}
569
	}
570

    
571
	/**
572
	 * Tests if a given image gallery is supported by the derived unit facade.
573
	 * It returns the only text data attached to the given image gallery. If the
574
	 * given image gallery does not have text data attached, it is created and
575
	 * attached.
576
	 *
577
	 * @param imageGallery
578
	 * @return
579
	 * @throws DerivedUnitFacadeNotSupportedException
580
	 */
581
	private TextData testImageGallery(SpecimenDescription imageGallery)
582
			throws DerivedUnitFacadeNotSupportedException {
583
		if (imageGallery.isImageGallery() == false) {
584
			throw new DerivedUnitFacadeNotSupportedException(
585
					"Image gallery needs to have image gallery flag set");
586
		}
587
		if (imageGallery.getElements().size() > 1) {
588
			throw new DerivedUnitFacadeNotSupportedException(
589
					"Image gallery must not have more then one description element");
590
		}
591
		TextData textData;
592
		if (imageGallery.getElements().size() == 0) {
593
			textData = TextData.NewInstance(Feature.IMAGE());
594
			imageGallery.addElement(textData);
595
		} else {
596
			if (!imageGallery.getElements().iterator().next()
597
					.isInstanceOf(TextData.class)) {
598
				throw new DerivedUnitFacadeNotSupportedException(
599
						"Image gallery must only have TextData as element");
600
			} else {
601
				textData = CdmBase.deproxy(imageGallery.getElements()
602
						.iterator().next(), TextData.class);
603
			}
604
		}
605
		return textData;
606
	}
607

    
608
	// ************************** METHODS
609
	// *****************************************
610

    
611
	private TextData getDerivedUnitImageGalleryTextData(
612
			boolean createIfNotExists)
613
			throws DerivedUnitFacadeNotSupportedException {
614
		if (this.derivedUnitMediaTextData == null && createIfNotExists) {
615
			this.derivedUnitMediaTextData = getImageGalleryTextData(
616
					derivedUnit, "Specimen");
617
		}
618
		return this.derivedUnitMediaTextData;
619
	}
620

    
621
	private TextData getObservationImageGalleryTextData(
622
			boolean createIfNotExists)
623
			throws DerivedUnitFacadeNotSupportedException {
624
		if (this.fieldObjectMediaTextData == null && createIfNotExists) {
625
			this.fieldObjectMediaTextData = getImageGalleryTextData(fieldUnit, "Field unit");
626
		}
627
		return this.fieldObjectMediaTextData;
628
	}
629

    
630
	/**
631
	 * @param derivationEvent
632
	 * @return
633
	 * @throws DerivedUnitFacadeNotSupportedException
634
	 */
635
	private Set<FieldUnit> getFieldUnitOriginals(
636
			DerivationEvent derivationEvent,
637
			Set<SpecimenOrObservationBase> recursionAvoidSet)
638
			throws DerivedUnitFacadeNotSupportedException {
639
		if (recursionAvoidSet == null) {
640
			recursionAvoidSet = new HashSet<SpecimenOrObservationBase>();
641
		}
642
		Set<FieldUnit> result = new HashSet<FieldUnit>();
643
		Set<SpecimenOrObservationBase> originals = derivationEvent.getOriginals();
644
		for (SpecimenOrObservationBase original : originals) {
645
			if (original.isInstanceOf(FieldUnit.class)) {
646
				result.add(CdmBase.deproxy(original, FieldUnit.class));
647
			} else if (original.isInstanceOf(DerivedUnit.class)) {
648
				// if specimen has already been tested exclude it from further
649
				// recursion
650
				if (recursionAvoidSet.contains(original)) {
651
					continue;
652
				}
653
				DerivedUnit derivedUnit = CdmBase.deproxy(original,	DerivedUnit.class);
654
				DerivationEvent originalDerivation = derivedUnit.getDerivedFrom();
655
				// Set<DerivationEvent> derivationEvents =
656
				// original.getDerivationEvents();
657
				// for (DerivationEvent originalDerivation : derivationEvents){
658
				if(originalDerivation!=null){
659
				    Set<FieldUnit> fieldUnits = getFieldUnitOriginals(
660
				            originalDerivation, recursionAvoidSet);
661
				    result.addAll(fieldUnits);
662
				}
663
				// }
664
			} else {
665
				throw new DerivedUnitFacadeNotSupportedException(
666
						"Unhandled specimen or observation base type: "
667
								+ original.getClass().getName());
668
			}
669

    
670
		}
671
		return result;
672
	}
673

    
674
	// *********** MEDIA METHODS ******************************
675

    
676
	// /**
677
	// * Returns the media list for a specimen. Throws an exception if the
678
	// existing specimen descriptions
679
	// * are not supported by this facade.
680
	// * @param specimen the specimen the media belongs to
681
	// * @param specimenExceptionText text describing the specimen for exception
682
	// messages
683
	// * @return
684
	// * @throws DerivedUnitFacadeNotSupportedException
685
	// */
686
	// private List<Media> getImageGalleryMedia(SpecimenOrObservationBase
687
	// specimen, String specimenExceptionText) throws
688
	// DerivedUnitFacadeNotSupportedException{
689
	// List<Media> result;
690
	// SpecimenDescription imageGallery =
691
	// getImageGalleryWithSupportTest(specimen, specimenExceptionText, true);
692
	// TextData textData = getImageTextDataWithSupportTest(imageGallery,
693
	// specimenExceptionText);
694
	// result = textData.getMedia();
695
	// return result;
696
	// }
697

    
698
	/**
699
	 * Returns the media list for a specimen. Throws an exception if the
700
	 * existing specimen descriptions are not supported by this facade.
701
	 *
702
	 * @param specimen
703
	 *            the specimen the media belongs to
704
	 * @param specimenExceptionText
705
	 *            text describing the specimen for exception messages
706
	 * @return
707
	 * @throws DerivedUnitFacadeNotSupportedException
708
	 */
709
	private TextData getImageGalleryTextData(SpecimenOrObservationBase specimen, String specimenExceptionText)
710
			throws DerivedUnitFacadeNotSupportedException {
711
		TextData result;
712
		SpecimenDescription imageGallery = getImageGalleryWithSupportTest(
713
				specimen, specimenExceptionText, true);
714
		result = getImageTextDataWithSupportTest(imageGallery,
715
				specimenExceptionText);
716
		return result;
717
	}
718

    
719
	/**
720
	 * Returns the image gallery of the according specimen. Throws an exception
721
	 * if the attached image gallerie(s) are not supported by this facade. If no
722
	 * image gallery exists a new one is created if
723
	 * <code>createNewIfNotExists</code> is true and if specimen is not
724
	 * <code>null</code>.
725
	 *
726
	 * @param specimen
727
	 * @param specimenText
728
	 * @param createNewIfNotExists
729
	 * @return
730
	 * @throws DerivedUnitFacadeNotSupportedException
731
	 */
732
	private SpecimenDescription getImageGalleryWithSupportTest(
733
			SpecimenOrObservationBase<?> specimen, String specimenText,
734
			boolean createNewIfNotExists)
735
			throws DerivedUnitFacadeNotSupportedException {
736
		if (specimen == null) {
737
			return null;
738
		}
739
		SpecimenDescription imageGallery;
740
		if (hasMultipleImageGalleries(specimen)) {
741
			throw new DerivedUnitFacadeNotSupportedException(specimenText
742
					+ " must not have more than 1 image gallery");
743
		} else {
744
			imageGallery = getImageGallery(specimen, createNewIfNotExists);
745
			getImageTextDataWithSupportTest(imageGallery, specimenText);
746
		}
747
		return imageGallery;
748
	}
749

    
750
	/**
751
	 * Returns the media holding text data element of the image gallery. Throws
752
	 * an exception if multiple such text data already exist. Creates a new text
753
	 * data if none exists and adds it to the image gallery. If image gallery is
754
	 * <code>null</code> nothing happens.
755
	 *
756
	 * @param imageGallery
757
	 * @param textData
758
	 * @return
759
	 * @throws DerivedUnitFacadeNotSupportedException
760
	 */
761
	private TextData getImageTextDataWithSupportTest(
762
			SpecimenDescription imageGallery, String specimenText)
763
			throws DerivedUnitFacadeNotSupportedException {
764
		if (imageGallery == null) {
765
			return null;
766
		}
767
		TextData textData = null;
768
		for (DescriptionElementBase element : imageGallery.getElements()) {
769
			if (element.isInstanceOf(TextData.class)
770
					&& element.getFeature().equals(Feature.IMAGE())) {
771
				if (textData != null) {
772
					throw new DerivedUnitFacadeNotSupportedException(
773
							specimenText
774
									+ " must not have more than 1 image text data element in image gallery");
775
				}
776
				textData = CdmBase.deproxy(element, TextData.class);
777
			}
778
		}
779
		if (textData == null) {
780
			textData = TextData.NewInstance(Feature.IMAGE());
781
			imageGallery.addElement(textData);
782
		}
783
		return textData;
784
	}
785

    
786
	/**
787
	 * Checks, if a specimen belongs to more than one description that is an
788
	 * image gallery
789
	 *
790
	 * @param derivedUnit
791
	 * @return
792
	 */
793
	private boolean hasMultipleImageGalleries(
794
			SpecimenOrObservationBase<?> derivedUnit) {
795
		int count = 0;
796
		Set<SpecimenDescription> descriptions = derivedUnit
797
				.getSpecimenDescriptions();
798
		for (SpecimenDescription description : descriptions) {
799
			if (description.isImageGallery()) {
800
				count++;
801
			}
802
		}
803
		return (count > 1);
804
	}
805

    
806
	/**
807
	 * Returns the image gallery for a specimen. If there are multiple specimen
808
	 * descriptions marked as image galleries an arbitrary one is chosen. If no
809
	 * image gallery exists, a new one is created if
810
	 * <code>createNewIfNotExists</code> is <code>true</code>.<Br>
811
	 * If specimen is <code>null</code> a null pointer exception is thrown.
812
	 *
813
	 * @param createNewIfNotExists
814
	 * @return
815
	 */
816
	private SpecimenDescription getImageGallery(SpecimenOrObservationBase<?> specimen, boolean createIfNotExists) {
817
		SpecimenDescription result = null;
818
		Set<SpecimenDescription> descriptions = specimen.getSpecimenDescriptions();
819
		for (SpecimenDescription description : descriptions) {
820
			if (description.isImageGallery()) {
821
				result = description;
822
				break;
823
			}
824
		}
825
		if (result == null && createIfNotExists) {
826
			result = SpecimenDescription.NewInstance(specimen);
827
			result.setImageGallery(true);
828
		}
829
		return result;
830
	}
831

    
832
	/**
833
	 * Adds a media to the specimens image gallery. If media is
834
	 * <code>null</code> nothing happens.
835
	 *
836
	 * @param media
837
	 * @param specimen
838
	 * @return true if media is not null (as specified by
839
	 *         {@link java.util.Collection#add(Object) Collection.add(E e)}
840
	 * @throws DerivedUnitFacadeNotSupportedException
841
	 */
842
	private boolean addMedia(Media media, SpecimenOrObservationBase<?> specimen) throws DerivedUnitFacadeNotSupportedException {
843
		if (media != null) {
844
			List<Media> mediaList = getMediaList(specimen, true);
845
			if (! mediaList.contains(media)){
846
				return mediaList.add(media);
847
			}else{
848
				return true;
849
			}
850
		} else {
851
			return false;
852
		}
853
	}
854

    
855
	/**
856
	 * Removes a media from the specimens image gallery.
857
	 *
858
	 * @param media
859
	 * @param specimen
860
	 * @return true if an element was removed as a result of this call (as
861
	 *         specified by {@link java.util.Collection#remove(Object)
862
	 *         Collection.remove(E e)}
863
	 * @throws DerivedUnitFacadeNotSupportedException
864
	 */
865
	private boolean removeMedia(Media media,
866
			SpecimenOrObservationBase<?> specimen)
867
			throws DerivedUnitFacadeNotSupportedException {
868
		List<Media> mediaList = getMediaList(specimen, true);
869
		return mediaList == null ? null : mediaList.remove(media);
870
	}
871

    
872
	private List<Media> getMediaList(SpecimenOrObservationBase<?> specimen, boolean createIfNotExists)
873
			throws DerivedUnitFacadeNotSupportedException {
874
		TextData textData = getMediaTextData(specimen, createIfNotExists);
875
		return textData == null ? null : textData.getMedia();
876
	}
877

    
878
	/**
879
	 * Returns the one media list of a specimen which is part of the only image
880
	 * gallery that this specimen is part of.<BR>
881
	 * If these conditions are not hold an exception is thrwon.
882
	 *
883
	 * @param specimen
884
	 * @return
885
	 * @throws DerivedUnitFacadeNotSupportedException
886
	 */
887
	// private List<Media> getMedia(SpecimenOrObservationBase<?> specimen)
888
	// throws DerivedUnitFacadeNotSupportedException {
889
	// if (specimen == null){
890
	// return null;
891
	// }
892
	// if (specimen == this.derivedUnit){
893
	// return getDerivedUnitImageGalleryMedia();
894
	// }else if (specimen == this.fieldUnit){
895
	// return getObservationImageGalleryTextData();
896
	// }else{
897
	// return getImageGalleryMedia(specimen, "Undefined specimen ");
898
	// }
899
	// }
900

    
901
	/**
902
	 * Returns the one media list of a specimen which is part of the only image
903
	 * gallery that this specimen is part of.<BR>
904
	 * If these conditions are not hold an exception is thrown.
905
	 *
906
	 * @param specimen
907
	 * @return
908
	 * @throws DerivedUnitFacadeNotSupportedException
909
	 */
910
	private TextData getMediaTextData(SpecimenOrObservationBase<?> specimen,
911
			boolean createIfNotExists)
912
			throws DerivedUnitFacadeNotSupportedException {
913
		if (specimen == null) {
914
			return null;
915
		}
916
		if (specimen == this.derivedUnit) {
917
			return getDerivedUnitImageGalleryTextData(createIfNotExists);
918
		} else if (specimen == this.fieldUnit) {
919
			return getObservationImageGalleryTextData(createIfNotExists);
920
		} else {
921
			return getImageGalleryTextData(specimen, "Undefined specimen ");
922
		}
923
	}
924

    
925
	// ****************** GETTER / SETTER / ADDER / REMOVER
926
	// ***********************/
927

    
928
	// ****************** Gathering Event *********************************/
929

    
930
	// country
931
	@Transient
932
	public NamedArea getCountry() {
933
		return (hasGatheringEvent() ? getGatheringEvent(true).getCountry()
934
				: null);
935
	}
936

    
937
	public void setCountry(NamedArea country) {
938
		getGatheringEvent(true).setCountry(country);
939
	}
940

    
941
	// Collecting area
942
	public void addCollectingArea(NamedArea area) {
943
		getGatheringEvent(true).addCollectingArea(area);
944
	}
945

    
946
	public void addCollectingAreas(java.util.Collection<NamedArea> areas) {
947
		for (NamedArea area : areas) {
948
			getGatheringEvent(true).addCollectingArea(area);
949
		}
950
	}
951

    
952
	@Transient
953
	public Set<NamedArea> getCollectingAreas() {
954
		return (hasGatheringEvent() ? getGatheringEvent(true)
955
				.getCollectingAreas() : null);
956
	}
957

    
958
	public void removeCollectingArea(NamedArea area) {
959
		if (hasGatheringEvent()) {
960
			getGatheringEvent(true).removeCollectingArea(area);
961
		}
962
	}
963

    
964
	static final String ALTITUDE_POSTFIX = " m";
965

    
966
	/**
967
	 * Returns the correctly formatted <code>absolute elevation</code> information.
968
	 * If absoluteElevationText is set, this will be returned,
969
	 * otherwise we absoluteElevation will be returned, followed by absoluteElevationMax
970
	 * if existing, separated by " - "
971
	 * @return
972
	 */
973
	@Transient
974
	public String absoluteElevationToString() {
975
		if (! hasGatheringEvent()){
976
			return null;
977
		}else{
978
			GatheringEvent ev = getGatheringEvent(true);
979
			if (StringUtils.isNotBlank(ev.getAbsoluteElevationText())){
980
				return ev.getAbsoluteElevationText();
981
			}else{
982
				String text = ev.getAbsoluteElevationText();
983
				Integer min = getAbsoluteElevation();
984
				Integer max = getAbsoluteElevationMaximum();
985
				return distanceString(min, max, text, METER);
986
			}
987
		}
988
	}
989

    
990

    
991
	/**
992
	 * meter above/below sea level of the surface
993
	 *
994
	 * @see #getAbsoluteElevationError()
995
	 * @see #getAbsoluteElevationRange()
996
	 **/
997
	@Transient
998
	public Integer getAbsoluteElevation() {
999
		return (hasGatheringEvent() ? getGatheringEvent(true).getAbsoluteElevation() : null);
1000
	}
1001

    
1002
	public void setAbsoluteElevation(Integer absoluteElevation) {
1003
		getGatheringEvent(true).setAbsoluteElevation(absoluteElevation);
1004
	}
1005

    
1006
	public void setAbsoluteElevationMax(Integer absoluteElevationMax) {
1007
		getGatheringEvent(true).setAbsoluteElevationMax(absoluteElevationMax);
1008
	}
1009

    
1010
	public void setAbsoluteElevationText(String absoluteElevationText) {
1011
		getGatheringEvent(true).setAbsoluteElevationText(absoluteElevationText);
1012
	}
1013

    
1014
	/**
1015
	 * @see #getAbsoluteElevation()
1016
	 * @see #getAbsoluteElevationError()
1017
	 * @see #setAbsoluteElevationRange(Integer, Integer)
1018
	 * @see #getAbsoluteElevationMinimum()
1019
	 */
1020
	@Transient
1021
	public Integer getAbsoluteElevationMaximum() {
1022
		if (!hasGatheringEvent()) {
1023
			return null;
1024
		}else{
1025
			return getGatheringEvent(true).getAbsoluteElevationMax();
1026
		}
1027
	}
1028

    
1029
	/**
1030
	 * @see #getAbsoluteElevation()
1031
	 * @see #getAbsoluteElevationError()
1032
	 * @see #setAbsoluteElevationRange(Integer, Integer)
1033
	 * @see #getAbsoluteElevationMinimum()
1034
	 */
1035
	@Transient
1036
	public String getAbsoluteElevationText() {
1037
		if (!hasGatheringEvent()) {
1038
			return null;
1039
		}else{
1040
			return getGatheringEvent(true).getAbsoluteElevationText();
1041
		}
1042
	}
1043

    
1044
	/**
1045
	 * Convenience method to set absolute elevation minimum and maximum.
1046
	 *
1047
	 * @see #setAbsoluteElevation(Integer)
1048
	 * @see #setAbsoluteElevationMax(Integer)
1049
	 * @param minimumElevation minimum of the range
1050
	 * @param maximumElevation maximum of the range
1051
	 */
1052
	public void setAbsoluteElevationRange(Integer minimumElevation, Integer maximumElevation) {
1053
		getGatheringEvent(true).setAbsoluteElevation(minimumElevation);
1054
		getGatheringEvent(true).setAbsoluteElevationMax(maximumElevation);
1055
	}
1056

    
1057
	// collector
1058
	@Transient
1059
	public AgentBase getCollector() {
1060
		return (hasGatheringEvent() ? getGatheringEvent(true).getCollector()
1061
				: null);
1062
	}
1063

    
1064
	public void setCollector(AgentBase collector) {
1065
		getGatheringEvent(true).setCollector(collector);
1066
	}
1067

    
1068
	// collecting method
1069
	@Transient
1070
	public String getCollectingMethod() {
1071
		return (hasGatheringEvent() ? getGatheringEvent(true).getCollectingMethod() : null);
1072
	}
1073

    
1074
	public void setCollectingMethod(String collectingMethod) {
1075
		getGatheringEvent(true).setCollectingMethod(collectingMethod);
1076
	}
1077

    
1078
	// distance to ground
1079

    
1080
	/**
1081
	 * Returns the correctly formatted <code>distance to ground</code> information.
1082
	 * If distanceToGroundText is not blank, it will be returned,
1083
	 * otherwise distanceToGround will be returned, followed by distanceToGroundMax
1084
	 * if existing, separated by " - "
1085
	 * @return
1086
	 */
1087
	@Transient
1088
	public String distanceToGroundToString() {
1089
		if (! hasGatheringEvent()){
1090
			return null;
1091
		}else{
1092
			GatheringEvent ev = getGatheringEvent(true);
1093
			String text = ev.getDistanceToGroundText();
1094
			Double min = getDistanceToGround();
1095
			Double max = getDistanceToGroundMax();
1096
			return distanceString(min, max, text, METER);
1097
		}
1098
	}
1099

    
1100
	@Transient
1101
	public Double getDistanceToGround() {
1102
		return (hasGatheringEvent() ? getGatheringEvent(true).getDistanceToGround() : null);
1103
	}
1104

    
1105
	public void setDistanceToGround(Double distanceToGround) {
1106
		getGatheringEvent(true).setDistanceToGround(distanceToGround);
1107
	}
1108

    
1109
	/**
1110
	 * @see #getDistanceToGround()
1111
	 * @see #getDistanceToGroundRange(Integer, Integer)
1112
	 */
1113
	@Transient
1114
	public Double getDistanceToGroundMax() {
1115
		if (!hasGatheringEvent()) {
1116
			return null;
1117
		}else{
1118
			return getGatheringEvent(true).getDistanceToGroundMax();
1119
		}
1120
	}
1121

    
1122
	public void setDistanceToGroundMax(Double distanceToGroundMax) {
1123
		getGatheringEvent(true).setDistanceToGroundMax(distanceToGroundMax);
1124
	}
1125

    
1126
	/**
1127
	 * @see #getDistanceToGround()
1128
	 * @see #setDistanceToGroundRange(Integer, Integer)
1129
	 */
1130
	@Transient
1131
	public String getDistanceToGroundText() {
1132
		if (!hasGatheringEvent()) {
1133
			return null;
1134
		}else{
1135
			return getGatheringEvent(true).getDistanceToGroundText();
1136
		}
1137
	}
1138
	public void setDistanceToGroundText(String distanceToGroundText) {
1139
		getGatheringEvent(true).setDistanceToGroundText(distanceToGroundText);
1140
	}
1141

    
1142
	/**
1143
	 * Convenience method to set distance to ground minimum and maximum.
1144
	 *
1145
	 * @see #getDistanceToGround()
1146
	 * @see #getDistanceToGroundMax()
1147
	 * @param minimumDistance minimum of the range
1148
	 * @param maximumDistance maximum of the range
1149
	 */
1150
	public void setDistanceToGroundRange(Double minimumDistance, Double maximumDistance) throws IllegalArgumentException{
1151
		getGatheringEvent(true).setDistanceToGround(minimumDistance);
1152
		getGatheringEvent(true).setDistanceToGroundMax(maximumDistance);
1153
	}
1154

    
1155

    
1156
	/**
1157
	 * Returns the correctly formatted <code>distance to water surface</code> information.
1158
	 * If distanceToWaterSurfaceText is not blank, it will be returned,
1159
	 * otherwise distanceToWaterSurface will be returned, followed by distanceToWatersurfaceMax
1160
	 * if existing, separated by " - "
1161
	 * @return
1162
	 */
1163
	@Transient
1164
	public String distanceToWaterSurfaceToString() {
1165
		if (! hasGatheringEvent()){
1166
			return null;
1167
		}else{
1168
			GatheringEvent ev = getGatheringEvent(true);
1169
			String text = ev.getDistanceToWaterSurfaceText();
1170
			Double min = getDistanceToWaterSurface();
1171
			Double max = getDistanceToWaterSurfaceMax();
1172
			return distanceString(min, max, text, METER);
1173
		}
1174
	}
1175

    
1176
	// distance to water surface
1177
	@Transient
1178
	public Double getDistanceToWaterSurface() {
1179
		return (hasGatheringEvent() ? getGatheringEvent(true).getDistanceToWaterSurface() : null);
1180
	}
1181

    
1182
	public void setDistanceToWaterSurface(Double distanceToWaterSurface) {
1183
		getGatheringEvent(true).setDistanceToWaterSurface(distanceToWaterSurface);
1184
	}
1185

    
1186
	/**
1187
	 * @see #getDistanceToWaterSurface()
1188
	 * @see #getDistanceToWaterSurfaceRange(Double, Double)
1189
	 */
1190
	@Transient
1191
	public Double getDistanceToWaterSurfaceMax() {
1192
		if (!hasGatheringEvent()) {
1193
			return null;
1194
		}else{
1195
			return getGatheringEvent(true).getDistanceToWaterSurfaceMax();
1196
		}
1197
	}
1198

    
1199
	public void setDistanceToWaterSurfaceMax(Double distanceToWaterSurfaceMax) {
1200
		getGatheringEvent(true).setDistanceToWaterSurfaceMax(distanceToWaterSurfaceMax);
1201
	}
1202

    
1203
	/**
1204
	 * @see #getDistanceToWaterSurface()
1205
	 * @see #getDistanceToWaterSurfaceRange(Double, Double)
1206
	 */
1207
	@Transient
1208
	public String getDistanceToWaterSurfaceText() {
1209
		if (!hasGatheringEvent()) {
1210
			return null;
1211
		}else{
1212
			return getGatheringEvent(true).getDistanceToWaterSurfaceText();
1213
		}
1214
	}
1215
	public void setDistanceToWaterSurfaceText(String distanceToWaterSurfaceText) {
1216
		getGatheringEvent(true).setDistanceToWaterSurfaceText(distanceToWaterSurfaceText);
1217
	}
1218

    
1219
	/**
1220
	 * Convenience method to set distance to ground minimum and maximum.
1221
	 *
1222
	 * @see #getDistanceToWaterSurface()
1223
	 * @see #getDistanceToWaterSurfaceMax()
1224
	 * @param minimumDistance minimum of the range, this is the distance which is closer to the water surface
1225
	 * @param maximumDistance maximum of the range, this is the distance which is farer to the water surface
1226
	 */
1227
	public void setDistanceToWaterSurfaceRange(Double minimumDistance, Double maximumDistance) throws IllegalArgumentException{
1228
		getGatheringEvent(true).setDistanceToWaterSurface(minimumDistance);
1229
		getGatheringEvent(true).setDistanceToWaterSurfaceMax(maximumDistance);
1230
	}
1231

    
1232

    
1233
	// exact location
1234
	@Transient
1235
	public Point getExactLocation() {
1236
		return (hasGatheringEvent() ? getGatheringEvent(true).getExactLocation() : null);
1237
	}
1238

    
1239
	/**
1240
	 * Returns a sexagesimal representation of the exact location (e.g.
1241
	 * 12°59'N, 35°23E). If the exact location is <code>null</code> the empty
1242
	 * string is returned.
1243
	 *
1244
	 * @param includeEmptySeconds
1245
	 * @param includeReferenceSystem
1246
	 * @return
1247
	 */
1248
	public String getExactLocationText(boolean includeEmptySeconds,
1249
			boolean includeReferenceSystem) {
1250
		return (this.getExactLocation() == null ? "" : this.getExactLocation()
1251
				.toSexagesimalString(includeEmptySeconds,
1252
						includeReferenceSystem));
1253
	}
1254

    
1255
	public void setExactLocation(Point exactLocation) {
1256
		getGatheringEvent(true).setExactLocation(exactLocation);
1257
	}
1258

    
1259
	public void setExactLocationByParsing(String longitudeToParse,
1260
			String latitudeToParse, ReferenceSystem referenceSystem,
1261
			Integer errorRadius) throws ParseException {
1262
		Point point = Point.NewInstance(null, null, referenceSystem,
1263
				errorRadius);
1264
		point.setLongitudeByParsing(longitudeToParse);
1265
		point.setLatitudeByParsing(latitudeToParse);
1266
		setExactLocation(point);
1267
	}
1268

    
1269
	// gathering event description
1270
	@Transient
1271
	public String getGatheringEventDescription() {
1272
		return (hasGatheringEvent() ? getGatheringEvent(true).getDescription()
1273
				: null);
1274
	}
1275

    
1276
	public void setGatheringEventDescription(String description) {
1277
		getGatheringEvent(true).setDescription(description);
1278
	}
1279

    
1280
	// gatering period
1281
	@Transient
1282
	public TimePeriod getGatheringPeriod() {
1283
		return (hasGatheringEvent() ? getGatheringEvent(true).getTimeperiod()
1284
				: null);
1285
	}
1286

    
1287
	public void setGatheringPeriod(TimePeriod timeperiod) {
1288
		getGatheringEvent(true).setTimeperiod(timeperiod);
1289
	}
1290

    
1291
	// locality
1292
	@Transient
1293
	public LanguageString getLocality() {
1294
		return (hasGatheringEvent() ? getGatheringEvent(true).getLocality()
1295
				: null);
1296
	}
1297

    
1298
	/**
1299
	 * convienience method for {@link #getLocality()}.
1300
	 * {@link LanguageString#getText() getText()}
1301
	 *
1302
	 * @return
1303
	 */
1304
	@Transient
1305
	public String getLocalityText() {
1306
		LanguageString locality = getLocality();
1307
		if (locality != null) {
1308
			return locality.getText();
1309
		}
1310
		return null;
1311
	}
1312

    
1313
	/**
1314
	 * convienience method for {@link #getLocality()}.
1315
	 * {@link LanguageString#getLanguage() getLanguage()}
1316
	 *
1317
	 * @return
1318
	 */
1319
	@Transient
1320
	public Language getLocalityLanguage() {
1321
		LanguageString locality = getLocality();
1322
		if (locality != null) {
1323
			return locality.getLanguage();
1324
		}
1325
		return null;
1326
	}
1327

    
1328
	/**
1329
	 * Sets the locality string in the default language
1330
	 *
1331
	 * @param locality
1332
	 */
1333
	public void setLocality(String locality) {
1334
		Language language = Language.DEFAULT();
1335
		setLocality(locality, language);
1336
	}
1337

    
1338
	public void setLocality(String locality, Language language) {
1339
		LanguageString langString = LanguageString.NewInstance(locality, language);
1340
		setLocality(langString);
1341
	}
1342

    
1343
	public void setLocality(LanguageString locality) {
1344
		getGatheringEvent(true).setLocality(locality);
1345
	}
1346

    
1347
	/**
1348
	 * The gathering event will be used for the field object instead of the old
1349
	 * gathering event.<BR>
1350
	 * <B>This method will override all gathering values (see below).</B>
1351
	 *
1352
	 * @see #getAbsoluteElevation()
1353
	 * @see #getAbsoluteElevationError()
1354
	 * @see #getDistanceToGround()
1355
	 * @see #getDistanceToWaterSurface()
1356
	 * @see #getExactLocation()
1357
	 * @see #getGatheringEventDescription()
1358
	 * @see #getGatheringPeriod()
1359
	 * @see #getCollectingAreas()
1360
	 * @see #getCollectingMethod()
1361
	 * @see #getLocality()
1362
	 * @see #getCollector()
1363
	 * @param gatheringEvent
1364
	 */
1365
	public void setGatheringEvent(GatheringEvent gatheringEvent) {
1366
		getFieldUnit(true).setGatheringEvent(gatheringEvent);
1367
	}
1368

    
1369
	public boolean hasGatheringEvent() {
1370
		return (getGatheringEvent(false) != null);
1371
	}
1372

    
1373
	public GatheringEvent innerGatheringEvent() {
1374
		return getGatheringEvent(false);
1375
	}
1376

    
1377
	public GatheringEvent getGatheringEvent(boolean createIfNotExists) {
1378
		if (!hasFieldUnit() && !createIfNotExists) {
1379
			return null;
1380
		}
1381
		if (createIfNotExists && getFieldUnit(true).getGatheringEvent() == null) {
1382
			GatheringEvent gatheringEvent = GatheringEvent.NewInstance();
1383
			getFieldUnit(true).setGatheringEvent(gatheringEvent);
1384
		}
1385
		return getFieldUnit(true).getGatheringEvent();
1386
	}
1387

    
1388
	// ****************** Field Object ************************************/
1389

    
1390
	/**
1391
	 * Returns true if a field unit exists (even if all attributes are
1392
	 * empty or <code>null<code>.
1393
	 *
1394
	 * @return
1395
	 */
1396
	public boolean hasFieldObject() {
1397
		return this.fieldUnit != null;
1398
	}
1399

    
1400
	// ecology
1401
	@Transient
1402
	public String getEcology() {
1403
		return getEcology(Language.DEFAULT());
1404
	}
1405

    
1406
	public String getEcology(Language language) {
1407
		LanguageString languageString = getEcologyAll().get(language);
1408
		return (languageString == null ? null : languageString.getText());
1409
	}
1410

    
1411
	// public String getEcologyPreferred(List<Language> languages){
1412
	// LanguageString languageString =
1413
	// getEcologyAll().getPreferredLanguageString(languages);
1414
	// return languageString.getText();
1415
	// }
1416
	/**
1417
	 * Returns a copy of the multilanguage text holding the ecology data.
1418
	 *
1419
	 * @see {@link TextData#getMultilanguageText()}
1420
	 * @return
1421
	 */
1422
	@Transient
1423
	public Map<Language, LanguageString> getEcologyAll() {
1424
		if (ecology == null) {
1425
			try {
1426
				ecology = initializeFieldObjectTextDataWithSupportTest(
1427
						Feature.ECOLOGY(), false, false);
1428
			} catch (DerivedUnitFacadeNotSupportedException e) {
1429
				throw new IllegalStateException(notSupportMessage, e);
1430
			}
1431
			if (ecology == null) {
1432
				return new HashMap<Language, LanguageString>();
1433
			}
1434
		}
1435
		return ecology.getMultilanguageText();
1436
	}
1437

    
1438
	public void setEcology(String ecology) {
1439
		setEcology(ecology, null);
1440
	}
1441

    
1442
	public void setEcology(String ecologyText, Language language) {
1443
		if (language == null) {
1444
			language = Language.DEFAULT();
1445
		}
1446
		boolean isEmpty = StringUtils.isBlank(ecologyText);
1447
		if (ecology == null) {
1448
			try {
1449
				ecology = initializeFieldObjectTextDataWithSupportTest(
1450
						Feature.ECOLOGY(), !isEmpty, false);
1451
			} catch (DerivedUnitFacadeNotSupportedException e) {
1452
				throw new IllegalStateException(notSupportMessage, e);
1453
			}
1454
		}
1455
		if (ecology != null){
1456
			if (ecologyText == null) {
1457
				ecology.removeText(language);
1458
			} else {
1459
				ecology.putText(language, ecologyText);
1460
			}
1461
		}
1462
	}
1463

    
1464
	public void removeEcology(Language language) {
1465
		setEcology(null, language);
1466
	}
1467

    
1468
	/**
1469
	 * Removes ecology for the default language
1470
	 */
1471
	public void removeEcology() {
1472
		setEcology(null, null);
1473
	}
1474

    
1475
	public void removeEcologyAll() {
1476

    
1477
	}
1478

    
1479
	// plant description
1480
	@Transient
1481
	public String getPlantDescription() {
1482
		return getPlantDescription(null);
1483
	}
1484

    
1485
	public String getPlantDescription(Language language) {
1486
		if (language == null) {
1487
			language = Language.DEFAULT();
1488
		}
1489
		LanguageString languageString = getPlantDescriptionAll().get(language);
1490
		return (languageString == null ? null : languageString.getText());
1491
	}
1492

    
1493
	// public String getPlantDescriptionPreferred(List<Language> languages){
1494
	// LanguageString languageString =
1495
	// getPlantDescriptionAll().getPreferredLanguageString(languages);
1496
	// return languageString.getText();
1497
	// }
1498
	/**
1499
	 * Returns a copy of the multilanguage text holding the description data.
1500
	 *
1501
	 * @see {@link TextData#getMultilanguageText()}
1502
	 * @return
1503
	 */
1504
	@Transient
1505
	public Map<Language, LanguageString> getPlantDescriptionAll() {
1506
		if (plantDescription == null) {
1507
			try {
1508
				plantDescription = initializeFieldObjectTextDataWithSupportTest(
1509
						Feature.DESCRIPTION(), false, false);
1510
			} catch (DerivedUnitFacadeNotSupportedException e) {
1511
				throw new IllegalStateException(notSupportMessage, e);
1512
			}
1513
			if (plantDescription == null) {
1514
				return new HashMap<Language, LanguageString>();
1515
			}
1516
		}
1517
		return plantDescription.getMultilanguageText();
1518
	}
1519

    
1520
	public void setPlantDescription(String plantDescription) {
1521
		setPlantDescription(plantDescription, null);
1522
	}
1523

    
1524
	public void setPlantDescription(String plantDescriptionText, Language language) {
1525
		if (language == null) {
1526
			language = Language.DEFAULT();
1527
		}
1528
		boolean isEmpty = StringUtils.isBlank(plantDescriptionText);
1529
		if (plantDescription == null) {
1530
			try {
1531
				plantDescription = initializeFieldObjectTextDataWithSupportTest(
1532
						Feature.DESCRIPTION(), !isEmpty, false);
1533
			} catch (DerivedUnitFacadeNotSupportedException e) {
1534
				throw new IllegalStateException(notSupportMessage, e);
1535
			}
1536
		}
1537
		if (plantDescription != null){
1538
			if (plantDescriptionText == null) {
1539
				plantDescription.removeText(language);
1540
			} else {
1541
				plantDescription.putText(language, plantDescriptionText);
1542
			}
1543
		}
1544
	}
1545

    
1546
	public void removePlantDescription(Language language) {
1547
		setPlantDescription(null, language);
1548
	}
1549

    
1550
	// field object definition
1551
	public void addFieldObjectDefinition(String text, Language language) {
1552
		getFieldUnit(true).putDefinition(language, text);
1553
	}
1554

    
1555
	@Transient
1556
	public Map<Language, LanguageString> getFieldObjectDefinition() {
1557
		if (!hasFieldUnit()) {
1558
			return new HashMap<Language, LanguageString>();
1559
		} else {
1560
			return getFieldUnit(true).getDefinition();
1561
		}
1562
	}
1563

    
1564
	public String getFieldObjectDefinition(Language language) {
1565
		Map<Language, LanguageString> map = getFieldObjectDefinition();
1566
		LanguageString languageString = (map == null ? null : map.get(language));
1567
		if (languageString != null) {
1568
			return languageString.getText();
1569
		} else {
1570
			return null;
1571
		}
1572
	}
1573

    
1574
	public void removeFieldObjectDefinition(Language lang) {
1575
		if (hasFieldUnit()) {
1576
			getFieldUnit(true).removeDefinition(lang);
1577
		}
1578
	}
1579

    
1580
	// media
1581
	public boolean addFieldObjectMedia(Media media) {
1582
		try {
1583
			return addMedia(media, getFieldUnit(true));
1584
		} catch (DerivedUnitFacadeNotSupportedException e) {
1585
			throw new IllegalStateException(notSupportMessage, e);
1586
		}
1587
	}
1588

    
1589
	/**
1590
	 * Returns true, if an image gallery for the field object exists.<BR>
1591
	 * Returns also <code>true</code> if the image gallery is empty.
1592
	 *
1593
	 * @return
1594
	 */
1595
	public boolean hasFieldObjectImageGallery() {
1596
		if (!hasFieldObject()) {
1597
			return false;
1598
		} else {
1599
			return (getImageGallery(fieldUnit, false) != null);
1600
		}
1601
	}
1602

    
1603
	public void setFieldObjectImageGallery(SpecimenDescription imageGallery)
1604
			throws DerivedUnitFacadeNotSupportedException {
1605
		SpecimenDescription existingGallery = getFieldObjectImageGallery(false);
1606

    
1607
		// test attached specimens contain this.derivedUnit
1608
		SpecimenOrObservationBase<?> facadeFieldUnit = innerFieldUnit();
1609
		testSpecimenInImageGallery(imageGallery, facadeFieldUnit);
1610

    
1611
		if (existingGallery != null) {
1612
			if (existingGallery != imageGallery) {
1613
				throw new DerivedUnitFacadeNotSupportedException(
1614
						"DerivedUnitFacade does not allow more than one image gallery");
1615
			} else {
1616
				// do nothing
1617
			}
1618
		} else {
1619
			TextData textData = testImageGallery(imageGallery);
1620
			this.fieldObjectMediaTextData = textData;
1621
		}
1622
	}
1623

    
1624
	/**
1625
	 * Returns the field object image gallery. If no such image gallery exists
1626
	 * and createIfNotExists is true an new one is created. Otherwise null is
1627
	 * returned.
1628
	 *
1629
	 * @param createIfNotExists
1630
	 * @return
1631
	 */
1632
	public SpecimenDescription getFieldObjectImageGallery(
1633
			boolean createIfNotExists) {
1634
		TextData textData;
1635
		try {
1636
			textData = initializeFieldObjectTextDataWithSupportTest(
1637
					Feature.IMAGE(), createIfNotExists, true);
1638
		} catch (DerivedUnitFacadeNotSupportedException e) {
1639
			throw new IllegalStateException(notSupportMessage, e);
1640
		}
1641
		if (textData != null) {
1642
			return CdmBase.deproxy(textData.getInDescription(),
1643
					SpecimenDescription.class);
1644
		} else {
1645
			return null;
1646
		}
1647
	}
1648

    
1649
	/**
1650
	 * Returns the media for the field object.<BR>
1651
	 *
1652
	 * @return
1653
	 */
1654
	@Transient
1655
	public List<Media> getFieldObjectMedia() {
1656
		try {
1657
			List<Media> result = getMediaList(getFieldUnit(false), false);
1658
			return result == null ? new ArrayList<Media>() : result;
1659
		} catch (DerivedUnitFacadeNotSupportedException e) {
1660
			throw new IllegalStateException(notSupportMessage, e);
1661
		}
1662
	}
1663

    
1664
	public boolean removeFieldObjectMedia(Media media) {
1665
		try {
1666
			return removeMedia(media, getFieldUnit(false));
1667
		} catch (DerivedUnitFacadeNotSupportedException e) {
1668
			throw new IllegalStateException(notSupportMessage, e);
1669
		}
1670
	}
1671

    
1672
	// field number
1673
	@Transient
1674
	public String getFieldNumber() {
1675
		if (!hasFieldUnit()) {
1676
			return null;
1677
		} else {
1678
			return getFieldUnit(true).getFieldNumber();
1679
		}
1680
	}
1681

    
1682
	public void setFieldNumber(String fieldNumber) {
1683
		getFieldUnit(true).setFieldNumber(fieldNumber);
1684
	}
1685

    
1686
	// primary collector
1687
	@Transient
1688
	public Person getPrimaryCollector() {
1689
		if (!hasFieldUnit()) {
1690
			return null;
1691
		} else {
1692
			return getFieldUnit(true).getPrimaryCollector();
1693
		}
1694
	}
1695

    
1696
	public void setPrimaryCollector(Person primaryCollector) {
1697
		getFieldUnit(true).setPrimaryCollector(primaryCollector);
1698
	}
1699

    
1700
	// field notes
1701
	@Transient
1702
	public String getFieldNotes() {
1703
		if (!hasFieldUnit()) {
1704
			return null;
1705
		} else {
1706
			return getFieldUnit(true).getFieldNotes();
1707
		}
1708
	}
1709

    
1710
	public void setFieldNotes(String fieldNotes) {
1711
		getFieldUnit(true).setFieldNotes(fieldNotes);
1712
	}
1713

    
1714
	// individual counts
1715
	@Transient
1716
	public Integer getIndividualCount() {
1717
		return (hasFieldUnit() ? getFieldUnit(true).getIndividualCount() : null);
1718
	}
1719

    
1720
	public void setIndividualCount(Integer individualCount) {
1721
		getFieldUnit(true).setIndividualCount(individualCount);
1722
	}
1723

    
1724
	// life stage
1725
	@Transient
1726
	public DefinedTerm getLifeStage() {
1727
		return (hasFieldUnit() ? getFieldUnit(true).getLifeStage() : null);
1728
	}
1729

    
1730
	public void setLifeStage(DefinedTerm lifeStage) {
1731
		getFieldUnit(true).setLifeStage(lifeStage);
1732
	}
1733

    
1734
	// sex
1735
	@Transient
1736
	public DefinedTerm getSex() {
1737
		return (hasFieldUnit() ? getFieldUnit(true).getSex(): null);
1738
	}
1739

    
1740
	public void setSex(DefinedTerm sex) {
1741
		getFieldUnit(true).setSex(sex);
1742
	}
1743

    
1744
	// kind of Unit
1745
	@Transient
1746
	public DefinedTerm getKindOfUnit() {
1747
		return (hasFieldUnit() ? getFieldUnit(true).getKindOfUnit() : null);
1748
	}
1749

    
1750
	public void setKindOfUnit(DefinedTerm kindOfUnit) {
1751
		getFieldUnit(true).setKindOfUnit(kindOfUnit);
1752
	}
1753

    
1754

    
1755
	// field unit
1756
	public boolean hasFieldUnit() {
1757
		return (getFieldUnit(CREATE_NOT) != null);
1758
	}
1759

    
1760
	/**
1761
	 * Returns the field unit as an object.
1762
	 *
1763
	 * @return
1764
	 */
1765
	public FieldUnit innerFieldUnit() {
1766
		return getFieldUnit(CREATE_NOT);
1767
	}
1768

    
1769
	/**
1770
	 * Returns the field unit as an object.
1771
	 *
1772
	 * @return
1773
	 */
1774
	public FieldUnit getFieldUnit(boolean createIfNotExists) {
1775
		if (fieldUnit == null && createIfNotExists) {
1776
			setFieldUnit(FieldUnit.NewInstance());
1777
		}
1778
		return this.fieldUnit;
1779
	}
1780

    
1781

    
1782
	private void setFieldUnit(FieldUnit fieldUnit) {
1783
		this.fieldUnit = fieldUnit;
1784
		if (fieldUnit != null){
1785
			if (config.isFirePropertyChangeEvents()){
1786
				addNewEventPropagationListener(fieldUnit);
1787
			}
1788
			if (derivedUnit != null){
1789
				DerivationEvent derivationEvent = getDerivationEvent(CREATE);
1790
				derivationEvent.addOriginal(fieldUnit);
1791
			}
1792
			setFieldUnitCacheStrategy();
1793
		}
1794
	}
1795

    
1796
	// ****************** Specimen *******************************************
1797

    
1798

    
1799
	// Definition
1800
	public void addDerivedUnitDefinition(String text, Language language) {
1801
		innerDerivedUnit().putDefinition(language, text);
1802
	}
1803

    
1804
	@Transient
1805
	public Map<Language, LanguageString> getDerivedUnitDefinitions() {
1806
		return ! checkDerivedUnit()? null : this.derivedUnit.getDefinition();
1807
	}
1808

    
1809

    
1810
	public String getDerivedUnitDefinition(Language language) {
1811
		if (! checkDerivedUnit()){
1812
			return null;
1813
		}
1814
		Map<Language, LanguageString> languageMap = derivedUnit.getDefinition();
1815
		LanguageString languageString = languageMap.get(language);
1816
		if (languageString != null) {
1817
			return languageString.getText();
1818
		} else {
1819
			return null;
1820
		}
1821
	}
1822

    
1823
	public void removeDerivedUnitDefinition(Language lang) {
1824
		testDerivedUnit();
1825
		derivedUnit.removeDefinition(lang);
1826
	}
1827

    
1828
	// Determination
1829
	public void addDetermination(DeterminationEvent determination) {
1830
		//TODO implement correct bidirectional mapping in model classes
1831
		determination.setIdentifiedUnit(baseUnit());
1832
		baseUnit().addDetermination(determination);
1833
	}
1834

    
1835
	@Transient
1836
	public DeterminationEvent getPreferredDetermination() {
1837
		Set<DeterminationEvent> events = baseUnit().getDeterminations();
1838
		for (DeterminationEvent event : events){
1839
			if (event.getPreferredFlag() == true){
1840
				return event;
1841
			}
1842
		}
1843
		return null;
1844
	}
1845

    
1846
	/**
1847
	 * This method returns the preferred determination.
1848
	 * @see #getOtherDeterminations()
1849
	 * @see #getDeterminations()
1850
	 * @return
1851
	 */
1852
	@Transient
1853
	public void setPreferredDetermination(DeterminationEvent newEvent) {
1854
		Set<DeterminationEvent> events = baseUnit().getDeterminations();
1855
		for (DeterminationEvent event : events){
1856
			if (event.getPreferredFlag() == true){
1857
				event.setPreferredFlag(false);
1858
			}
1859
		}
1860
		newEvent.setPreferredFlag(true);
1861
		events.add(newEvent);
1862
	}
1863

    
1864
	/**
1865
	 * This method returns all determinations except for the preferred one.
1866
	 * @see #getPreferredDetermination()
1867
	 * @see #getDeterminations()
1868
	 * @return
1869
	 */
1870
	@Transient
1871
	public Set<DeterminationEvent> getOtherDeterminations() {
1872
		Set<DeterminationEvent> events = baseUnit().getDeterminations();
1873
		Set<DeterminationEvent> result = new HashSet<DeterminationEvent>();
1874
		for (DeterminationEvent event : events){
1875
			if (event.getPreferredFlag() != true){
1876
				result.add(event);
1877
			}
1878
		}
1879
		return result;
1880
	}
1881

    
1882
	/**
1883
	 * This method returns all determination events. The preferred one {@link #getPreferredDetermination()}
1884
	 * and all others {@link #getOtherDeterminations()}.
1885
	 * @return
1886
	 */
1887
	@Transient
1888
	public Set<DeterminationEvent> getDeterminations() {
1889
		return baseUnit().getDeterminations();
1890
	}
1891

    
1892
	public void removeDetermination(DeterminationEvent determination) {
1893
		baseUnit().removeDetermination(determination);
1894
	}
1895

    
1896
	// Media
1897
	public boolean addDerivedUnitMedia(Media media) {
1898
		testDerivedUnit();
1899
		try {
1900
			return addMedia(media, derivedUnit);
1901
		} catch (DerivedUnitFacadeNotSupportedException e) {
1902
			throw new IllegalStateException(notSupportMessage, e);
1903
		}
1904
	}
1905

    
1906
	/**
1907
	 * Returns true, if an image gallery exists for the specimen.<BR>
1908
	 * Returns also <code>true</code> if the image gallery is empty.
1909
	 */
1910
	public boolean hasDerivedUnitImageGallery() {
1911
		return (getImageGallery(derivedUnit, false) != null);
1912
	}
1913

    
1914
	public SpecimenDescription getDerivedUnitImageGallery(boolean createIfNotExists) {
1915
		if (!checkDerivedUnit()){
1916
			return null;
1917
		}
1918
		TextData textData;
1919
		try {
1920
			textData = inititializeTextDataWithSupportTest(Feature.IMAGE(),
1921
					derivedUnit, createIfNotExists, true);
1922
		} catch (DerivedUnitFacadeNotSupportedException e) {
1923
			throw new IllegalStateException(notSupportMessage, e);
1924
		}
1925
		if (textData != null) {
1926
			return CdmBase.deproxy(textData.getInDescription(),
1927
					SpecimenDescription.class);
1928
		} else {
1929
			return null;
1930
		}
1931
	}
1932

    
1933
	public void setDerivedUnitImageGallery(SpecimenDescription imageGallery)
1934
			throws DerivedUnitFacadeNotSupportedException {
1935
		testDerivedUnit();
1936
		SpecimenDescription existingGallery = getDerivedUnitImageGallery(false);
1937

    
1938
		// test attached specimens contain this.derivedUnit
1939
		SpecimenOrObservationBase facadeDerivedUnit = innerDerivedUnit();
1940
		testSpecimenInImageGallery(imageGallery, facadeDerivedUnit);
1941

    
1942
		if (existingGallery != null) {
1943
			if (existingGallery != imageGallery) {
1944
				throw new DerivedUnitFacadeNotSupportedException(
1945
						"DerivedUnitFacade does not allow more than one image gallery");
1946
			} else {
1947
				// do nothing
1948
			}
1949
		} else {
1950
			TextData textData = testImageGallery(imageGallery);
1951
			this.derivedUnitMediaTextData = textData;
1952
		}
1953
	}
1954

    
1955
	/**
1956
	 * @param imageGallery
1957
	 * @throws DerivedUnitFacadeNotSupportedException
1958
	 */
1959
	private void testSpecimenInImageGallery(SpecimenDescription imageGallery, SpecimenOrObservationBase specimen)
1960
				throws DerivedUnitFacadeNotSupportedException {
1961
		SpecimenOrObservationBase imageGallerySpecimen = imageGallery.getDescribedSpecimenOrObservation();
1962
		if (imageGallerySpecimen == null) {
1963
			throw new DerivedUnitFacadeNotSupportedException(
1964
					"Image Gallery has no Specimen attached. Please attache according specimen or field unit.");
1965
		}
1966
		if (! imageGallerySpecimen.equals(specimen)) {
1967
			throw new DerivedUnitFacadeNotSupportedException(
1968
					"Image Gallery has not the facade's field object attached. Please add field object first " +
1969
					"to image gallery specimenOrObservation list.");
1970
		}
1971
	}
1972

    
1973
	/**
1974
	 * Returns the media for the specimen.<BR>
1975
	 *
1976
	 * @return
1977
	 */
1978
	@Transient
1979
	public List<Media> getDerivedUnitMedia() {
1980
		if (! checkDerivedUnit()){
1981
			return new ArrayList<Media>();
1982
		}
1983
		try {
1984
			List<Media> result = getMediaList(derivedUnit, false);
1985
			return result == null ? new ArrayList<Media>() : result;
1986
		} catch (DerivedUnitFacadeNotSupportedException e) {
1987
			throw new IllegalStateException(notSupportMessage, e);
1988
		}
1989
	}
1990

    
1991
	public boolean removeDerivedUnitMedia(Media media) {
1992
		testDerivedUnit();
1993
		try {
1994
			return removeMedia(media, derivedUnit);
1995
		} catch (DerivedUnitFacadeNotSupportedException e) {
1996
			throw new IllegalStateException(notSupportMessage, e);
1997
		}
1998
	}
1999

    
2000
	// Accession Number
2001
	@Transient
2002
	public String getAccessionNumber() {
2003
		return ! checkDerivedUnit()? null : derivedUnit.getAccessionNumber();
2004
	}
2005

    
2006
	public void setAccessionNumber(String accessionNumber) {
2007
		testDerivedUnit();
2008
		derivedUnit.setAccessionNumber(accessionNumber);
2009
	}
2010

    
2011
	@Transient
2012
	public String getCatalogNumber() {
2013
		return ! checkDerivedUnit()? null : derivedUnit.getCatalogNumber();
2014
	}
2015

    
2016
	public void setCatalogNumber(String catalogNumber) {
2017
		testDerivedUnit();
2018
		derivedUnit.setCatalogNumber(catalogNumber);
2019
	}
2020

    
2021
	@Transient
2022
	public String getBarcode() {
2023
		return ! checkDerivedUnit()? null : derivedUnit.getBarcode();
2024
	}
2025

    
2026
	public void setBarcode(String barcode) {
2027
		testDerivedUnit();
2028
		derivedUnit.setBarcode(barcode);
2029
	}
2030

    
2031
	// Preservation Method
2032

    
2033
	/**
2034
	 * Only supported by specimen and fossils
2035
	 *
2036
	 * @see #DerivedUnitType
2037
	 * @return
2038
	 */
2039
	@Transient
2040
	public PreservationMethod getPreservationMethod() throws MethodNotSupportedByDerivedUnitTypeException {
2041
		if (derivedUnit!=null && derivedUnit.getRecordBasis().isPreservedSpecimen()) {
2042
			return CdmBase.deproxy(derivedUnit, DerivedUnit.class).getPreservation();
2043
		} else {
2044
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2045
				throw new MethodNotSupportedByDerivedUnitTypeException(
2046
						"A preservation method is only available in derived units of type 'Preserved Specimen' or one of its specializations like 'Fossil Specimen' ");
2047
			} else {
2048
				return null;
2049
			}
2050
		}
2051
	}
2052

    
2053
	/**
2054
	 * Only supported by specimen and fossils
2055
	 *
2056
	 * @see #DerivedUnitType
2057
	 * @return
2058
	 */
2059
	public void setPreservationMethod(PreservationMethod preservation)
2060
			throws MethodNotSupportedByDerivedUnitTypeException {
2061
		if (derivedUnit!=null && derivedUnit.getRecordBasis().isPreservedSpecimen()) {
2062
			CdmBase.deproxy(derivedUnit, DerivedUnit.class).setPreservation(preservation);
2063
		} else {
2064
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2065
				throw new MethodNotSupportedByDerivedUnitTypeException(
2066
						"A preservation method is only available in derived units of type 'Specimen' or 'Fossil'");
2067
			} else {
2068
				return;
2069
			}
2070
		}
2071
	}
2072

    
2073
	//preferred stable URI  #5606
2074
	@Transient
2075
    public URI getPreferredStableUri(){
2076
        return baseUnit().getPreferredStableUri();
2077
    }
2078
    public void setPreferredStableUri(URI stableUri){
2079
        baseUnit().setPreferredStableUri(stableUri);
2080
    }
2081

    
2082

    
2083
	// Stored under name
2084
	@Transient
2085
	public TaxonNameBase getStoredUnder() {
2086
		return ! checkDerivedUnit()? null : derivedUnit.getStoredUnder();
2087
	}
2088

    
2089
	public void setStoredUnder(TaxonNameBase storedUnder) {
2090
		testDerivedUnit();
2091
		derivedUnit.setStoredUnder(storedUnder);
2092
	}
2093

    
2094
	// title cache
2095
	public String getTitleCache() {
2096
		SpecimenOrObservationBase<?> titledUnit = baseUnit();
2097

    
2098
		if (!titledUnit.isProtectedTitleCache()) {
2099
			// always compute title cache anew as long as there are no property
2100
			// change listeners on
2101
			// field unit, gathering event etc
2102
			titledUnit.setTitleCache(null, false);
2103
		}
2104
		return titledUnit.getTitleCache();
2105
	}
2106

    
2107
	public boolean isProtectedTitleCache() {
2108
		return baseUnit().isProtectedTitleCache();
2109
	}
2110

    
2111
	public void setTitleCache(String titleCache, boolean isProtected) {
2112
		this.baseUnit().setTitleCache(titleCache, isProtected);
2113
	}
2114

    
2115
	/**
2116
	 * Returns the derived unit itself.
2117
	 *
2118
	 * @return the derived unit
2119
	 */
2120
	public DerivedUnit innerDerivedUnit() {
2121
		return this.derivedUnit;
2122
	}
2123

    
2124
//	/**
2125
//	 * Returns the derived unit itself.
2126
//	 *
2127
//	 * @return the derived unit
2128
//	 */
2129
//	public DerivedUnit innerDerivedUnit(boolean createIfNotExists) {
2130
//		DerivedUnit result = this.derivedUnit;
2131
//		if (result == null && createIfNotExists){
2132
//			if (this.fieldUnit == null){
2133
//				String message = "Field unit must exist to create derived unit.";
2134
//				throw new IllegalStateException(message);
2135
//			}else{
2136
//				DerivedUnit =
2137
//				DerivationEvent derivationEvent = getDerivationEvent(true);
2138
//				derivationEvent.addOriginal(fieldUnit);
2139
//				return this.derivedUnit;
2140
//			}
2141
//		}
2142
//	}
2143

    
2144
	private boolean hasDerivationEvent() {
2145
		return getDerivationEvent() == null ? false : true;
2146
	}
2147

    
2148
	private DerivationEvent getDerivationEvent() {
2149
		return getDerivationEvent(CREATE_NOT);
2150
	}
2151

    
2152
	/**
2153
	 * Returns the derivation event. If no derivation event exists and <code>createIfNotExists</code>
2154
	 * is <code>true</code> a new derivation event is created and returned.
2155
	 * Otherwise <code>null</code> is returned.
2156
	 * @param createIfNotExists
2157
	 */
2158
	private DerivationEvent getDerivationEvent(boolean createIfNotExists) {
2159
		DerivationEvent result = null;
2160
		if (derivedUnit != null){
2161
			result = derivedUnit.getDerivedFrom();
2162
		}else{
2163
			return null;
2164
		}
2165
		if (result == null && createIfNotExists) {
2166
			DerivationEventType type = null;
2167
			if (isAccessioned(derivedUnit)){
2168
				type = DerivationEventType.ACCESSIONING();
2169
			}
2170

    
2171
			result = DerivationEvent.NewInstance(type);
2172
			derivedUnit.setDerivedFrom(result);
2173
		}
2174
		return result;
2175
	}
2176

    
2177
	/**
2178
	 * TODO still unclear which classes do definetly require accessioning.
2179
	 * Only return true for those classes which are clear.
2180
	 * @param derivedUnit
2181
	 * @return
2182
	 */
2183
	private boolean isAccessioned(DerivedUnit derivedUnit) {
2184
		if (derivedUnit.getRecordBasis().equals(SpecimenOrObservationType.PreservedSpecimen) ){
2185
			return true;   //maybe also subtypes should be true
2186
		}else{
2187
			return false;
2188
		}
2189
	}
2190

    
2191
	@Transient
2192
	public String getExsiccatum() throws MethodNotSupportedByDerivedUnitTypeException {
2193
		testDerivedUnit();
2194
		if (derivedUnit.getRecordBasis().isPreservedSpecimen()) {
2195
			return derivedUnit.getExsiccatum();
2196
		} else {
2197
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2198
				throw new MethodNotSupportedByDerivedUnitTypeException(
2199
						"An exsiccatum is only available in derived units of type 'Specimen' or 'Fossil'");
2200
			} else {
2201
				return null;
2202
			}
2203
		}
2204
	}
2205

    
2206
	public void setExsiccatum(String exsiccatum) throws Exception {
2207
		testDerivedUnit();
2208
		if (derivedUnit.getRecordBasis().isPreservedSpecimen()) {
2209
			derivedUnit.setExsiccatum(exsiccatum);
2210
		} else {
2211
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2212
				throw new MethodNotSupportedByDerivedUnitTypeException(
2213
						"An exsiccatum is only available in derived units of type 'Specimen' or 'Fossil'");
2214
			} else {
2215
				return;
2216
			}
2217
		}
2218
	}
2219

    
2220
	/**
2221
	 * Returns the original label information of the derived unit.
2222
	 * @return
2223
	 */
2224
	@Transient
2225
	public String getOriginalLabelInfo() {
2226
		return ! checkDerivedUnit()? null : derivedUnit.getOriginalLabelInfo();
2227
	}
2228
	public void setOriginalLabelInfo(String originalLabelInfo) {
2229
		testDerivedUnit();
2230
		derivedUnit.setOriginalLabelInfo(originalLabelInfo);
2231
	}
2232

    
2233
	// **** sources **/
2234
	public void addSource(IdentifiableSource source) {
2235
		this.baseUnit().addSource(source);
2236
	}
2237

    
2238
	/**
2239
	 * Creates an {@link IOriginalSource orignal source} or type ,
2240
	 * adds it to the specimen and returns it.
2241
	 *
2242
	 * @param reference
2243
	 * @param microReference
2244
	 * @param originalNameString
2245
	 * @return
2246
	 */
2247
	public IdentifiableSource addSource(OriginalSourceType type, Reference reference, String microReference, String originalNameString) {
2248
		IdentifiableSource source = IdentifiableSource.NewInstance(type, null, null, reference, microReference);
2249
		source.setOriginalNameString(originalNameString);
2250
		addSource(source);
2251
		return source;
2252
	}
2253

    
2254
	@Transient
2255
	public Set<IdentifiableSource> getSources() {
2256
		return baseUnit().getSources();
2257
	}
2258

    
2259
	public void removeSource(IdentifiableSource source) {
2260
		this.baseUnit().removeSource(source);
2261
	}
2262

    
2263
	//*** identifiers ***/
2264

    
2265

    
2266
    public void addIdentifier(Identifier identifier) {
2267
        this.baseUnit().addIdentifier(identifier);
2268
    }
2269

    
2270
	@Transient
2271
	public List<Identifier> getIdentifiers() {
2272
	    return baseUnit().getIdentifiers();
2273
	}
2274

    
2275
	public void removeIdentifier(Identifier identifier) {
2276
	    this.baseUnit().removeIdentifier(identifier);
2277
	}
2278

    
2279
	@Transient
2280
	public Set<Rights> getRights() {
2281
		return baseUnit().getRights();
2282
	}
2283

    
2284
	/**
2285
	 * @return the collection
2286
	 */
2287
	@Transient
2288
	public Collection getCollection() {
2289
		return ! checkDerivedUnit()? null :  derivedUnit.getCollection();
2290
	}
2291

    
2292
	/**
2293
	 * @param collection
2294
	 *            the collection to set
2295
	 */
2296
	public void setCollection(Collection collection) {
2297
		testDerivedUnit();
2298
		derivedUnit.setCollection(collection);
2299
	}
2300

    
2301
	// annotation
2302
	public void addAnnotation(Annotation annotation) {
2303
		this.baseUnit().addAnnotation(annotation);
2304
	}
2305

    
2306
	@Transient
2307
	public void getAnnotations() {
2308
		this.baseUnit().getAnnotations();
2309
	}
2310

    
2311
	public void removeAnnotation(Annotation annotation) {
2312
		this.baseUnit().removeAnnotation(annotation);
2313
	}
2314

    
2315
	// ******************************* Events ***************************
2316

    
2317
	//set of events that were currently fired by this facades field unit
2318
	//to avoid recursive fireing of the same event
2319
	private final Set<PropertyChangeEvent> fireingEvents = new HashSet<PropertyChangeEvent>();
2320

    
2321
	/**
2322
	 * @return
2323
	 */
2324
	private void addNewEventPropagationListener(CdmBase listeningObject) {
2325
		//if there is already a listener, don't do anything
2326
		for (PropertyChangeListener listener : this.listeners.keySet()){
2327
			if (listeners.get(listener) == listeningObject){
2328
				return;
2329
			}
2330
		}
2331
		//create new listener
2332
		PropertyChangeListener listener = new PropertyChangeListener() {
2333
			@Override
2334
			public void propertyChange(PropertyChangeEvent event) {
2335
				if (derivedUnit != null){
2336
					derivedUnit.firePropertyChange(event);
2337
				}else{
2338
					if (! event.getSource().equals(fieldUnit) && ! fireingEvents.contains(event)  ){
2339
						fireingEvents.add(event);
2340
						fieldUnit.firePropertyChange(event);
2341
						fireingEvents.remove(event);
2342
					}
2343
				}
2344
			}
2345
		};
2346
		//add listener to listening object and to list of listeners
2347
		listeningObject.addPropertyChangeListener(listener);
2348
		listeners.put(listener, listeningObject);
2349
	}
2350

    
2351
	// **************** Other Collections ********************************
2352

    
2353
	/**
2354
	 * Creates a duplicate specimen which derives from the same derivation event
2355
	 * as the facade specimen and adds collection data to it (all data available
2356
	 * in DerivedUnit and Specimen. Data from SpecimenOrObservationBase and
2357
	 * above are not yet shared at the moment.
2358
	 *
2359
	 * @param collection
2360
	 * @param catalogNumber
2361
	 * @param accessionNumber
2362
	 * @param storedUnder
2363
	 * @param preservation
2364
	 * @return
2365
	 */
2366
	public DerivedUnit addDuplicate(Collection collection, String catalogNumber,
2367
			String accessionNumber, TaxonNameBase storedUnder, PreservationMethod preservation){
2368
		testDerivedUnit();
2369
		DerivedUnit duplicate = DerivedUnit.NewPreservedSpecimenInstance();
2370
		duplicate.setDerivedFrom(getDerivationEvent(CREATE));
2371
		duplicate.setCollection(collection);
2372
		duplicate.setCatalogNumber(catalogNumber);
2373
		duplicate.setAccessionNumber(accessionNumber);
2374
		duplicate.setStoredUnder(storedUnder);
2375
		duplicate.setPreservation(preservation);
2376
		return duplicate;
2377
	}
2378

    
2379
	public void addDuplicate(DerivedUnit duplicateSpecimen) {
2380
		testDerivedUnit();
2381
		getDerivationEvent(CREATE).addDerivative(duplicateSpecimen);
2382
	}
2383

    
2384
	@Transient
2385
	public Set<DerivedUnit> getDuplicates() {
2386
		if (! checkDerivedUnit()){
2387
			return new HashSet<DerivedUnit>();
2388
		}
2389
		Set<DerivedUnit> result = new HashSet<DerivedUnit>();
2390
		if (hasDerivationEvent()) {
2391
			for (DerivedUnit derivedUnit : getDerivationEvent(CREATE)
2392
					.getDerivatives()) {
2393
				if (derivedUnit.isInstanceOf(DerivedUnit.class)
2394
						&& !derivedUnit.equals(this.derivedUnit)) {
2395
					result.add(CdmBase.deproxy(derivedUnit, DerivedUnit.class));
2396
				}
2397
			}
2398
		}
2399
		return result;
2400
	}
2401

    
2402
	public void removeDuplicate(DerivedUnit duplicateSpecimen) {
2403
		testDerivedUnit();
2404
		if (hasDerivationEvent()) {
2405
			getDerivationEvent(CREATE).removeDerivative(duplicateSpecimen);
2406
		}
2407
	}
2408

    
2409
	public SpecimenOrObservationBase<?> baseUnit(){
2410
	    if(derivedUnit!=null){
2411
	        return derivedUnit;
2412
	    }
2413
	    else if(fieldUnit!=null){
2414
	        return fieldUnit;
2415
	    }
2416
	    else{
2417
	        throw new IllegalStateException("A DerivedUnitFacade must always have either a field unit or a derived unit");
2418
	    }
2419
	}
2420

    
2421

    
2422
	private boolean checkDerivedUnit()  {
2423
		if (derivedUnit == null){
2424
			return false;
2425
		}else{
2426
			return true;
2427
		}
2428
	}
2429

    
2430
	private void testDerivedUnit() /* throws MethodNotSupportedByDerivedUnitTypeException */ {
2431
		if (derivedUnit == null){
2432
			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 unit");
2433
		}
2434
	}
2435

    
2436
	public void setType(SpecimenOrObservationType type) {
2437
		if (type == null){
2438
			throw new IllegalArgumentException("The type of a specimen or observation may not be null");
2439
		}
2440
		SpecimenOrObservationBase<?> baseUnit = baseUnit();
2441
		if(baseUnit.isInstanceOf(FieldUnit.class) && !type.isFieldUnit()){
2442
		    throw new IllegalArgumentException("A FieldUnit may only be of type FieldUnit") ;
2443
		}
2444
		else if(baseUnit.isInstanceOf(DerivedUnit.class) && type.isFieldUnit()){
2445
		    throw new IllegalArgumentException("A derived unit may not be of type FieldUnit") ;
2446
		}
2447
		baseUnit.setRecordBasis(type);
2448
	}
2449

    
2450
	public SpecimenOrObservationType getType() {
2451
	    return baseUnit().getRecordBasis();
2452
	}
2453

    
2454
	/**
2455
	 * Closes this facade. As a minimum this method removes all listeners created by this facade from their
2456
	 * listening objects.
2457
	 */
2458
	public void close(){
2459
		for (PropertyChangeListener listener : this.listeners.keySet()){
2460
			CdmBase listeningObject = listeners.get(listener);
2461
			listeningObject.removePropertyChangeListener(listener);
2462
		}
2463
	}
2464

    
2465

    
2466
	/**
2467
	 * Computes the correct distance string for given values for min, max and text.
2468
	 * If text is not blank, text is returned, otherwise "min - max" or a single value is returned.
2469
	 * @param min min value as number
2470
	 * @param max max value as number
2471
	 * @param text text representation of distance
2472
	 * @return the formatted distance string
2473
	 */
2474
	private String distanceString(Number min, Number max, String text, String unit) {
2475
		if (StringUtils.isNotBlank(text)){
2476
			return text;
2477
		}else{
2478
			String minStr = min == null? null : String.valueOf(min);
2479
			String maxStr = max == null? null : String.valueOf(max);
2480
			String result = CdmUtils.concat(UTF8.EN_DASH_SPATIUM.toString(), minStr, maxStr);
2481
			if (StringUtils.isNotBlank(result) && StringUtils.isNotBlank(unit)){
2482
				result = result + " " + unit;
2483
			}
2484
			return result;
2485
		}
2486
	}
2487

    
2488
	/**
2489
	 * First checks the inner field unit for the publish flag. If set to <code>true</code>
2490
	 * then <code>true</code> is returned. If the field unit is <code>null</code> the inner derived unit
2491
	 * is checked.
2492
	 * @return <code>true</code> if this facade can be published
2493
	 */
2494
	public boolean isPublish(){
2495
	    if(fieldUnit!=null){
2496
	        return fieldUnit.isPublish();
2497
	    }
2498
	    if(derivedUnit!=null){
2499
	        return derivedUnit.isPublish();
2500
	    }
2501
	    return false;
2502
	}
2503
}
(1-1/6)