Project

General

Profile

Download (77.2 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
 * Copyright (C) 2007 EDIT
3
 * European Distributed Institute of Taxonomy
4
 * http://www.e-taxonomy.eu
5
 *
6
 * The contents of this file are subject to the Mozilla Public License Version 1.1
7
 * See LICENSE.TXT at the top of this package for the full license terms.
8
 */
9

    
10
package eu.etaxonomy.cdm.api.facade;
11

    
12
import java.beans.PropertyChangeEvent;
13
import java.beans.PropertyChangeListener;
14
import java.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.IdentifiableSource;
36
import eu.etaxonomy.cdm.model.common.Identifier;
37
import eu.etaxonomy.cdm.model.common.Language;
38
import eu.etaxonomy.cdm.model.common.LanguageString;
39
import eu.etaxonomy.cdm.model.common.TimePeriod;
40
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
41
import eu.etaxonomy.cdm.model.description.Feature;
42
import eu.etaxonomy.cdm.model.description.SpecimenDescription;
43
import eu.etaxonomy.cdm.model.description.TextData;
44
import eu.etaxonomy.cdm.model.location.NamedArea;
45
import eu.etaxonomy.cdm.model.location.Point;
46
import eu.etaxonomy.cdm.model.location.ReferenceSystem;
47
import eu.etaxonomy.cdm.model.media.Media;
48
import eu.etaxonomy.cdm.model.media.Rights;
49
import eu.etaxonomy.cdm.model.name.TaxonName;
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.DeterminationEvent;
55
import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
56
import eu.etaxonomy.cdm.model.occurrence.GatheringEvent;
57
import eu.etaxonomy.cdm.model.occurrence.PreservationMethod;
58
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
59
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;
60
import eu.etaxonomy.cdm.model.reference.IOriginalSource;
61
import eu.etaxonomy.cdm.model.reference.OriginalSourceType;
62
import eu.etaxonomy.cdm.model.reference.Reference;
63
import eu.etaxonomy.cdm.model.term.DefinedTerm;
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
 * @since 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<>();
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
	private TextData lifeform;
102

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

    
114
	   /**
115
     * Creates a derived unit facade for a new derived unit of type
116
     * {@link SpecimenOrObservationType#PreservedSpecimen}.
117
     *
118
     * @return the derived unit facade
119
     */
120
    public static DerivedUnitFacade NewPreservedSpecimenInstance() {
121
        return new DerivedUnitFacade(SpecimenOrObservationType.PreservedSpecimen, null, 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
	 * @return
130
	 */
131
	public static DerivedUnitFacade NewInstance(SpecimenOrObservationType type, FieldUnit fieldUnit) {
132
		return new DerivedUnitFacade(type, fieldUnit, null);
133
	}
134

    
135
	/**
136
	 * Creates a derived unit facade for a new derived unit of type
137
	 * <code>type</code>.
138
	 *
139
	 * @param type
140
	 * @param fieldUnit the field unit to use
141
	 * @param config the facade configurator to use
142
	 * //TODO are there any ambiguities to solve with defining a field unit or a configurator
143
	 * @return
144
	 */
145
	public static DerivedUnitFacade NewInstance(SpecimenOrObservationType type, FieldUnit fieldUnit, DerivedUnitFacadeConfigurator config) {
146
		return new DerivedUnitFacade(type, fieldUnit, config);
147
	}
148

    
149

    
150
	/**
151
	 * Creates a derived unit facade for a given derived unit using the default
152
	 * configuration.
153
	 *
154
	 * @param derivedUnit
155
	 * @return
156
	 * @throws DerivedUnitFacadeNotSupportedException
157
	 */
158
	public static DerivedUnitFacade NewInstance(DerivedUnit derivedUnit)
159
			throws DerivedUnitFacadeNotSupportedException {
160
		return new DerivedUnitFacade(derivedUnit, null);
161
	}
162

    
163
	public static DerivedUnitFacade NewInstance(DerivedUnit derivedUnit,
164
			DerivedUnitFacadeConfigurator config)
165
			throws DerivedUnitFacadeNotSupportedException {
166
		return new DerivedUnitFacade(derivedUnit, config);
167
	}
168

    
169
	// ****************** CONSTRUCTOR ******************************************
170

    
171
	private DerivedUnitFacade(SpecimenOrObservationType type, FieldUnit fieldUnit, DerivedUnitFacadeConfigurator config) {
172
		if (config == null){
173
			config = DerivedUnitFacadeConfigurator.NewInstance();
174
		}
175
		this.config = config;
176
		// derivedUnit
177
		derivedUnit = getNewDerivedUnitInstance(type);
178
		//TODO parameter checking should be solved in a more generic way if we start using other entity facades
179
		if(derivedUnit==null && fieldUnit==null && type.isFieldUnit()){
180
		    this.fieldUnit = getFieldUnit(CREATE);
181
		}
182
		setFieldUnit(fieldUnit);
183
		if (derivedUnit != null){
184
			setCacheStrategy();
185
		}else{
186
			setFieldUnitCacheStrategy();
187
		}
188
	}
189

    
190
	private DerivedUnit getNewDerivedUnitInstance(SpecimenOrObservationType type) {
191
		if (type.isFieldUnit()){
192
			return null;
193
		}else if(type.isAnyDerivedUnit()){
194
			return DerivedUnit.NewInstance(type);
195
		} else {
196
			String message = "Unknown specimen or observation type %s";
197
			message = String.format(message, type.getMessage());
198
			throw new IllegalStateException(message);
199
		}
200
	}
201

    
202
	private DerivedUnitFacade(DerivedUnit derivedUnit, DerivedUnitFacadeConfigurator config)
203
			throws DerivedUnitFacadeNotSupportedException {
204

    
205
	    if(derivedUnit==null){
206
	        throw new IllegalArgumentException("DerivedUnit must not be null");
207
	    }
208

    
209
		if (config == null) {
210
			config = DerivedUnitFacadeConfigurator.NewInstance();
211
		}
212
		this.config = config;
213

    
214
		// derived unit
215
		this.derivedUnit = derivedUnit;
216

    
217
		// derivation event
218
		if (this.derivedUnit.getDerivedFrom() != null) {
219
			DerivationEvent derivationEvent = getDerivationEvent(CREATE);
220
			// fieldUnit
221
			Set<FieldUnit> fieldOriginals = getFieldUnitOriginals(derivationEvent, null);
222
			if (fieldOriginals.size() > 1) {
223
				throw new DerivedUnitFacadeNotSupportedException(
224
						"Specimen must not have more than 1 derivation event");
225
			} else if (fieldOriginals.size() == 0) {
226
				// fieldUnit = FieldUnit.NewInstance();
227
			} else if (fieldOriginals.size() == 1) {
228
				fieldUnit = fieldOriginals.iterator().next();
229
				// ###fieldUnit =
230
				// getInitializedFieldUnit(fieldUnit);
231
				if (config.isFirePropertyChangeEvents()){
232
					addNewEventPropagationListener(fieldUnit);
233
				}
234
			} else {
235
				throw new IllegalStateException("Illegal state");
236
			}
237
		}
238

    
239
		this.derivedUnitMediaTextData = inititializeTextDataWithSupportTest(Feature.IMAGE(), this.derivedUnit, false, true);
240

    
241
		fieldObjectMediaTextData = initializeFieldObjectTextDataWithSupportTest(Feature.IMAGE(), false, true);
242

    
243

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

    
276
		// test if descriptions are supported
277
		ecology = initializeFieldObjectTextDataWithSupportTest(
278
				Feature.ECOLOGY(), false, false);
279
		plantDescription = initializeFieldObjectTextDataWithSupportTest(
280
				Feature.DESCRIPTION(), false, false);
281

    
282
		setCacheStrategy();
283

    
284
	}
285

    
286
	private DerivedUnit getInitializedDerivedUnit(
287
			DerivedUnit derivedUnit) {
288
		IOccurrenceService occurrenceService = this.config
289
				.getOccurrenceService();
290
		if (occurrenceService == null) {
291
			return derivedUnit;
292
		}
293
		List<String> propertyPaths = this.config.getPropertyPaths();
294
		if (propertyPaths == null) {
295
			return derivedUnit;
296
		}
297
		propertyPaths = getDerivedUnitPropertyPaths(propertyPaths);
298
		DerivedUnit result = (DerivedUnit) occurrenceService.load(
299
				derivedUnit.getUuid(), propertyPaths);
300
		return result;
301
	}
302

    
303
	/**
304
	 * Initializes the derived unit according to the configuartions property
305
	 * path. If the property path is <code>null</code> or no occurrence service
306
	 * is given the returned object is the same as the input parameter.
307
	 *
308
	 * @param fieldUnit
309
	 * @return
310
	 */
311
	private FieldUnit getInitializedFieldUnit(FieldUnit fieldUnit) {
312
		IOccurrenceService occurrenceService = this.config
313
				.getOccurrenceService();
314
		if (occurrenceService == null) {
315
			return fieldUnit;
316
		}
317
		List<String> propertyPaths = this.config.getPropertyPaths();
318
		if (propertyPaths == null) {
319
			return fieldUnit;
320
		}
321
		propertyPaths = getFieldObjectPropertyPaths(propertyPaths);
322
		FieldUnit result = (FieldUnit) occurrenceService.load(
323
				fieldUnit.getUuid(), propertyPaths);
324
		return result;
325
	}
326

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

    
367
			// *********** FIELD OBJECT ************
368
			// fieldObjectDefinitions (Map<language, languageString)
369
			else if (facadePath.startsWith("fieldObjectDefinitions")) {
370
				// TODO or definition ???
371
				facadePath = facadePath.replace("fieldObjectDefinitions",
372
						"description");
373
				result.add(facadePath);
374
			}
375
			// fieldObjectMedia (Media)
376
			else if (facadePath.startsWith("fieldObjectMedia")) {
377
				// TODO ???
378
				facadePath = facadePath.replace("fieldObjectMedia",
379
						"descriptions.elements.media");
380
				result.add(facadePath);
381
			}
382

    
383
			// Gathering Event will always be added
384
			result.add("gatheringEvent");
385

    
386
		}
387

    
388
		/*
389
		 * Gathering Event ==================== - gatheringEvent
390
		 * (GatheringEvent)
391
		 *
392
		 * Field Object ================= - ecology/ ecologyAll (String) ??? -
393
		 * plant description (like ecology)
394
		 *
395
		 * - fieldObjectImageGallery (SpecimenDescription) - is automatically
396
		 * initialized via fieldObjectMedia
397
		 */
398

    
399
		return result;
400
	}
401

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

    
443
			// *********** FIELD OBJECT ************
444
			// derivedUnitDefinitions (Map<language, languageString)
445
			else if (facadePath.startsWith("derivedUnitDefinitions")) {
446
				// TODO or definition ???
447
				facadePath = facadePath.replace("derivedUnitDefinitions",
448
						"description");
449
				result.add(facadePath);
450
			}
451

    
452
			// derivedUnitMedia (Media)
453
			else if (facadePath.startsWith("derivedUnitMedia")) {
454
				// TODO ???
455
				facadePath = facadePath.replace("derivedUnitMedia",
456
						"descriptions.elements.media");
457
				result.add(facadePath);
458
			}
459

    
460
		}
461

    
462
		/*
463
		 * //TODO Derived Unit =====================
464
		 *
465
		 * - derivedUnitImageGallery (SpecimenDescription) - is automatically
466
		 * initialized via derivedUnitMedia
467
		 *
468
		 * - derivationEvent (DerivationEvent) - will always be initialized -
469
		 * duplicates (??? Specimen???) ???
470
		 */
471

    
472
		return result;
473
	}
474

    
475
	/**
476
	 *
477
	 */
478
	private void setCacheStrategy() {
479
		if (derivedUnit == null) {
480
			throw new NullPointerException(
481
					"Facade's derviedUnit must not be null to set cache strategy");
482
		}else{
483
			derivedUnit.setCacheStrategy(new DerivedUnitFacadeCacheStrategy());
484
			setFieldUnitCacheStrategy();
485
		}
486
	}
487

    
488
	private void setFieldUnitCacheStrategy() {
489
		if (this.hasFieldObject()){
490
			DerivedUnitFacadeFieldUnitCacheStrategy strategy = new DerivedUnitFacadeFieldUnitCacheStrategy();
491
			this.fieldUnit.setCacheStrategy(strategy);
492
		}
493
	}
494

    
495
	/**
496
	 * @param feature
497
	 * @param createIfNotExists
498
	 * @param isImageGallery
499
	 * @return
500
	 * @throws DerivedUnitFacadeNotSupportedException
501
	 */
502
	private TextData initializeFieldObjectTextDataWithSupportTest(
503
			Feature feature, boolean createIfNotExists, boolean isImageGallery)
504
			throws DerivedUnitFacadeNotSupportedException {
505
		// field object
506
		FieldUnit fieldObject = getFieldUnit(createIfNotExists);
507
		if (fieldObject == null) {
508
			return null;
509
		}
510
		return inititializeTextDataWithSupportTest(feature, fieldObject,
511
				createIfNotExists, isImageGallery);
512
	}
513

    
514
	/**
515
	 * @param feature
516
	 * @param specimen
517
	 * @param createIfNotExists
518
	 * @param isImageGallery
519
	 * @return
520
	 * @throws DerivedUnitFacadeNotSupportedException
521
	 */
522
	private TextData inititializeTextDataWithSupportTest(Feature feature,
523
			SpecimenOrObservationBase<?> specimen, boolean createIfNotExists,
524
			boolean isImageGallery)
525
			throws DerivedUnitFacadeNotSupportedException {
526
		if (feature == null) {
527
			return null;
528
		}
529
		TextData textData = null;
530
		if (createIfNotExists) {
531
			textData = TextData.NewInstance(feature);
532
		}
533

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

    
569
		} else if (existingTextData.size() == 1) {
570
			return CdmBase.deproxy(existingTextData.iterator().next(),
571
					TextData.class);
572
		} else {
573
			if (createIfNotExists) {
574
				SpecimenDescription description = descriptions.iterator()
575
						.next();
576
				description.addElement(textData);
577
			}
578
			return textData;
579
		}
580
	}
581

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

    
619
	// ************************** METHODS
620
	// *****************************************
621

    
622
	private TextData getDerivedUnitImageGalleryTextData(
623
			boolean createIfNotExists)
624
			throws DerivedUnitFacadeNotSupportedException {
625
		if (this.derivedUnitMediaTextData == null && createIfNotExists) {
626
			this.derivedUnitMediaTextData = getImageGalleryTextData(
627
					derivedUnit, "Specimen");
628
		}
629
		return this.derivedUnitMediaTextData;
630
	}
631

    
632
	private TextData getObservationImageGalleryTextData(
633
			boolean createIfNotExists)
634
			throws DerivedUnitFacadeNotSupportedException {
635
		if (this.fieldObjectMediaTextData == null && createIfNotExists) {
636
			this.fieldObjectMediaTextData = getImageGalleryTextData(fieldUnit, "Field unit");
637
		}
638
		return this.fieldObjectMediaTextData;
639
	}
640

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

    
681
		}
682
		return result;
683
	}
684

    
685
	// *********** MEDIA METHODS ******************************
686

    
687
	// /**
688
	// * Returns the media list for a specimen. Throws an exception if the
689
	// existing specimen descriptions
690
	// * are not supported by this facade.
691
	// * @param specimen the specimen the media belongs to
692
	// * @param specimenExceptionText text describing the specimen for exception
693
	// messages
694
	// * @return
695
	// * @throws DerivedUnitFacadeNotSupportedException
696
	// */
697
	// private List<Media> getImageGalleryMedia(SpecimenOrObservationBase
698
	// specimen, String specimenExceptionText) throws
699
	// DerivedUnitFacadeNotSupportedException{
700
	// List<Media> result;
701
	// SpecimenDescription imageGallery =
702
	// getImageGalleryWithSupportTest(specimen, specimenExceptionText, true);
703
	// TextData textData = getImageTextDataWithSupportTest(imageGallery,
704
	// specimenExceptionText);
705
	// result = textData.getMedia();
706
	// return result;
707
	// }
708

    
709
	/**
710
	 * Returns the media list for a specimen. Throws an exception if the
711
	 * existing specimen descriptions are not supported by this facade.
712
	 *
713
	 * @param specimen
714
	 *            the specimen the media belongs to
715
	 * @param specimenExceptionText
716
	 *            text describing the specimen for exception messages
717
	 * @return
718
	 * @throws DerivedUnitFacadeNotSupportedException
719
	 */
720
	private TextData getImageGalleryTextData(SpecimenOrObservationBase specimen, String specimenExceptionText)
721
			throws DerivedUnitFacadeNotSupportedException {
722
		TextData result;
723
		SpecimenDescription imageGallery = getImageGalleryWithSupportTest(
724
				specimen, specimenExceptionText, true);
725
		result = getImageTextDataWithSupportTest(imageGallery,
726
				specimenExceptionText);
727
		return result;
728
	}
729

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

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

    
797
	/**
798
	 * Checks, if a specimen belongs to more than one description that is an
799
	 * image gallery
800
	 *
801
	 * @param derivedUnit
802
	 * @return
803
	 */
804
	private boolean hasMultipleImageGalleries(
805
			SpecimenOrObservationBase<?> derivedUnit) {
806
		int count = 0;
807
		Set<SpecimenDescription> descriptions = derivedUnit
808
				.getSpecimenDescriptions();
809
		for (SpecimenDescription description : descriptions) {
810
			if (description.isImageGallery()) {
811
				count++;
812
			}
813
		}
814
		return (count > 1);
815
	}
816

    
817
	/**
818
	 * Returns the image gallery for a specimen. If there are multiple specimen
819
	 * descriptions marked as image galleries an arbitrary one is chosen. If no
820
	 * image gallery exists, a new one is created if
821
	 * <code>createNewIfNotExists</code> is <code>true</code>.<Br>
822
	 * If specimen is <code>null</code> a null pointer exception is thrown.
823
	 *
824
	 * @param createNewIfNotExists
825
	 * @return
826
	 */
827
	private SpecimenDescription getImageGallery(SpecimenOrObservationBase<?> specimen, boolean createIfNotExists) {
828
		SpecimenDescription result = null;
829
		Set<SpecimenDescription> descriptions = specimen.getSpecimenDescriptions();
830
		for (SpecimenDescription description : descriptions) {
831
			if (description.isImageGallery()) {
832
				result = description;
833
				break;
834
			}
835
		}
836
		if (result == null && createIfNotExists) {
837
			result = SpecimenDescription.NewInstance(specimen);
838
			result.setImageGallery(true);
839
		}
840
		return result;
841
	}
842

    
843
	/**
844
	 * Adds a media to the specimens image gallery. If media is
845
	 * <code>null</code> nothing happens.
846
	 *
847
	 * @param media
848
	 * @param specimen
849
	 * @return true if media is not null (as specified by
850
	 *         {@link java.util.Collection#add(Object) Collection.add(E e)}
851
	 * @throws DerivedUnitFacadeNotSupportedException
852
	 */
853
	private boolean addMedia(Media media, SpecimenOrObservationBase<?> specimen) throws DerivedUnitFacadeNotSupportedException {
854
		if (media != null) {
855
			List<Media> mediaList = getMediaList(specimen, true);
856
			if (! mediaList.contains(media)){
857
				return mediaList.add(media);
858
			}else{
859
				return true;
860
			}
861
		} else {
862
			return false;
863
		}
864
	}
865

    
866
	/**
867
	 * Removes a media from the specimens image gallery.
868
	 *
869
	 * @param media
870
	 * @param specimen
871
	 * @return true if an element was removed as a result of this call (as
872
	 *         specified by {@link java.util.Collection#remove(Object)
873
	 *         Collection.remove(E e)}
874
	 * @throws DerivedUnitFacadeNotSupportedException
875
	 */
876
	private boolean removeMedia(Media media,
877
			SpecimenOrObservationBase<?> specimen)
878
			throws DerivedUnitFacadeNotSupportedException {
879
		List<Media> mediaList = getMediaList(specimen, true);
880
		return mediaList == null ? null : mediaList.remove(media);
881
	}
882

    
883
	private List<Media> getMediaList(SpecimenOrObservationBase<?> specimen, boolean createIfNotExists)
884
			throws DerivedUnitFacadeNotSupportedException {
885
		TextData textData = getMediaTextData(specimen, createIfNotExists);
886
		return textData == null ? null : textData.getMedia();
887
	}
888

    
889
	/**
890
	 * Returns the one media list of a specimen which is part of the only image
891
	 * gallery that this specimen is part of.<BR>
892
	 * If these conditions are not hold an exception is thrwon.
893
	 *
894
	 * @param specimen
895
	 * @return
896
	 * @throws DerivedUnitFacadeNotSupportedException
897
	 */
898
	// private List<Media> getMedia(SpecimenOrObservationBase<?> specimen)
899
	// throws DerivedUnitFacadeNotSupportedException {
900
	// if (specimen == null){
901
	// return null;
902
	// }
903
	// if (specimen == this.derivedUnit){
904
	// return getDerivedUnitImageGalleryMedia();
905
	// }else if (specimen == this.fieldUnit){
906
	// return getObservationImageGalleryTextData();
907
	// }else{
908
	// return getImageGalleryMedia(specimen, "Undefined specimen ");
909
	// }
910
	// }
911

    
912
	/**
913
	 * Returns the one media list of a specimen which is part of the only image
914
	 * gallery that this specimen is part of.<BR>
915
	 * If these conditions are not hold an exception is thrown.
916
	 *
917
	 * @param specimen
918
	 * @return
919
	 * @throws DerivedUnitFacadeNotSupportedException
920
	 */
921
	private TextData getMediaTextData(SpecimenOrObservationBase<?> specimen,
922
			boolean createIfNotExists)
923
			throws DerivedUnitFacadeNotSupportedException {
924
		if (specimen == null) {
925
			return null;
926
		}
927
		if (specimen == this.derivedUnit) {
928
			return getDerivedUnitImageGalleryTextData(createIfNotExists);
929
		} else if (specimen == this.fieldUnit) {
930
			return getObservationImageGalleryTextData(createIfNotExists);
931
		} else {
932
			return getImageGalleryTextData(specimen, "Undefined specimen ");
933
		}
934
	}
935

    
936
	// ****************** GETTER / SETTER / ADDER / REMOVER
937
	// ***********************/
938

    
939
	// ****************** Gathering Event *********************************/
940

    
941
	// country
942
	@Transient
943
	public NamedArea getCountry() {
944
		return (hasGatheringEvent() ? getGatheringEvent(true).getCountry()
945
				: null);
946
	}
947

    
948
	public void setCountry(NamedArea country) {
949
		getGatheringEvent(true).setCountry(country);
950
	}
951

    
952
	// Collecting area
953
	public void addCollectingArea(NamedArea area) {
954
		getGatheringEvent(true).addCollectingArea(area);
955
	}
956

    
957
	public void addCollectingAreas(java.util.Collection<NamedArea> areas) {
958
		for (NamedArea area : areas) {
959
			getGatheringEvent(true).addCollectingArea(area);
960
		}
961
	}
962

    
963
	@Transient
964
	public Set<NamedArea> getCollectingAreas() {
965
		return (hasGatheringEvent() ? getGatheringEvent(true)
966
				.getCollectingAreas() : null);
967
	}
968

    
969
	public void removeCollectingArea(NamedArea area) {
970
		if (hasGatheringEvent()) {
971
			getGatheringEvent(true).removeCollectingArea(area);
972
		}
973
	}
974

    
975
	static final String ALTITUDE_POSTFIX = " m";
976

    
977
	/**
978
	 * Returns the correctly formatted <code>absolute elevation</code> information.
979
	 * If absoluteElevationText is set, this will be returned,
980
	 * otherwise we absoluteElevation will be returned, followed by absoluteElevationMax
981
	 * if existing, separated by " - "
982
	 * @return
983
	 */
984
	@Transient
985
	public String absoluteElevationToString() {
986
		if (! hasGatheringEvent()){
987
			return null;
988
		}else{
989
			GatheringEvent ev = getGatheringEvent(true);
990
			if (StringUtils.isNotBlank(ev.getAbsoluteElevationText())){
991
				return ev.getAbsoluteElevationText();
992
			}else{
993
				String text = ev.getAbsoluteElevationText();
994
				Integer min = getAbsoluteElevation();
995
				Integer max = getAbsoluteElevationMaximum();
996
				return distanceString(min, max, text, METER);
997
			}
998
		}
999
	}
1000

    
1001

    
1002
	/**
1003
	 * meter above/below sea level of the surface
1004
	 *
1005
	 * @see #getAbsoluteElevationError()
1006
	 * @see #getAbsoluteElevationRange()
1007
	 **/
1008
	@Transient
1009
	public Integer getAbsoluteElevation() {
1010
		return (hasGatheringEvent() ? getGatheringEvent(true).getAbsoluteElevation() : null);
1011
	}
1012

    
1013
	public void setAbsoluteElevation(Integer absoluteElevation) {
1014
		getGatheringEvent(true).setAbsoluteElevation(absoluteElevation);
1015
	}
1016

    
1017
	public void setAbsoluteElevationMax(Integer absoluteElevationMax) {
1018
		getGatheringEvent(true).setAbsoluteElevationMax(absoluteElevationMax);
1019
	}
1020

    
1021
	public void setAbsoluteElevationText(String absoluteElevationText) {
1022
		getGatheringEvent(true).setAbsoluteElevationText(absoluteElevationText);
1023
	}
1024

    
1025
	/**
1026
	 * @see #getAbsoluteElevation()
1027
	 * @see #getAbsoluteElevationError()
1028
	 * @see #setAbsoluteElevationRange(Integer, Integer)
1029
	 * @see #getAbsoluteElevationMinimum()
1030
	 */
1031
	@Transient
1032
	public Integer getAbsoluteElevationMaximum() {
1033
		if (!hasGatheringEvent()) {
1034
			return null;
1035
		}else{
1036
			return getGatheringEvent(true).getAbsoluteElevationMax();
1037
		}
1038
	}
1039

    
1040
	/**
1041
	 * @see #getAbsoluteElevation()
1042
	 * @see #getAbsoluteElevationError()
1043
	 * @see #setAbsoluteElevationRange(Integer, Integer)
1044
	 * @see #getAbsoluteElevationMinimum()
1045
	 */
1046
	@Transient
1047
	public String getAbsoluteElevationText() {
1048
		if (!hasGatheringEvent()) {
1049
			return null;
1050
		}else{
1051
			return getGatheringEvent(true).getAbsoluteElevationText();
1052
		}
1053
	}
1054

    
1055
	/**
1056
	 * Convenience method to set absolute elevation minimum and maximum.
1057
	 *
1058
	 * @see #setAbsoluteElevation(Integer)
1059
	 * @see #setAbsoluteElevationMax(Integer)
1060
	 * @param minimumElevation minimum of the range
1061
	 * @param maximumElevation maximum of the range
1062
	 */
1063
	public void setAbsoluteElevationRange(Integer minimumElevation, Integer maximumElevation) {
1064
		getGatheringEvent(true).setAbsoluteElevation(minimumElevation);
1065
		getGatheringEvent(true).setAbsoluteElevationMax(maximumElevation);
1066
	}
1067

    
1068
	// collector
1069
	@Transient
1070
	public AgentBase getCollector() {
1071
		return (hasGatheringEvent() ? getGatheringEvent(true).getCollector()
1072
				: null);
1073
	}
1074

    
1075
	public void setCollector(AgentBase collector) {
1076
		getGatheringEvent(true).setCollector(collector);
1077
	}
1078

    
1079
	// collecting method
1080
	@Transient
1081
	public String getCollectingMethod() {
1082
		return (hasGatheringEvent() ? getGatheringEvent(true).getCollectingMethod() : null);
1083
	}
1084

    
1085
	public void setCollectingMethod(String collectingMethod) {
1086
		getGatheringEvent(true).setCollectingMethod(collectingMethod);
1087
	}
1088

    
1089
	// distance to ground
1090

    
1091
	/**
1092
	 * Returns the correctly formatted <code>distance to ground</code> information.
1093
	 * If distanceToGroundText is not blank, it will be returned,
1094
	 * otherwise distanceToGround will be returned, followed by distanceToGroundMax
1095
	 * if existing, separated by " - "
1096
	 * @return
1097
	 */
1098
	@Transient
1099
	public String distanceToGroundToString() {
1100
		if (! hasGatheringEvent()){
1101
			return null;
1102
		}else{
1103
			GatheringEvent ev = getGatheringEvent(true);
1104
			String text = ev.getDistanceToGroundText();
1105
			Double min = getDistanceToGround();
1106
			Double max = getDistanceToGroundMax();
1107
			return distanceString(min, max, text, METER);
1108
		}
1109
	}
1110

    
1111
	@Transient
1112
	public Double getDistanceToGround() {
1113
		return (hasGatheringEvent() ? getGatheringEvent(true).getDistanceToGround() : null);
1114
	}
1115

    
1116
	public void setDistanceToGround(Double distanceToGround) {
1117
		getGatheringEvent(true).setDistanceToGround(distanceToGround);
1118
	}
1119

    
1120
	/**
1121
	 * @see #getDistanceToGround()
1122
	 * @see #getDistanceToGroundRange(Integer, Integer)
1123
	 */
1124
	@Transient
1125
	public Double getDistanceToGroundMax() {
1126
		if (!hasGatheringEvent()) {
1127
			return null;
1128
		}else{
1129
			return getGatheringEvent(true).getDistanceToGroundMax();
1130
		}
1131
	}
1132

    
1133
	public void setDistanceToGroundMax(Double distanceToGroundMax) {
1134
		getGatheringEvent(true).setDistanceToGroundMax(distanceToGroundMax);
1135
	}
1136

    
1137
	/**
1138
	 * @see #getDistanceToGround()
1139
	 * @see #setDistanceToGroundRange(Integer, Integer)
1140
	 */
1141
	@Transient
1142
	public String getDistanceToGroundText() {
1143
		if (!hasGatheringEvent()) {
1144
			return null;
1145
		}else{
1146
			return getGatheringEvent(true).getDistanceToGroundText();
1147
		}
1148
	}
1149
	public void setDistanceToGroundText(String distanceToGroundText) {
1150
		getGatheringEvent(true).setDistanceToGroundText(distanceToGroundText);
1151
	}
1152

    
1153
	/**
1154
	 * Convenience method to set distance to ground minimum and maximum.
1155
	 *
1156
	 * @see #getDistanceToGround()
1157
	 * @see #getDistanceToGroundMax()
1158
	 * @param minimumDistance minimum of the range
1159
	 * @param maximumDistance maximum of the range
1160
	 */
1161
	public void setDistanceToGroundRange(Double minimumDistance, Double maximumDistance) throws IllegalArgumentException{
1162
		getGatheringEvent(true).setDistanceToGround(minimumDistance);
1163
		getGatheringEvent(true).setDistanceToGroundMax(maximumDistance);
1164
	}
1165

    
1166

    
1167
	/**
1168
	 * Returns the correctly formatted <code>distance to water surface</code> information.
1169
	 * If distanceToWaterSurfaceText is not blank, it will be returned,
1170
	 * otherwise distanceToWaterSurface will be returned, followed by distanceToWatersurfaceMax
1171
	 * if existing, separated by " - "
1172
	 * @return
1173
	 */
1174
	@Transient
1175
	public String distanceToWaterSurfaceToString() {
1176
		if (! hasGatheringEvent()){
1177
			return null;
1178
		}else{
1179
			GatheringEvent ev = getGatheringEvent(true);
1180
			String text = ev.getDistanceToWaterSurfaceText();
1181
			Double min = getDistanceToWaterSurface();
1182
			Double max = getDistanceToWaterSurfaceMax();
1183
			return distanceString(min, max, text, METER);
1184
		}
1185
	}
1186

    
1187
	// distance to water surface
1188
	@Transient
1189
	public Double getDistanceToWaterSurface() {
1190
		return (hasGatheringEvent() ? getGatheringEvent(true).getDistanceToWaterSurface() : null);
1191
	}
1192

    
1193
	public void setDistanceToWaterSurface(Double distanceToWaterSurface) {
1194
		getGatheringEvent(true).setDistanceToWaterSurface(distanceToWaterSurface);
1195
	}
1196

    
1197
	/**
1198
	 * @see #getDistanceToWaterSurface()
1199
	 * @see #getDistanceToWaterSurfaceRange(Double, Double)
1200
	 */
1201
	@Transient
1202
	public Double getDistanceToWaterSurfaceMax() {
1203
		if (!hasGatheringEvent()) {
1204
			return null;
1205
		}else{
1206
			return getGatheringEvent(true).getDistanceToWaterSurfaceMax();
1207
		}
1208
	}
1209

    
1210
	public void setDistanceToWaterSurfaceMax(Double distanceToWaterSurfaceMax) {
1211
		getGatheringEvent(true).setDistanceToWaterSurfaceMax(distanceToWaterSurfaceMax);
1212
	}
1213

    
1214
	/**
1215
	 * @see #getDistanceToWaterSurface()
1216
	 * @see #getDistanceToWaterSurfaceRange(Double, Double)
1217
	 */
1218
	@Transient
1219
	public String getDistanceToWaterSurfaceText() {
1220
		if (!hasGatheringEvent()) {
1221
			return null;
1222
		}else{
1223
			return getGatheringEvent(true).getDistanceToWaterSurfaceText();
1224
		}
1225
	}
1226
	public void setDistanceToWaterSurfaceText(String distanceToWaterSurfaceText) {
1227
		getGatheringEvent(true).setDistanceToWaterSurfaceText(distanceToWaterSurfaceText);
1228
	}
1229

    
1230
	/**
1231
	 * Convenience method to set distance to ground minimum and maximum.
1232
	 *
1233
	 * @see #getDistanceToWaterSurface()
1234
	 * @see #getDistanceToWaterSurfaceMax()
1235
	 * @param minimumDistance minimum of the range, this is the distance which is closer to the water surface
1236
	 * @param maximumDistance maximum of the range, this is the distance which is farer to the water surface
1237
	 */
1238
	public void setDistanceToWaterSurfaceRange(Double minimumDistance, Double maximumDistance) throws IllegalArgumentException{
1239
		getGatheringEvent(true).setDistanceToWaterSurface(minimumDistance);
1240
		getGatheringEvent(true).setDistanceToWaterSurfaceMax(maximumDistance);
1241
	}
1242

    
1243

    
1244
	// exact location
1245
	@Transient
1246
	public Point getExactLocation() {
1247
		return (hasGatheringEvent() ? getGatheringEvent(true).getExactLocation() : null);
1248
	}
1249

    
1250
	/**
1251
	 * Returns a sexagesimal representation of the exact location (e.g.
1252
	 * 12°59'N, 35°23E). If the exact location is <code>null</code> the empty
1253
	 * string is returned.
1254
	 *
1255
	 * @param includeEmptySeconds
1256
	 * @param includeReferenceSystem
1257
	 * @return
1258
	 */
1259
	public String getExactLocationText(boolean includeEmptySeconds,
1260
			boolean includeReferenceSystem) {
1261
		return (this.getExactLocation() == null ? "" : this.getExactLocation()
1262
				.toSexagesimalString(includeEmptySeconds,
1263
						includeReferenceSystem));
1264
	}
1265

    
1266
	public void setExactLocation(Point exactLocation) {
1267
		getGatheringEvent(true).setExactLocation(exactLocation);
1268
	}
1269

    
1270
	public void setExactLocationByParsing(String longitudeToParse,
1271
			String latitudeToParse, ReferenceSystem referenceSystem,
1272
			Integer errorRadius) throws ParseException {
1273
		Point point = Point.NewInstance(null, null, referenceSystem,
1274
				errorRadius);
1275
		point.setLongitudeByParsing(longitudeToParse);
1276
		point.setLatitudeByParsing(latitudeToParse);
1277
		setExactLocation(point);
1278
	}
1279

    
1280
	// gathering event description
1281
	@Transient
1282
	public String getGatheringEventDescription() {
1283
		return (hasGatheringEvent() ? getGatheringEvent(true).getDescription()
1284
				: null);
1285
	}
1286

    
1287
	public void setGatheringEventDescription(String description) {
1288
		getGatheringEvent(true).setDescription(description);
1289
	}
1290

    
1291
	// gatering period
1292
	@Transient
1293
	public TimePeriod getGatheringPeriod() {
1294
		return (hasGatheringEvent() ? getGatheringEvent(true).getTimeperiod()
1295
				: null);
1296
	}
1297

    
1298
	public void setGatheringPeriod(TimePeriod timeperiod) {
1299
		getGatheringEvent(true).setTimeperiod(timeperiod);
1300
	}
1301

    
1302
	// locality
1303
	@Transient
1304
	public LanguageString getLocality() {
1305
		return (hasGatheringEvent() ? getGatheringEvent(true).getLocality()
1306
				: null);
1307
	}
1308

    
1309
	/**
1310
	 * convienience method for {@link #getLocality()}.
1311
	 * {@link LanguageString#getText() getText()}
1312
	 *
1313
	 * @return
1314
	 */
1315
	@Transient
1316
	public String getLocalityText() {
1317
		LanguageString locality = getLocality();
1318
		if (locality != null) {
1319
			return locality.getText();
1320
		}
1321
		return null;
1322
	}
1323

    
1324
	/**
1325
	 * convienience method for {@link #getLocality()}.
1326
	 * {@link LanguageString#getLanguage() getLanguage()}
1327
	 *
1328
	 * @return
1329
	 */
1330
	@Transient
1331
	public Language getLocalityLanguage() {
1332
		LanguageString locality = getLocality();
1333
		if (locality != null) {
1334
			return locality.getLanguage();
1335
		}
1336
		return null;
1337
	}
1338

    
1339
	/**
1340
	 * Sets the locality string in the default language
1341
	 *
1342
	 * @param locality
1343
	 */
1344
	public void setLocality(String locality) {
1345
		Language language = Language.DEFAULT();
1346
		setLocality(locality, language);
1347
	}
1348

    
1349
	public void setLocality(String locality, Language language) {
1350
		LanguageString langString = LanguageString.NewInstance(locality, language);
1351
		setLocality(langString);
1352
	}
1353

    
1354
	public void setLocality(LanguageString locality) {
1355
		getGatheringEvent(true).setLocality(locality);
1356
	}
1357

    
1358
	/**
1359
	 * The gathering event will be used for the field object instead of the old
1360
	 * gathering event.<BR>
1361
	 * <B>This method will override all gathering values (see below).</B>
1362
	 *
1363
	 * @see #getAbsoluteElevation()
1364
	 * @see #getAbsoluteElevationError()
1365
	 * @see #getDistanceToGround()
1366
	 * @see #getDistanceToWaterSurface()
1367
	 * @see #getExactLocation()
1368
	 * @see #getGatheringEventDescription()
1369
	 * @see #getGatheringPeriod()
1370
	 * @see #getCollectingAreas()
1371
	 * @see #getCollectingMethod()
1372
	 * @see #getLocality()
1373
	 * @see #getCollector()
1374
	 * @param gatheringEvent
1375
	 */
1376
	public void setGatheringEvent(GatheringEvent gatheringEvent) {
1377
		getFieldUnit(true).setGatheringEvent(gatheringEvent);
1378
	}
1379

    
1380
	public boolean hasGatheringEvent() {
1381
		return (getGatheringEvent(false) != null);
1382
	}
1383

    
1384
	public GatheringEvent innerGatheringEvent() {
1385
		return getGatheringEvent(false);
1386
	}
1387

    
1388
	public GatheringEvent getGatheringEvent(boolean createIfNotExists) {
1389
		if (!hasFieldUnit() && !createIfNotExists) {
1390
			return null;
1391
		}
1392
		if (createIfNotExists && getFieldUnit(true).getGatheringEvent() == null) {
1393
			GatheringEvent gatheringEvent = GatheringEvent.NewInstance();
1394
			getFieldUnit(true).setGatheringEvent(gatheringEvent);
1395
		}
1396
		return getFieldUnit(true).getGatheringEvent();
1397
	}
1398

    
1399
	// ****************** Field Object ************************************/
1400

    
1401
	/**
1402
	 * Returns true if a field unit exists (even if all attributes are
1403
	 * empty or <code>null<code>.
1404
	 *
1405
	 * @return
1406
	 */
1407
	public boolean hasFieldObject() {
1408
		return this.fieldUnit != null;
1409
	}
1410

    
1411
	// ecology
1412
	@Transient
1413
	public String getEcology() {
1414
		return getEcology(Language.DEFAULT());
1415
	}
1416

    
1417
	public String getEcology(Language language) {
1418
		LanguageString languageString = getEcologyAll().get(language);
1419
		return (languageString == null ? null : languageString.getText());
1420
	}
1421

    
1422
	// public String getEcologyPreferred(List<Language> languages){
1423
	// LanguageString languageString =
1424
	// getEcologyAll().getPreferredLanguageString(languages);
1425
	// return languageString.getText();
1426
	// }
1427
	/**
1428
	 * Returns a copy of the multilanguage text holding the ecology data.
1429
	 *
1430
	 * @see {@link TextData#getMultilanguageText()}
1431
	 * @return
1432
	 */
1433
	@Transient
1434
	public Map<Language, LanguageString> getEcologyAll() {
1435
		if (ecology == null) {
1436
			try {
1437
				ecology = initializeFieldObjectTextDataWithSupportTest(
1438
						Feature.ECOLOGY(), false, false);
1439
			} catch (DerivedUnitFacadeNotSupportedException e) {
1440
				throw new IllegalStateException(notSupportMessage, e);
1441
			}
1442
			if (ecology == null) {
1443
				return new HashMap<>();
1444
			}
1445
		}
1446
		return ecology.getMultilanguageText();
1447
	}
1448

    
1449
	public void setEcology(String ecology) {
1450
		setEcology(ecology, null);
1451
	}
1452

    
1453
	public void setEcology(String ecologyText, Language language) {
1454
		if (language == null) {
1455
			language = Language.DEFAULT();
1456
		}
1457
		boolean isEmpty = StringUtils.isBlank(ecologyText);
1458
		if (ecology == null) {
1459
			try {
1460
				ecology = initializeFieldObjectTextDataWithSupportTest(
1461
						Feature.ECOLOGY(), !isEmpty, false);
1462
			} catch (DerivedUnitFacadeNotSupportedException e) {
1463
				throw new IllegalStateException(notSupportMessage, e);
1464
			}
1465
		}
1466
		if (ecology != null){
1467
			if (ecologyText == null) {
1468
				ecology.removeText(language);
1469
			} else {
1470
				ecology.putText(language, ecologyText);
1471
			}
1472
		}
1473
	}
1474

    
1475
	public void removeEcology(Language language) {
1476
		setEcology(null, language);
1477
	}
1478

    
1479
	/**
1480
	 * Removes ecology for the default language
1481
	 */
1482
	public void removeEcology() {
1483
		setEcology(null, null);
1484
	}
1485

    
1486
	// plant description
1487
	@Transient
1488
	public String getPlantDescription() {
1489
		return getPlantDescription(null);
1490
	}
1491

    
1492
	public String getPlantDescription(Language language) {
1493
		if (language == null) {
1494
			language = Language.DEFAULT();
1495
		}
1496
		LanguageString languageString = getPlantDescriptionAll().get(language);
1497
		return (languageString == null ? null : languageString.getText());
1498
	}
1499

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

    
1527
	public void setPlantDescription(String plantDescription) {
1528
		setPlantDescription(plantDescription, null);
1529
	}
1530

    
1531
	public void setPlantDescription(String plantDescriptionText, Language language) {
1532
		if (language == null) {
1533
			language = Language.DEFAULT();
1534
		}
1535
		boolean isEmpty = StringUtils.isBlank(plantDescriptionText);
1536
		if (plantDescription == null) {
1537
			try {
1538
				plantDescription = initializeFieldObjectTextDataWithSupportTest(
1539
						Feature.DESCRIPTION(), !isEmpty, false);
1540
			} catch (DerivedUnitFacadeNotSupportedException e) {
1541
				throw new IllegalStateException(notSupportMessage, e);
1542
			}
1543
		}
1544
		if (plantDescription != null){
1545
			if (plantDescriptionText == null) {
1546
				plantDescription.removeText(language);
1547
			} else {
1548
				plantDescription.putText(language, plantDescriptionText);
1549
			}
1550
		}
1551
	}
1552

    
1553
	public void removePlantDescription(Language language) {
1554
		setPlantDescription(null, language);
1555
	}
1556

    
1557
	// life-form
1558
    @Transient
1559
    public String getLifeform() {
1560
        return getLifeform(Language.DEFAULT());
1561
    }
1562

    
1563
    public String getLifeform(Language language) {
1564
        LanguageString languageString = getLifeformAll().get(language);
1565
        return (languageString == null ? null : languageString.getText());
1566
    }
1567

    
1568
    // public String getLifeformPreferred(List<Language> languages){
1569
    // LanguageString languageString =
1570
    // getLifeformAll().getPreferredLanguageString(languages);
1571
    // return languageString.getText();
1572
    // }
1573
    /**
1574
     * Returns a copy of the multi language text holding the life-form data.
1575
     *
1576
     * @see {@link TextData#getMultilanguageText()}
1577
     * @return
1578
     */
1579
    @Transient
1580
    public Map<Language, LanguageString> getLifeformAll() {
1581
        if (lifeform == null) {
1582
            try {
1583
                lifeform = initializeFieldObjectTextDataWithSupportTest(
1584
                        Feature.LIFEFORM(), false, false);
1585
            } catch (DerivedUnitFacadeNotSupportedException e) {
1586
                throw new IllegalStateException(notSupportMessage, e);
1587
            }
1588
            if (lifeform == null) {
1589
                return new HashMap<>();
1590
            }
1591
        }
1592
        return lifeform.getMultilanguageText();
1593
    }
1594

    
1595
    public void setLifeform(String lifeform) {
1596
        setLifeform(lifeform, null);
1597
    }
1598

    
1599
    public void setLifeform(String lifeformText, Language language) {
1600
        if (language == null) {
1601
            language = Language.DEFAULT();
1602
        }
1603
        boolean isEmpty = StringUtils.isBlank(lifeformText);
1604
        if (lifeform == null) {
1605
            try {
1606
                lifeform = initializeFieldObjectTextDataWithSupportTest(
1607
                        Feature.LIFEFORM(), !isEmpty, false);
1608
            } catch (DerivedUnitFacadeNotSupportedException e) {
1609
                throw new IllegalStateException(notSupportMessage, e);
1610
            }
1611
        }
1612
        if (lifeform != null){
1613
            if (lifeformText == null) {
1614
                lifeform.removeText(language);
1615
            } else {
1616
                lifeform.putText(language, lifeformText);
1617
            }
1618
        }
1619
    }
1620

    
1621
    public void removeLifeform(Language language) {
1622
        setLifeform(null, language);
1623
    }
1624

    
1625
    /**
1626
     * Removes life-form for the default language
1627
     */
1628
    public void removeLifeform() {
1629
        setLifeform(null, null);
1630
    }
1631

    
1632
	// field object definition
1633
	public void addFieldObjectDefinition(String text, Language language) {
1634
		getFieldUnit(true).putDefinition(language, text);
1635
	}
1636

    
1637
	@Transient
1638
	public Map<Language, LanguageString> getFieldObjectDefinition() {
1639
		if (!hasFieldUnit()) {
1640
			return new HashMap<>();
1641
		} else {
1642
			return getFieldUnit(true).getDefinition();
1643
		}
1644
	}
1645

    
1646
	public String getFieldObjectDefinition(Language language) {
1647
		Map<Language, LanguageString> map = getFieldObjectDefinition();
1648
		LanguageString languageString = (map == null ? null : map.get(language));
1649
		if (languageString != null) {
1650
			return languageString.getText();
1651
		} else {
1652
			return null;
1653
		}
1654
	}
1655

    
1656
	public void removeFieldObjectDefinition(Language lang) {
1657
		if (hasFieldUnit()) {
1658
			getFieldUnit(true).removeDefinition(lang);
1659
		}
1660
	}
1661

    
1662
	// media
1663
	public boolean addFieldObjectMedia(Media media) {
1664
		try {
1665
			return addMedia(media, getFieldUnit(true));
1666
		} catch (DerivedUnitFacadeNotSupportedException e) {
1667
			throw new IllegalStateException(notSupportMessage, e);
1668
		}
1669
	}
1670

    
1671
	/**
1672
	 * Returns true, if an image gallery for the field object exists.<BR>
1673
	 * Returns also <code>true</code> if the image gallery is empty.
1674
	 *
1675
	 * @return
1676
	 */
1677
	public boolean hasFieldObjectImageGallery() {
1678
		if (!hasFieldObject()) {
1679
			return false;
1680
		} else {
1681
			return (getImageGallery(fieldUnit, false) != null);
1682
		}
1683
	}
1684

    
1685
	public void setFieldObjectImageGallery(SpecimenDescription imageGallery)
1686
			throws DerivedUnitFacadeNotSupportedException {
1687
		SpecimenDescription existingGallery = getFieldObjectImageGallery(false);
1688

    
1689
		// test attached specimens contain this.derivedUnit
1690
		SpecimenOrObservationBase<?> facadeFieldUnit = innerFieldUnit();
1691
		testSpecimenInImageGallery(imageGallery, facadeFieldUnit);
1692

    
1693
		if (existingGallery != null) {
1694
			if (existingGallery != imageGallery) {
1695
				throw new DerivedUnitFacadeNotSupportedException(
1696
						"DerivedUnitFacade does not allow more than one image gallery");
1697
			} else {
1698
				// do nothing
1699
			}
1700
		} else {
1701
			TextData textData = testImageGallery(imageGallery);
1702
			this.fieldObjectMediaTextData = textData;
1703
		}
1704
	}
1705

    
1706
	/**
1707
	 * Returns the field object image gallery. If no such image gallery exists
1708
	 * and createIfNotExists is true an new one is created. Otherwise null is
1709
	 * returned.
1710
	 *
1711
	 * @param createIfNotExists
1712
	 * @return
1713
	 */
1714
	public SpecimenDescription getFieldObjectImageGallery(
1715
			boolean createIfNotExists) {
1716
		TextData textData;
1717
		try {
1718
			textData = initializeFieldObjectTextDataWithSupportTest(
1719
					Feature.IMAGE(), createIfNotExists, true);
1720
		} catch (DerivedUnitFacadeNotSupportedException e) {
1721
			throw new IllegalStateException(notSupportMessage, e);
1722
		}
1723
		if (textData != null) {
1724
			return CdmBase.deproxy(textData.getInDescription(),
1725
					SpecimenDescription.class);
1726
		} else {
1727
			return null;
1728
		}
1729
	}
1730

    
1731
	/**
1732
	 * Returns the media for the field object.<BR>
1733
	 *
1734
	 * @return
1735
	 */
1736
	@Transient
1737
	public List<Media> getFieldObjectMedia() {
1738
		try {
1739
			List<Media> result = getMediaList(getFieldUnit(false), false);
1740
			return result == null ? new ArrayList<>() : result;
1741
		} catch (DerivedUnitFacadeNotSupportedException e) {
1742
			throw new IllegalStateException(notSupportMessage, e);
1743
		}
1744
	}
1745

    
1746
	public boolean removeFieldObjectMedia(Media media) {
1747
		try {
1748
			return removeMedia(media, getFieldUnit(false));
1749
		} catch (DerivedUnitFacadeNotSupportedException e) {
1750
			throw new IllegalStateException(notSupportMessage, e);
1751
		}
1752
	}
1753

    
1754
	// field number
1755
	@Transient
1756
	public String getFieldNumber() {
1757
		if (!hasFieldUnit()) {
1758
			return null;
1759
		} else {
1760
			return getFieldUnit(true).getFieldNumber();
1761
		}
1762
	}
1763

    
1764
	public void setFieldNumber(String fieldNumber) {
1765
		getFieldUnit(true).setFieldNumber(fieldNumber);
1766
	}
1767

    
1768
	// primary collector
1769
	@Transient
1770
	public Person getPrimaryCollector() {
1771
		if (!hasFieldUnit()) {
1772
			return null;
1773
		} else {
1774
			return getFieldUnit(true).getPrimaryCollector();
1775
		}
1776
	}
1777

    
1778
	public void setPrimaryCollector(Person primaryCollector) {
1779
		getFieldUnit(true).setPrimaryCollector(primaryCollector);
1780
	}
1781

    
1782
	// field notes
1783
	@Transient
1784
	public String getFieldNotes() {
1785
		if (!hasFieldUnit()) {
1786
			return null;
1787
		} else {
1788
			return getFieldUnit(true).getFieldNotes();
1789
		}
1790
	}
1791

    
1792
	public void setFieldNotes(String fieldNotes) {
1793
		getFieldUnit(true).setFieldNotes(fieldNotes);
1794
	}
1795

    
1796
	// individual counts
1797
	@Transient
1798
	public String getIndividualCount() {
1799
		return (hasFieldUnit() ? getFieldUnit(true).getIndividualCount() : null);
1800
	}
1801

    
1802
	public void setIndividualCount(String individualCount) {
1803
		getFieldUnit(true).setIndividualCount(individualCount);
1804
	}
1805

    
1806
	// life stage
1807
	@Transient
1808
	public DefinedTerm getLifeStage() {
1809
		return (hasFieldUnit() ? getFieldUnit(true).getLifeStage() : null);
1810
	}
1811

    
1812
	public void setLifeStage(DefinedTerm lifeStage) {
1813
	    FieldUnit fieldUnit = getFieldUnit(lifeStage != null);
1814
        if (fieldUnit != null){
1815
            fieldUnit.setLifeStage(lifeStage);
1816
        }
1817
	}
1818

    
1819
	// sex
1820
	@Transient
1821
	public DefinedTerm getSex() {
1822
		return (hasFieldUnit() ? getFieldUnit(true).getSex(): null);
1823
	}
1824

    
1825
	public void setSex(DefinedTerm sex) {
1826
	    FieldUnit fieldUnit = getFieldUnit(sex != null);
1827
        if (fieldUnit != null){
1828
            fieldUnit.setSex(sex);
1829
        }
1830
	}
1831

    
1832
	// kind of Unit
1833
	@Transient
1834
	public DefinedTerm getFieldUnitKindOfUnit() {
1835
		return (hasFieldUnit() ? fieldUnit.getKindOfUnit() : null);
1836
	}
1837
//
1838
   @Transient
1839
    public DefinedTerm getDerivedUnitKindOfUnit() {
1840
       checkDerivedUnit();
1841
       return checkDerivedUnit() ? derivedUnit.getKindOfUnit() : null;
1842
    }
1843

    
1844

    
1845
	/**
1846
	 * Sets the kind-of-unit
1847
	 * @param kindOfUnit
1848
	 */
1849
	public void setFieldUnitKindOfUnit(DefinedTerm kindOfUnit) {
1850
	    FieldUnit fieldUnit = getFieldUnit(kindOfUnit != null);
1851
	    if (fieldUnit != null){
1852
	        fieldUnit.setKindOfUnit(kindOfUnit);
1853
	    }
1854
	}
1855

    
1856
    public void setDerivedUnitKindOfUnit(DefinedTerm kindOfUnit) {
1857
        testDerivedUnit();
1858

    
1859
        baseUnit().setKindOfUnit(kindOfUnit);
1860
    }
1861

    
1862

    
1863
	// field unit
1864
	public boolean hasFieldUnit() {
1865
		return (getFieldUnit(CREATE_NOT) != null);
1866
	}
1867

    
1868
	/**
1869
	 * Returns the field unit as an object.
1870
	 *
1871
	 * @return
1872
	 */
1873
	public FieldUnit innerFieldUnit() {
1874
		return getFieldUnit(CREATE_NOT);
1875
	}
1876

    
1877
	/**
1878
	 * Returns the field unit as an object.
1879
	 *
1880
	 * @return
1881
	 */
1882
	public FieldUnit getFieldUnit(boolean createIfNotExists) {
1883
		if (fieldUnit == null && createIfNotExists) {
1884
			setFieldUnit(FieldUnit.NewInstance());
1885
		}
1886
		return this.fieldUnit;
1887
	}
1888

    
1889

    
1890
	public void setFieldUnit(FieldUnit fieldUnit) {
1891
		this.fieldUnit = fieldUnit;
1892
		if (fieldUnit != null){
1893
			if (config.isFirePropertyChangeEvents()){
1894
				addNewEventPropagationListener(fieldUnit);
1895
			}
1896
			if (derivedUnit != null){
1897
				DerivationEvent derivationEvent = getDerivationEvent(CREATE);
1898
				derivationEvent.addOriginal(fieldUnit);
1899
			}
1900
			setFieldUnitCacheStrategy();
1901
		}
1902
	}
1903

    
1904
	// ****************** Specimen *******************************************
1905

    
1906

    
1907
	// Definition
1908
	public void addDerivedUnitDefinition(String text, Language language) {
1909
		innerDerivedUnit().putDefinition(language, text);
1910
	}
1911

    
1912
	@Transient
1913
	public Map<Language, LanguageString> getDerivedUnitDefinitions() {
1914
		return ! checkDerivedUnit()? null : this.derivedUnit.getDefinition();
1915
	}
1916

    
1917

    
1918
	public String getDerivedUnitDefinition(Language language) {
1919
		if (! checkDerivedUnit()){
1920
			return null;
1921
		}
1922
		Map<Language, LanguageString> languageMap = derivedUnit.getDefinition();
1923
		LanguageString languageString = languageMap.get(language);
1924
		if (languageString != null) {
1925
			return languageString.getText();
1926
		} else {
1927
			return null;
1928
		}
1929
	}
1930

    
1931
	public void removeDerivedUnitDefinition(Language lang) {
1932
		testDerivedUnit();
1933
		derivedUnit.removeDefinition(lang);
1934
	}
1935

    
1936
	// Determination
1937
	public void addDetermination(DeterminationEvent determination) {
1938
		//TODO implement correct bidirectional mapping in model classes
1939
		determination.setIdentifiedUnit(baseUnit());
1940
		baseUnit().addDetermination(determination);
1941
	}
1942

    
1943
	@Transient
1944
	public DeterminationEvent getPreferredDetermination() {
1945
		Set<DeterminationEvent> events = baseUnit().getDeterminations();
1946
		for (DeterminationEvent event : events){
1947
			if (event.getPreferredFlag() == true){
1948
				return event;
1949
			}
1950
		}
1951
		return null;
1952
	}
1953

    
1954
	/**
1955
	 * This method returns the preferred determination.
1956
	 * @see #getOtherDeterminations()
1957
	 * @see #getDeterminations()
1958
	 * @return
1959
	 */
1960
	@Transient
1961
	public void setPreferredDetermination(DeterminationEvent newEvent) {
1962
		Set<DeterminationEvent> events = baseUnit().getDeterminations();
1963
		for (DeterminationEvent event : events){
1964
			if (event.getPreferredFlag() == true){
1965
				event.setPreferredFlag(false);
1966
			}
1967
		}
1968
		newEvent.setPreferredFlag(true);
1969
		events.add(newEvent);
1970
	}
1971

    
1972
	/**
1973
	 * This method returns all determinations except for the preferred one.
1974
	 * @see #getPreferredDetermination()
1975
	 * @see #getDeterminations()
1976
	 * @return
1977
	 */
1978
	@Transient
1979
	public Set<DeterminationEvent> getOtherDeterminations() {
1980
		Set<DeterminationEvent> events = baseUnit().getDeterminations();
1981
		Set<DeterminationEvent> result = new HashSet<>();
1982
		for (DeterminationEvent event : events){
1983
			if (event.getPreferredFlag() != true){
1984
				result.add(event);
1985
			}
1986
		}
1987
		return result;
1988
	}
1989

    
1990
	/**
1991
	 * This method returns all determination events. The preferred one {@link #getPreferredDetermination()}
1992
	 * and all others {@link #getOtherDeterminations()}.
1993
	 * @return
1994
	 */
1995
	@Transient
1996
	public Set<DeterminationEvent> getDeterminations() {
1997
		return baseUnit().getDeterminations();
1998
	}
1999

    
2000
	public void removeDetermination(DeterminationEvent determination) {
2001
		baseUnit().removeDetermination(determination);
2002
	}
2003

    
2004
	// Media
2005
	public boolean addDerivedUnitMedia(Media media) {
2006
		testDerivedUnit();
2007
		try {
2008
			return addMedia(media, derivedUnit);
2009
		} catch (DerivedUnitFacadeNotSupportedException e) {
2010
			throw new IllegalStateException(notSupportMessage, e);
2011
		}
2012
	}
2013

    
2014
	/**
2015
	 * Returns true, if an image gallery exists for the specimen.<BR>
2016
	 * Returns also <code>true</code> if the image gallery is empty.
2017
	 */
2018
	public boolean hasDerivedUnitImageGallery() {
2019
		return (getImageGallery(derivedUnit, false) != null);
2020
	}
2021

    
2022
	public SpecimenDescription getDerivedUnitImageGallery(boolean createIfNotExists) {
2023
		if (!checkDerivedUnit()){
2024
			return null;
2025
		}
2026
		TextData textData;
2027
		try {
2028
			textData = inititializeTextDataWithSupportTest(Feature.IMAGE(),
2029
					derivedUnit, createIfNotExists, true);
2030
		} catch (DerivedUnitFacadeNotSupportedException e) {
2031
			throw new IllegalStateException(notSupportMessage, e);
2032
		}
2033
		if (textData != null) {
2034
			return CdmBase.deproxy(textData.getInDescription(),
2035
					SpecimenDescription.class);
2036
		} else {
2037
			return null;
2038
		}
2039
	}
2040

    
2041
	public void setDerivedUnitImageGallery(SpecimenDescription imageGallery)
2042
			throws DerivedUnitFacadeNotSupportedException {
2043
		testDerivedUnit();
2044
		SpecimenDescription existingGallery = getDerivedUnitImageGallery(false);
2045

    
2046
		// test attached specimens contain this.derivedUnit
2047
		SpecimenOrObservationBase facadeDerivedUnit = innerDerivedUnit();
2048
		testSpecimenInImageGallery(imageGallery, facadeDerivedUnit);
2049

    
2050
		if (existingGallery != null) {
2051
			if (existingGallery != imageGallery) {
2052
				throw new DerivedUnitFacadeNotSupportedException(
2053
						"DerivedUnitFacade does not allow more than one image gallery");
2054
			} else {
2055
				// do nothing
2056
			}
2057
		} else {
2058
			TextData textData = testImageGallery(imageGallery);
2059
			this.derivedUnitMediaTextData = textData;
2060
		}
2061
	}
2062

    
2063
	/**
2064
	 * @param imageGallery
2065
	 * @throws DerivedUnitFacadeNotSupportedException
2066
	 */
2067
	private void testSpecimenInImageGallery(SpecimenDescription imageGallery, SpecimenOrObservationBase specimen)
2068
				throws DerivedUnitFacadeNotSupportedException {
2069
		SpecimenOrObservationBase imageGallerySpecimen = imageGallery.getDescribedSpecimenOrObservation();
2070
		if (imageGallerySpecimen == null) {
2071
			throw new DerivedUnitFacadeNotSupportedException(
2072
					"Image Gallery has no Specimen attached. Please attache according specimen or field unit.");
2073
		}
2074
		if (! imageGallerySpecimen.equals(specimen)) {
2075
			throw new DerivedUnitFacadeNotSupportedException(
2076
					"Image Gallery has not the facade's field object attached. Please add field object first " +
2077
					"to image gallery specimenOrObservation list.");
2078
		}
2079
	}
2080

    
2081
	/**
2082
	 * Returns the media for the specimen.<BR>
2083
	 *
2084
	 * @return
2085
	 */
2086
	@Transient
2087
	public List<Media> getDerivedUnitMedia() {
2088
		if (! checkDerivedUnit()){
2089
			return new ArrayList<Media>();
2090
		}
2091
		try {
2092
			List<Media> result = getMediaList(derivedUnit, false);
2093
			return result == null ? new ArrayList<>() : result;
2094
		} catch (DerivedUnitFacadeNotSupportedException e) {
2095
			throw new IllegalStateException(notSupportMessage, e);
2096
		}
2097
	}
2098

    
2099
	public boolean removeDerivedUnitMedia(Media media) {
2100
		testDerivedUnit();
2101
		try {
2102
			return removeMedia(media, derivedUnit);
2103
		} catch (DerivedUnitFacadeNotSupportedException e) {
2104
			throw new IllegalStateException(notSupportMessage, e);
2105
		}
2106
	}
2107

    
2108
	// Accession Number
2109
	@Transient
2110
	public String getAccessionNumber() {
2111
		return ! checkDerivedUnit()? null : derivedUnit.getAccessionNumber();
2112
	}
2113

    
2114
	public void setAccessionNumber(String accessionNumber) {
2115
		testDerivedUnit();
2116
		derivedUnit.setAccessionNumber(accessionNumber);
2117
	}
2118

    
2119
	@Transient
2120
	public String getCatalogNumber() {
2121
		return ! checkDerivedUnit()? null : derivedUnit.getCatalogNumber();
2122
	}
2123

    
2124
	public void setCatalogNumber(String catalogNumber) {
2125
		testDerivedUnit();
2126
		derivedUnit.setCatalogNumber(catalogNumber);
2127
	}
2128

    
2129
	@Transient
2130
	public String getBarcode() {
2131
		return ! checkDerivedUnit()? null : derivedUnit.getBarcode();
2132
	}
2133

    
2134
	public void setBarcode(String barcode) {
2135
		testDerivedUnit();
2136
		derivedUnit.setBarcode(barcode);
2137
	}
2138

    
2139
	// Preservation Method
2140

    
2141
	/**
2142
	 * Only supported by specimen and fossils
2143
	 *
2144
	 * @see #DerivedUnitType
2145
	 * @return
2146
	 */
2147
	@Transient
2148
	public PreservationMethod getPreservationMethod() throws MethodNotSupportedByDerivedUnitTypeException {
2149
		if (derivedUnit!=null && derivedUnit.getRecordBasis().isPreservedSpecimen()) {
2150
			return CdmBase.deproxy(derivedUnit, DerivedUnit.class).getPreservation();
2151
		} else {
2152
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2153
				throw new MethodNotSupportedByDerivedUnitTypeException(
2154
						"A preservation method is only available in derived units of type 'Preserved Specimen' or one of its specializations like 'Fossil Specimen' ");
2155
			} else {
2156
				return null;
2157
			}
2158
		}
2159
	}
2160

    
2161
	/**
2162
	 * Only supported by specimen and fossils
2163
	 *
2164
	 * @see #DerivedUnitType
2165
	 * @return
2166
	 */
2167
	public void setPreservationMethod(PreservationMethod preservation)
2168
			throws MethodNotSupportedByDerivedUnitTypeException {
2169
		if (derivedUnit!=null && derivedUnit.getRecordBasis().isPreservedSpecimen()) {
2170
			CdmBase.deproxy(derivedUnit, DerivedUnit.class).setPreservation(preservation);
2171
		} else {
2172
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2173
				throw new MethodNotSupportedByDerivedUnitTypeException(
2174
						"A preservation method is only available in derived units of type 'Specimen' or 'Fossil'");
2175
			} else {
2176
				return;
2177
			}
2178
		}
2179
	}
2180

    
2181
	//preferred stable URI  #5606
2182
	@Transient
2183
    public URI getPreferredStableUri(){
2184
        return baseUnit().getPreferredStableUri();
2185
    }
2186
    public void setPreferredStableUri(URI stableUri){
2187
        baseUnit().setPreferredStableUri(stableUri);
2188
    }
2189

    
2190

    
2191
	// Stored under name
2192
	@Transient
2193
	public TaxonName getStoredUnder() {
2194
		return ! checkDerivedUnit()? null : derivedUnit.getStoredUnder();
2195
	}
2196

    
2197
	public void setStoredUnder(TaxonName storedUnder) {
2198
		testDerivedUnit();
2199
		derivedUnit.setStoredUnder(storedUnder);
2200
	}
2201

    
2202
	// title cache
2203
	public String getTitleCache() {
2204
		SpecimenOrObservationBase<?> titledUnit = baseUnit();
2205

    
2206
		if (!titledUnit.isProtectedTitleCache()) {
2207
			// always compute title cache anew as long as there are no property
2208
			// change listeners on
2209
			// field unit, gathering event etc
2210
			titledUnit.setTitleCache(null, false);
2211
		}
2212
		return titledUnit.getTitleCache();
2213
	}
2214

    
2215
	public boolean isProtectedTitleCache() {
2216
		return baseUnit().isProtectedTitleCache();
2217
	}
2218

    
2219
	public void setTitleCache(String titleCache, boolean isProtected) {
2220
		this.baseUnit().setTitleCache(titleCache, isProtected);
2221
	}
2222

    
2223
	/**
2224
	 * Returns the derived unit itself.
2225
	 *
2226
	 * @return the derived unit
2227
	 */
2228
	public DerivedUnit innerDerivedUnit() {
2229
		return this.derivedUnit;
2230
	}
2231

    
2232
//	/**
2233
//	 * Returns the derived unit itself.
2234
//	 *
2235
//	 * @return the derived unit
2236
//	 */
2237
//	public DerivedUnit innerDerivedUnit(boolean createIfNotExists) {
2238
//		DerivedUnit result = this.derivedUnit;
2239
//		if (result == null && createIfNotExists){
2240
//			if (this.fieldUnit == null){
2241
//				String message = "Field unit must exist to create derived unit.";
2242
//				throw new IllegalStateException(message);
2243
//			}else{
2244
//				DerivedUnit =
2245
//				DerivationEvent derivationEvent = getDerivationEvent(true);
2246
//				derivationEvent.addOriginal(fieldUnit);
2247
//				return this.derivedUnit;
2248
//			}
2249
//		}
2250
//	}
2251

    
2252
	private boolean hasDerivationEvent() {
2253
		return getDerivationEvent() == null ? false : true;
2254
	}
2255

    
2256
	private DerivationEvent getDerivationEvent() {
2257
		return getDerivationEvent(CREATE_NOT);
2258
	}
2259

    
2260
	/**
2261
	 * Returns the derivation event. If no derivation event exists and <code>createIfNotExists</code>
2262
	 * is <code>true</code> a new derivation event is created and returned.
2263
	 * Otherwise <code>null</code> is returned.
2264
	 * @param createIfNotExists
2265
	 */
2266
	private DerivationEvent getDerivationEvent(boolean createIfNotExists) {
2267
		DerivationEvent result = null;
2268
		if (derivedUnit != null){
2269
			result = derivedUnit.getDerivedFrom();
2270
		}else{
2271
			return null;
2272
		}
2273
		if (result == null && createIfNotExists) {
2274
			DerivationEventType type = null;
2275
			if (isAccessioned(derivedUnit)){
2276
				type = DerivationEventType.ACCESSIONING();
2277
			}
2278

    
2279
			result = DerivationEvent.NewInstance(type);
2280
			derivedUnit.setDerivedFrom(result);
2281
		}
2282
		return result;
2283
	}
2284

    
2285
	/**
2286
	 * TODO still unclear which classes do definetly require accessioning.
2287
	 * Only return true for those classes which are clear.
2288
	 * @param derivedUnit
2289
	 * @return
2290
	 */
2291
	private boolean isAccessioned(DerivedUnit derivedUnit) {
2292
		if (derivedUnit.getRecordBasis().equals(SpecimenOrObservationType.PreservedSpecimen) ){
2293
			return true;   //maybe also subtypes should be true
2294
		}else{
2295
			return false;
2296
		}
2297
	}
2298

    
2299
	@Transient
2300
	public String getExsiccatum() throws MethodNotSupportedByDerivedUnitTypeException {
2301
		testDerivedUnit();
2302
		if (derivedUnit.getRecordBasis().isPreservedSpecimen()) {
2303
			return derivedUnit.getExsiccatum();
2304
		} else {
2305
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2306
				throw new MethodNotSupportedByDerivedUnitTypeException(
2307
						"An exsiccatum is only available in derived units of type 'Specimen' or 'Fossil'");
2308
			} else {
2309
				return null;
2310
			}
2311
		}
2312
	}
2313

    
2314
	public void setExsiccatum(String exsiccatum) throws Exception {
2315
		testDerivedUnit();
2316
		if (derivedUnit.getRecordBasis().isPreservedSpecimen()) {
2317
			derivedUnit.setExsiccatum(exsiccatum);
2318
		} else {
2319
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2320
				throw new MethodNotSupportedByDerivedUnitTypeException(
2321
						"An exsiccatum is only available in derived units of type 'Specimen' or 'Fossil'");
2322
			} else {
2323
				return;
2324
			}
2325
		}
2326
	}
2327

    
2328
	/**
2329
	 * Returns the original label information of the derived unit.
2330
	 * @return
2331
	 */
2332
	@Transient
2333
	public String getOriginalLabelInfo() {
2334
		return ! checkDerivedUnit()? null : derivedUnit.getOriginalLabelInfo();
2335
	}
2336
	public void setOriginalLabelInfo(String originalLabelInfo) {
2337
		testDerivedUnit();
2338
		derivedUnit.setOriginalLabelInfo(originalLabelInfo);
2339
	}
2340

    
2341
	// **** sources **/
2342
	public void addSource(IdentifiableSource source) {
2343
		this.baseUnit().addSource(source);
2344
	}
2345

    
2346
	/**
2347
	 * Creates an {@link IOriginalSource orignal source} or type ,
2348
	 * adds it to the specimen and returns it.
2349
	 *
2350
	 * @param reference
2351
	 * @param microReference
2352
	 * @param originalNameString
2353
	 * @return
2354
	 */
2355
	public IdentifiableSource addSource(OriginalSourceType type, Reference reference, String microReference, String originalNameString) {
2356
		IdentifiableSource source = IdentifiableSource.NewInstance(type, null, null, reference, microReference);
2357
		source.setOriginalNameString(originalNameString);
2358
		addSource(source);
2359
		return source;
2360
	}
2361

    
2362
	@Transient
2363
	public Set<IdentifiableSource> getSources() {
2364
		return baseUnit().getSources();
2365
	}
2366

    
2367
	public void removeSource(IdentifiableSource source) {
2368
		this.baseUnit().removeSource(source);
2369
	}
2370

    
2371
	//*** identifiers ***/
2372

    
2373

    
2374
    public void addIdentifier(Identifier identifier) {
2375
        this.baseUnit().addIdentifier(identifier);
2376
    }
2377

    
2378
	@Transient
2379
	public List<Identifier> getIdentifiers() {
2380
	    return baseUnit().getIdentifiers();
2381
	}
2382

    
2383
	public void removeIdentifier(Identifier identifier) {
2384
	    this.baseUnit().removeIdentifier(identifier);
2385
	}
2386

    
2387
	@Transient
2388
	public Set<Rights> getRights() {
2389
		return baseUnit().getRights();
2390
	}
2391

    
2392
	/**
2393
	 * @return the collection
2394
	 */
2395
	@Transient
2396
	public Collection getCollection() {
2397
		return ! checkDerivedUnit()? null :  derivedUnit.getCollection();
2398
	}
2399

    
2400
	/**
2401
	 * @param collection
2402
	 *            the collection to set
2403
	 */
2404
	public void setCollection(Collection collection) {
2405
		testDerivedUnit();
2406
		derivedUnit.setCollection(collection);
2407
	}
2408

    
2409
	// annotation
2410
	public void addAnnotation(Annotation annotation) {
2411
		this.baseUnit().addAnnotation(annotation);
2412
	}
2413

    
2414
	@Transient
2415
	public void getAnnotations() {
2416
		this.baseUnit().getAnnotations();
2417
	}
2418

    
2419
	public void removeAnnotation(Annotation annotation) {
2420
		this.baseUnit().removeAnnotation(annotation);
2421
	}
2422

    
2423
	// ******************************* Events ***************************
2424

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

    
2429
	/**
2430
	 * @return
2431
	 */
2432
	private void addNewEventPropagationListener(CdmBase listeningObject) {
2433
		//if there is already a listener, don't do anything
2434
		for (PropertyChangeListener listener : this.listeners.keySet()){
2435
			if (listeners.get(listener) == listeningObject){
2436
				return;
2437
			}
2438
		}
2439
		//create new listener
2440
		PropertyChangeListener listener = new PropertyChangeListener() {
2441
			@Override
2442
			public void propertyChange(PropertyChangeEvent event) {
2443
				if (derivedUnit != null){
2444
					derivedUnit.firePropertyChange(event);
2445
				}else{
2446
					if (! event.getSource().equals(fieldUnit) && ! fireingEvents.contains(event)  ){
2447
						fireingEvents.add(event);
2448
						fieldUnit.firePropertyChange(event);
2449
						fireingEvents.remove(event);
2450
					}
2451
				}
2452
			}
2453
		};
2454
		//add listener to listening object and to list of listeners
2455
		listeningObject.addPropertyChangeListener(listener);
2456
		listeners.put(listener, listeningObject);
2457
	}
2458

    
2459
	// **************** Other Collections ********************************
2460

    
2461
	/**
2462
	 * Creates a duplicate specimen which derives from the same derivation event
2463
	 * as the facade specimen and adds collection data to it (all data available
2464
	 * in DerivedUnit and Specimen. Data from SpecimenOrObservationBase and
2465
	 * above are not yet shared at the moment.
2466
	 *
2467
	 * @param collection
2468
	 * @param catalogNumber
2469
	 * @param accessionNumber
2470
	 * @param storedUnder
2471
	 * @param preservation
2472
	 * @return
2473
	 */
2474
	public DerivedUnit addDuplicate(Collection collection, String catalogNumber,
2475
			String accessionNumber, TaxonName storedUnder, PreservationMethod preservation){
2476
		testDerivedUnit();
2477
		DerivedUnit duplicate = DerivedUnit.NewPreservedSpecimenInstance();
2478
		duplicate.setDerivedFrom(getDerivationEvent(CREATE));
2479
		duplicate.setCollection(collection);
2480
		duplicate.setCatalogNumber(catalogNumber);
2481
		duplicate.setAccessionNumber(accessionNumber);
2482
		duplicate.setStoredUnder(storedUnder);
2483
		duplicate.setPreservation(preservation);
2484
		return duplicate;
2485
	}
2486

    
2487
	public void addDuplicate(DerivedUnit duplicateSpecimen) {
2488
		testDerivedUnit();
2489
		getDerivationEvent(CREATE).addDerivative(duplicateSpecimen);
2490
	}
2491

    
2492
	@Transient
2493
	public Set<DerivedUnit> getDuplicates() {
2494
		if (! checkDerivedUnit()){
2495
			return new HashSet<>();
2496
		}
2497
		Set<DerivedUnit> result = new HashSet<>();
2498
		if (hasDerivationEvent()) {
2499
			for (DerivedUnit derivedUnit : getDerivationEvent(CREATE)
2500
					.getDerivatives()) {
2501
				if (derivedUnit.isInstanceOf(DerivedUnit.class)
2502
						&& !derivedUnit.equals(this.derivedUnit)) {
2503
					result.add(CdmBase.deproxy(derivedUnit, DerivedUnit.class));
2504
				}
2505
			}
2506
		}
2507
		return result;
2508
	}
2509

    
2510
	public void removeDuplicate(DerivedUnit duplicateSpecimen) {
2511
		testDerivedUnit();
2512
		if (hasDerivationEvent()) {
2513
			getDerivationEvent(CREATE).removeDerivative(duplicateSpecimen);
2514
		}
2515
	}
2516

    
2517
	public SpecimenOrObservationBase<?> baseUnit(){
2518
	    if(derivedUnit!=null){
2519
	        return derivedUnit;
2520
	    }
2521
	    else if(fieldUnit!=null){
2522
	        return fieldUnit;
2523
	    }
2524
	    else{
2525
	        throw new IllegalStateException("A DerivedUnitFacade must always have either a field unit or a derived unit");
2526
	    }
2527
	}
2528

    
2529
	/**
2530
	 * @return true if <code>this.derivedUnit</code> exists
2531
	 */
2532
	private boolean checkDerivedUnit()  {
2533
		if (derivedUnit == null){
2534
			return false;
2535
		}else{
2536
			return true;
2537
		}
2538
	}
2539

    
2540
	private void testDerivedUnit() /* throws MethodNotSupportedByDerivedUnitTypeException */ {
2541
		if (derivedUnit == null){
2542
			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");
2543
		}
2544
	}
2545

    
2546
	public void setType(SpecimenOrObservationType type) {
2547
		if (type == null){
2548
			throw new IllegalArgumentException("The type of a specimen or observation may not be null");
2549
		}
2550
		SpecimenOrObservationBase<?> baseUnit = baseUnit();
2551
		if(baseUnit.isInstanceOf(FieldUnit.class) && !type.isFieldUnit()){
2552
		    throw new IllegalArgumentException("A FieldUnit may only be of type FieldUnit") ;
2553
		}
2554
		else if(baseUnit.isInstanceOf(DerivedUnit.class) && type.isFieldUnit()){
2555
		    throw new IllegalArgumentException("A derived unit may not be of type FieldUnit") ;
2556
		}
2557
		baseUnit.setRecordBasis(type);
2558
	}
2559

    
2560
	public SpecimenOrObservationType getType() {
2561
	    return baseUnit().getRecordBasis();
2562
	}
2563

    
2564
	/**
2565
	 * Closes this facade. As a minimum this method removes all listeners created by this facade from their
2566
	 * listening objects.
2567
	 */
2568
	public void close(){
2569
		for (PropertyChangeListener listener : this.listeners.keySet()){
2570
			CdmBase listeningObject = listeners.get(listener);
2571
			listeningObject.removePropertyChangeListener(listener);
2572
		}
2573
	}
2574

    
2575

    
2576
	/**
2577
	 * Computes the correct distance string for given values for min, max and text.
2578
	 * If text is not blank, text is returned, otherwise "min - max" or a single value is returned.
2579
	 * @param min min value as number
2580
	 * @param max max value as number
2581
	 * @param text text representation of distance
2582
	 * @return the formatted distance string
2583
	 */
2584
	public static String distanceString(Number min, Number max, String text, String unit) {
2585
		if (StringUtils.isNotBlank(text)){
2586
			return text;
2587
		}else{
2588
			String minStr = min == null? null : String.valueOf(min);
2589
			String maxStr = max == null? null : String.valueOf(max);
2590
			String result = CdmUtils.concat(UTF8.EN_DASH_SPATIUM.toString(), minStr, maxStr);
2591
			if (StringUtils.isNotBlank(result) && StringUtils.isNotBlank(unit)){
2592
				result = result + " " + unit;
2593
			}
2594
			return result;
2595
		}
2596
	}
2597

    
2598
	/**
2599
	 * First checks the inner field unit for the publish flag. If set to <code>true</code>
2600
	 * then <code>true</code> is returned. If the field unit is <code>null</code> the inner derived unit
2601
	 * is checked.
2602
	 * @return <code>true</code> if this facade can be published
2603
	 */
2604
	public boolean isPublish(){
2605
	    if(fieldUnit!=null){
2606
	        return fieldUnit.isPublish();
2607
	    }
2608
	    if(derivedUnit!=null){
2609
	        return derivedUnit.isPublish();
2610
	    }
2611
	    return false;
2612
	}
2613
}
(1-1/6)