Project

General

Profile

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

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

    
12
import java.beans.PropertyChangeEvent;
13
import java.beans.PropertyChangeListener;
14
import java.net.URI;
15
import java.text.ParseException;
16
import java.util.ArrayList;
17
import java.util.HashMap;
18
import java.util.HashSet;
19
import java.util.List;
20
import java.util.Map;
21
import java.util.Set;
22

    
23
import javax.persistence.Transient;
24

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

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

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

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

    
82
	private static final String notSupportMessage = "A specimen facade not supported exception has occurred at a place where this should not have happened. The developer should implement not support check properly during class initialization ";
83

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

    
87
	private final DerivedUnitFacadeConfigurator config;
88

    
89
	private final Map<PropertyChangeListener, CdmBase> listeners = new HashMap<>();
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
	 * <code>type</code>.
117
	 *
118
	 * @param type
119
	 * @return
120
	 */
121
	public static DerivedUnitFacade NewInstance(SpecimenOrObservationType type, FieldUnit fieldUnit) {
122
		return new DerivedUnitFacade(type, fieldUnit, null);
123
	}
124

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

    
139

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

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

    
159
	// ****************** CONSTRUCTOR ******************************************
160

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

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

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

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

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

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

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

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

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

    
233

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

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

    
272
		setCacheStrategy();
273

    
274
	}
275

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

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

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

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

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

    
376
		}
377

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

    
389
		return result;
390
	}
391

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

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

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

    
450
		}
451

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

    
462
		return result;
463
	}
464

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

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

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

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

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

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

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

    
609
	// ************************** METHODS
610
	// *****************************************
611

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

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

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

    
671
		}
672
		return result;
673
	}
674

    
675
	// *********** MEDIA METHODS ******************************
676

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

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

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

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

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

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

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

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

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

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

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

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

    
929
	// ****************** Gathering Event *********************************/
930

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

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

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

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

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

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

    
965
	static final String ALTITUDE_POSTFIX = " m";
966

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

    
991

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

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

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

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

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

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

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

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

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

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

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

    
1079
	// distance to ground
1080

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

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

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

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

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

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

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

    
1156

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

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

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

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

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

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

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

    
1233

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1389
	// ****************** Field Object ************************************/
1390

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

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

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

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

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

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

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

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

    
1476
	// plant description
1477
	@Transient
1478
	public String getPlantDescription() {
1479
		return getPlantDescription(null);
1480
	}
1481

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

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

    
1517
	public void setPlantDescription(String plantDescription) {
1518
		setPlantDescription(plantDescription, null);
1519
	}
1520

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

    
1543
	public void removePlantDescription(Language language) {
1544
		setPlantDescription(null, language);
1545
	}
1546

    
1547
	// life-form
1548
    @Transient
1549
    public String getLifeform() {
1550
        return getLifeform(Language.DEFAULT());
1551
    }
1552

    
1553
    public String getLifeform(Language language) {
1554
        LanguageString languageString = getLifeformAll().get(language);
1555
        return (languageString == null ? null : languageString.getText());
1556
    }
1557

    
1558
    // public String getLifeformPreferred(List<Language> languages){
1559
    // LanguageString languageString =
1560
    // getLifeformAll().getPreferredLanguageString(languages);
1561
    // return languageString.getText();
1562
    // }
1563
    /**
1564
     * Returns a copy of the multi language text holding the life-form data.
1565
     *
1566
     * @see {@link TextData#getMultilanguageText()}
1567
     * @return
1568
     */
1569
    @Transient
1570
    public Map<Language, LanguageString> getLifeformAll() {
1571
        if (lifeform == null) {
1572
            try {
1573
                lifeform = initializeFieldObjectTextDataWithSupportTest(
1574
                        Feature.LIFEFORM(), false, false);
1575
            } catch (DerivedUnitFacadeNotSupportedException e) {
1576
                throw new IllegalStateException(notSupportMessage, e);
1577
            }
1578
            if (lifeform == null) {
1579
                return new HashMap<>();
1580
            }
1581
        }
1582
        return lifeform.getMultilanguageText();
1583
    }
1584

    
1585
    public void setLifeform(String lifeform) {
1586
        setLifeform(lifeform, null);
1587
    }
1588

    
1589
    public void setLifeform(String lifeformText, Language language) {
1590
        if (language == null) {
1591
            language = Language.DEFAULT();
1592
        }
1593
        boolean isEmpty = StringUtils.isBlank(lifeformText);
1594
        if (lifeform == null) {
1595
            try {
1596
                lifeform = initializeFieldObjectTextDataWithSupportTest(
1597
                        Feature.LIFEFORM(), !isEmpty, false);
1598
            } catch (DerivedUnitFacadeNotSupportedException e) {
1599
                throw new IllegalStateException(notSupportMessage, e);
1600
            }
1601
        }
1602
        if (lifeform != null){
1603
            if (lifeformText == null) {
1604
                lifeform.removeText(language);
1605
            } else {
1606
                lifeform.putText(language, lifeformText);
1607
            }
1608
        }
1609
    }
1610

    
1611
    public void removeLifeform(Language language) {
1612
        setLifeform(null, language);
1613
    }
1614

    
1615
    /**
1616
     * Removes life-form for the default language
1617
     */
1618
    public void removeLifeform() {
1619
        setLifeform(null, null);
1620
    }
1621

    
1622
	// field object definition
1623
	public void addFieldObjectDefinition(String text, Language language) {
1624
		getFieldUnit(true).putDefinition(language, text);
1625
	}
1626

    
1627
	@Transient
1628
	public Map<Language, LanguageString> getFieldObjectDefinition() {
1629
		if (!hasFieldUnit()) {
1630
			return new HashMap<>();
1631
		} else {
1632
			return getFieldUnit(true).getDefinition();
1633
		}
1634
	}
1635

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

    
1646
	public void removeFieldObjectDefinition(Language lang) {
1647
		if (hasFieldUnit()) {
1648
			getFieldUnit(true).removeDefinition(lang);
1649
		}
1650
	}
1651

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

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

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

    
1679
		// test attached specimens contain this.derivedUnit
1680
		SpecimenOrObservationBase<?> facadeFieldUnit = innerFieldUnit();
1681
		testSpecimenInImageGallery(imageGallery, facadeFieldUnit);
1682

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

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

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

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

    
1744
	// field number
1745
	@Transient
1746
	public String getFieldNumber() {
1747
		if (!hasFieldUnit()) {
1748
			return null;
1749
		} else {
1750
			return getFieldUnit(true).getFieldNumber();
1751
		}
1752
	}
1753

    
1754
	public void setFieldNumber(String fieldNumber) {
1755
		getFieldUnit(true).setFieldNumber(fieldNumber);
1756
	}
1757

    
1758
	// primary collector
1759
	@Transient
1760
	public Person getPrimaryCollector() {
1761
		if (!hasFieldUnit()) {
1762
			return null;
1763
		} else {
1764
			return getFieldUnit(true).getPrimaryCollector();
1765
		}
1766
	}
1767

    
1768
	public void setPrimaryCollector(Person primaryCollector) {
1769
		getFieldUnit(true).setPrimaryCollector(primaryCollector);
1770
	}
1771

    
1772
	// field notes
1773
	@Transient
1774
	public String getFieldNotes() {
1775
		if (!hasFieldUnit()) {
1776
			return null;
1777
		} else {
1778
			return getFieldUnit(true).getFieldNotes();
1779
		}
1780
	}
1781

    
1782
	public void setFieldNotes(String fieldNotes) {
1783
		getFieldUnit(true).setFieldNotes(fieldNotes);
1784
	}
1785

    
1786
	// individual counts
1787
	@Transient
1788
	public Integer getIndividualCount() {
1789
		return (hasFieldUnit() ? getFieldUnit(true).getIndividualCount() : null);
1790
	}
1791

    
1792
	public void setIndividualCount(Integer individualCount) {
1793
		getFieldUnit(true).setIndividualCount(individualCount);
1794
	}
1795

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

    
1802
	public void setLifeStage(DefinedTerm lifeStage) {
1803
	    FieldUnit fieldUnit = getFieldUnit(lifeStage != null);
1804
        if (fieldUnit != null){
1805
            fieldUnit.setLifeStage(lifeStage);
1806
        }
1807
	}
1808

    
1809
	// sex
1810
	@Transient
1811
	public DefinedTerm getSex() {
1812
		return (hasFieldUnit() ? getFieldUnit(true).getSex(): null);
1813
	}
1814

    
1815
	public void setSex(DefinedTerm sex) {
1816
	    FieldUnit fieldUnit = getFieldUnit(sex != null);
1817
        if (fieldUnit != null){
1818
            fieldUnit.setSex(sex);
1819
        }
1820
	}
1821

    
1822
	// kind of Unit
1823
	@Transient
1824
	public DefinedTerm getKindOfUnit() {
1825
		return (hasFieldUnit() ? getFieldUnit(true).getKindOfUnit() : null);
1826
	}
1827
//
1828
//   @Transient
1829
//    public DefinedTerm getDerivedUnitKindOfUnit() {
1830
//       checkDerivedUnit();
1831
//       return checkDerivedUnit() ? derivedUnit.getKindOfUnit() : null;
1832
//    }
1833

    
1834

    
1835
	/**
1836
	 * Sets the kind-of-unit
1837
	 * @param kindOfUnit
1838
	 */
1839
	public void setKindOfUnit(DefinedTerm kindOfUnit) {
1840
	    FieldUnit fieldUnit = getFieldUnit(kindOfUnit != null);
1841
	    if (fieldUnit != null){
1842
	        fieldUnit.setKindOfUnit(kindOfUnit);
1843
	    }
1844
	}
1845

    
1846
//    public void setDerivedUnitKindOfUnit(DefinedTerm kindOfUnit) {
1847
//        testDerivedUnit();
1848
//
1849
//        baseUnit().setKindOfUnit(kindOfUnit);
1850
//    }
1851

    
1852

    
1853
	// field unit
1854
	public boolean hasFieldUnit() {
1855
		return (getFieldUnit(CREATE_NOT) != null);
1856
	}
1857

    
1858
	/**
1859
	 * Returns the field unit as an object.
1860
	 *
1861
	 * @return
1862
	 */
1863
	public FieldUnit innerFieldUnit() {
1864
		return getFieldUnit(CREATE_NOT);
1865
	}
1866

    
1867
	/**
1868
	 * Returns the field unit as an object.
1869
	 *
1870
	 * @return
1871
	 */
1872
	public FieldUnit getFieldUnit(boolean createIfNotExists) {
1873
		if (fieldUnit == null && createIfNotExists) {
1874
			setFieldUnit(FieldUnit.NewInstance());
1875
		}
1876
		return this.fieldUnit;
1877
	}
1878

    
1879

    
1880
	public void setFieldUnit(FieldUnit fieldUnit) {
1881
		this.fieldUnit = fieldUnit;
1882
		if (fieldUnit != null){
1883
			if (config.isFirePropertyChangeEvents()){
1884
				addNewEventPropagationListener(fieldUnit);
1885
			}
1886
			if (derivedUnit != null){
1887
				DerivationEvent derivationEvent = getDerivationEvent(CREATE);
1888
				derivationEvent.addOriginal(fieldUnit);
1889
			}
1890
			setFieldUnitCacheStrategy();
1891
		}
1892
	}
1893

    
1894
	// ****************** Specimen *******************************************
1895

    
1896

    
1897
	// Definition
1898
	public void addDerivedUnitDefinition(String text, Language language) {
1899
		innerDerivedUnit().putDefinition(language, text);
1900
	}
1901

    
1902
	@Transient
1903
	public Map<Language, LanguageString> getDerivedUnitDefinitions() {
1904
		return ! checkDerivedUnit()? null : this.derivedUnit.getDefinition();
1905
	}
1906

    
1907

    
1908
	public String getDerivedUnitDefinition(Language language) {
1909
		if (! checkDerivedUnit()){
1910
			return null;
1911
		}
1912
		Map<Language, LanguageString> languageMap = derivedUnit.getDefinition();
1913
		LanguageString languageString = languageMap.get(language);
1914
		if (languageString != null) {
1915
			return languageString.getText();
1916
		} else {
1917
			return null;
1918
		}
1919
	}
1920

    
1921
	public void removeDerivedUnitDefinition(Language lang) {
1922
		testDerivedUnit();
1923
		derivedUnit.removeDefinition(lang);
1924
	}
1925

    
1926
	// Determination
1927
	public void addDetermination(DeterminationEvent determination) {
1928
		//TODO implement correct bidirectional mapping in model classes
1929
		determination.setIdentifiedUnit(baseUnit());
1930
		baseUnit().addDetermination(determination);
1931
	}
1932

    
1933
	@Transient
1934
	public DeterminationEvent getPreferredDetermination() {
1935
		Set<DeterminationEvent> events = baseUnit().getDeterminations();
1936
		for (DeterminationEvent event : events){
1937
			if (event.getPreferredFlag() == true){
1938
				return event;
1939
			}
1940
		}
1941
		return null;
1942
	}
1943

    
1944
	/**
1945
	 * This method returns the preferred determination.
1946
	 * @see #getOtherDeterminations()
1947
	 * @see #getDeterminations()
1948
	 * @return
1949
	 */
1950
	@Transient
1951
	public void setPreferredDetermination(DeterminationEvent newEvent) {
1952
		Set<DeterminationEvent> events = baseUnit().getDeterminations();
1953
		for (DeterminationEvent event : events){
1954
			if (event.getPreferredFlag() == true){
1955
				event.setPreferredFlag(false);
1956
			}
1957
		}
1958
		newEvent.setPreferredFlag(true);
1959
		events.add(newEvent);
1960
	}
1961

    
1962
	/**
1963
	 * This method returns all determinations except for the preferred one.
1964
	 * @see #getPreferredDetermination()
1965
	 * @see #getDeterminations()
1966
	 * @return
1967
	 */
1968
	@Transient
1969
	public Set<DeterminationEvent> getOtherDeterminations() {
1970
		Set<DeterminationEvent> events = baseUnit().getDeterminations();
1971
		Set<DeterminationEvent> result = new HashSet<>();
1972
		for (DeterminationEvent event : events){
1973
			if (event.getPreferredFlag() != true){
1974
				result.add(event);
1975
			}
1976
		}
1977
		return result;
1978
	}
1979

    
1980
	/**
1981
	 * This method returns all determination events. The preferred one {@link #getPreferredDetermination()}
1982
	 * and all others {@link #getOtherDeterminations()}.
1983
	 * @return
1984
	 */
1985
	@Transient
1986
	public Set<DeterminationEvent> getDeterminations() {
1987
		return baseUnit().getDeterminations();
1988
	}
1989

    
1990
	public void removeDetermination(DeterminationEvent determination) {
1991
		baseUnit().removeDetermination(determination);
1992
	}
1993

    
1994
	// Media
1995
	public boolean addDerivedUnitMedia(Media media) {
1996
		testDerivedUnit();
1997
		try {
1998
			return addMedia(media, derivedUnit);
1999
		} catch (DerivedUnitFacadeNotSupportedException e) {
2000
			throw new IllegalStateException(notSupportMessage, e);
2001
		}
2002
	}
2003

    
2004
	/**
2005
	 * Returns true, if an image gallery exists for the specimen.<BR>
2006
	 * Returns also <code>true</code> if the image gallery is empty.
2007
	 */
2008
	public boolean hasDerivedUnitImageGallery() {
2009
		return (getImageGallery(derivedUnit, false) != null);
2010
	}
2011

    
2012
	public SpecimenDescription getDerivedUnitImageGallery(boolean createIfNotExists) {
2013
		if (!checkDerivedUnit()){
2014
			return null;
2015
		}
2016
		TextData textData;
2017
		try {
2018
			textData = inititializeTextDataWithSupportTest(Feature.IMAGE(),
2019
					derivedUnit, createIfNotExists, true);
2020
		} catch (DerivedUnitFacadeNotSupportedException e) {
2021
			throw new IllegalStateException(notSupportMessage, e);
2022
		}
2023
		if (textData != null) {
2024
			return CdmBase.deproxy(textData.getInDescription(),
2025
					SpecimenDescription.class);
2026
		} else {
2027
			return null;
2028
		}
2029
	}
2030

    
2031
	public void setDerivedUnitImageGallery(SpecimenDescription imageGallery)
2032
			throws DerivedUnitFacadeNotSupportedException {
2033
		testDerivedUnit();
2034
		SpecimenDescription existingGallery = getDerivedUnitImageGallery(false);
2035

    
2036
		// test attached specimens contain this.derivedUnit
2037
		SpecimenOrObservationBase facadeDerivedUnit = innerDerivedUnit();
2038
		testSpecimenInImageGallery(imageGallery, facadeDerivedUnit);
2039

    
2040
		if (existingGallery != null) {
2041
			if (existingGallery != imageGallery) {
2042
				throw new DerivedUnitFacadeNotSupportedException(
2043
						"DerivedUnitFacade does not allow more than one image gallery");
2044
			} else {
2045
				// do nothing
2046
			}
2047
		} else {
2048
			TextData textData = testImageGallery(imageGallery);
2049
			this.derivedUnitMediaTextData = textData;
2050
		}
2051
	}
2052

    
2053
	/**
2054
	 * @param imageGallery
2055
	 * @throws DerivedUnitFacadeNotSupportedException
2056
	 */
2057
	private void testSpecimenInImageGallery(SpecimenDescription imageGallery, SpecimenOrObservationBase specimen)
2058
				throws DerivedUnitFacadeNotSupportedException {
2059
		SpecimenOrObservationBase imageGallerySpecimen = imageGallery.getDescribedSpecimenOrObservation();
2060
		if (imageGallerySpecimen == null) {
2061
			throw new DerivedUnitFacadeNotSupportedException(
2062
					"Image Gallery has no Specimen attached. Please attache according specimen or field unit.");
2063
		}
2064
		if (! imageGallerySpecimen.equals(specimen)) {
2065
			throw new DerivedUnitFacadeNotSupportedException(
2066
					"Image Gallery has not the facade's field object attached. Please add field object first " +
2067
					"to image gallery specimenOrObservation list.");
2068
		}
2069
	}
2070

    
2071
	/**
2072
	 * Returns the media for the specimen.<BR>
2073
	 *
2074
	 * @return
2075
	 */
2076
	@Transient
2077
	public List<Media> getDerivedUnitMedia() {
2078
		if (! checkDerivedUnit()){
2079
			return new ArrayList<Media>();
2080
		}
2081
		try {
2082
			List<Media> result = getMediaList(derivedUnit, false);
2083
			return result == null ? new ArrayList<Media>() : result;
2084
		} catch (DerivedUnitFacadeNotSupportedException e) {
2085
			throw new IllegalStateException(notSupportMessage, e);
2086
		}
2087
	}
2088

    
2089
	public boolean removeDerivedUnitMedia(Media media) {
2090
		testDerivedUnit();
2091
		try {
2092
			return removeMedia(media, derivedUnit);
2093
		} catch (DerivedUnitFacadeNotSupportedException e) {
2094
			throw new IllegalStateException(notSupportMessage, e);
2095
		}
2096
	}
2097

    
2098
	// Accession Number
2099
	@Transient
2100
	public String getAccessionNumber() {
2101
		return ! checkDerivedUnit()? null : derivedUnit.getAccessionNumber();
2102
	}
2103

    
2104
	public void setAccessionNumber(String accessionNumber) {
2105
		testDerivedUnit();
2106
		derivedUnit.setAccessionNumber(accessionNumber);
2107
	}
2108

    
2109
	@Transient
2110
	public String getCatalogNumber() {
2111
		return ! checkDerivedUnit()? null : derivedUnit.getCatalogNumber();
2112
	}
2113

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

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

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

    
2129
	// Preservation Method
2130

    
2131
	/**
2132
	 * Only supported by specimen and fossils
2133
	 *
2134
	 * @see #DerivedUnitType
2135
	 * @return
2136
	 */
2137
	@Transient
2138
	public PreservationMethod getPreservationMethod() throws MethodNotSupportedByDerivedUnitTypeException {
2139
		if (derivedUnit!=null && derivedUnit.getRecordBasis().isPreservedSpecimen()) {
2140
			return CdmBase.deproxy(derivedUnit, DerivedUnit.class).getPreservation();
2141
		} else {
2142
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2143
				throw new MethodNotSupportedByDerivedUnitTypeException(
2144
						"A preservation method is only available in derived units of type 'Preserved Specimen' or one of its specializations like 'Fossil Specimen' ");
2145
			} else {
2146
				return null;
2147
			}
2148
		}
2149
	}
2150

    
2151
	/**
2152
	 * Only supported by specimen and fossils
2153
	 *
2154
	 * @see #DerivedUnitType
2155
	 * @return
2156
	 */
2157
	public void setPreservationMethod(PreservationMethod preservation)
2158
			throws MethodNotSupportedByDerivedUnitTypeException {
2159
		if (derivedUnit!=null && derivedUnit.getRecordBasis().isPreservedSpecimen()) {
2160
			CdmBase.deproxy(derivedUnit, DerivedUnit.class).setPreservation(preservation);
2161
		} else {
2162
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2163
				throw new MethodNotSupportedByDerivedUnitTypeException(
2164
						"A preservation method is only available in derived units of type 'Specimen' or 'Fossil'");
2165
			} else {
2166
				return;
2167
			}
2168
		}
2169
	}
2170

    
2171
	//preferred stable URI  #5606
2172
	@Transient
2173
    public URI getPreferredStableUri(){
2174
        return baseUnit().getPreferredStableUri();
2175
    }
2176
    public void setPreferredStableUri(URI stableUri){
2177
        baseUnit().setPreferredStableUri(stableUri);
2178
    }
2179

    
2180

    
2181
	// Stored under name
2182
	@Transient
2183
	public TaxonName getStoredUnder() {
2184
		return ! checkDerivedUnit()? null : derivedUnit.getStoredUnder();
2185
	}
2186

    
2187
	public void setStoredUnder(TaxonName storedUnder) {
2188
		testDerivedUnit();
2189
		derivedUnit.setStoredUnder(storedUnder);
2190
	}
2191

    
2192
	// title cache
2193
	public String getTitleCache() {
2194
		SpecimenOrObservationBase<?> titledUnit = baseUnit();
2195

    
2196
		if (!titledUnit.isProtectedTitleCache()) {
2197
			// always compute title cache anew as long as there are no property
2198
			// change listeners on
2199
			// field unit, gathering event etc
2200
			titledUnit.setTitleCache(null, false);
2201
		}
2202
		return titledUnit.getTitleCache();
2203
	}
2204

    
2205
	public boolean isProtectedTitleCache() {
2206
		return baseUnit().isProtectedTitleCache();
2207
	}
2208

    
2209
	public void setTitleCache(String titleCache, boolean isProtected) {
2210
		this.baseUnit().setTitleCache(titleCache, isProtected);
2211
	}
2212

    
2213
	/**
2214
	 * Returns the derived unit itself.
2215
	 *
2216
	 * @return the derived unit
2217
	 */
2218
	public DerivedUnit innerDerivedUnit() {
2219
		return this.derivedUnit;
2220
	}
2221

    
2222
//	/**
2223
//	 * Returns the derived unit itself.
2224
//	 *
2225
//	 * @return the derived unit
2226
//	 */
2227
//	public DerivedUnit innerDerivedUnit(boolean createIfNotExists) {
2228
//		DerivedUnit result = this.derivedUnit;
2229
//		if (result == null && createIfNotExists){
2230
//			if (this.fieldUnit == null){
2231
//				String message = "Field unit must exist to create derived unit.";
2232
//				throw new IllegalStateException(message);
2233
//			}else{
2234
//				DerivedUnit =
2235
//				DerivationEvent derivationEvent = getDerivationEvent(true);
2236
//				derivationEvent.addOriginal(fieldUnit);
2237
//				return this.derivedUnit;
2238
//			}
2239
//		}
2240
//	}
2241

    
2242
	private boolean hasDerivationEvent() {
2243
		return getDerivationEvent() == null ? false : true;
2244
	}
2245

    
2246
	private DerivationEvent getDerivationEvent() {
2247
		return getDerivationEvent(CREATE_NOT);
2248
	}
2249

    
2250
	/**
2251
	 * Returns the derivation event. If no derivation event exists and <code>createIfNotExists</code>
2252
	 * is <code>true</code> a new derivation event is created and returned.
2253
	 * Otherwise <code>null</code> is returned.
2254
	 * @param createIfNotExists
2255
	 */
2256
	private DerivationEvent getDerivationEvent(boolean createIfNotExists) {
2257
		DerivationEvent result = null;
2258
		if (derivedUnit != null){
2259
			result = derivedUnit.getDerivedFrom();
2260
		}else{
2261
			return null;
2262
		}
2263
		if (result == null && createIfNotExists) {
2264
			DerivationEventType type = null;
2265
			if (isAccessioned(derivedUnit)){
2266
				type = DerivationEventType.ACCESSIONING();
2267
			}
2268

    
2269
			result = DerivationEvent.NewInstance(type);
2270
			derivedUnit.setDerivedFrom(result);
2271
		}
2272
		return result;
2273
	}
2274

    
2275
	/**
2276
	 * TODO still unclear which classes do definetly require accessioning.
2277
	 * Only return true for those classes which are clear.
2278
	 * @param derivedUnit
2279
	 * @return
2280
	 */
2281
	private boolean isAccessioned(DerivedUnit derivedUnit) {
2282
		if (derivedUnit.getRecordBasis().equals(SpecimenOrObservationType.PreservedSpecimen) ){
2283
			return true;   //maybe also subtypes should be true
2284
		}else{
2285
			return false;
2286
		}
2287
	}
2288

    
2289
	@Transient
2290
	public String getExsiccatum() throws MethodNotSupportedByDerivedUnitTypeException {
2291
		testDerivedUnit();
2292
		if (derivedUnit.getRecordBasis().isPreservedSpecimen()) {
2293
			return derivedUnit.getExsiccatum();
2294
		} else {
2295
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2296
				throw new MethodNotSupportedByDerivedUnitTypeException(
2297
						"An exsiccatum is only available in derived units of type 'Specimen' or 'Fossil'");
2298
			} else {
2299
				return null;
2300
			}
2301
		}
2302
	}
2303

    
2304
	public void setExsiccatum(String exsiccatum) throws Exception {
2305
		testDerivedUnit();
2306
		if (derivedUnit.getRecordBasis().isPreservedSpecimen()) {
2307
			derivedUnit.setExsiccatum(exsiccatum);
2308
		} else {
2309
			if (this.config.isThrowExceptionForNonSpecimenPreservationMethodRequest()) {
2310
				throw new MethodNotSupportedByDerivedUnitTypeException(
2311
						"An exsiccatum is only available in derived units of type 'Specimen' or 'Fossil'");
2312
			} else {
2313
				return;
2314
			}
2315
		}
2316
	}
2317

    
2318
	/**
2319
	 * Returns the original label information of the derived unit.
2320
	 * @return
2321
	 */
2322
	@Transient
2323
	public String getOriginalLabelInfo() {
2324
		return ! checkDerivedUnit()? null : derivedUnit.getOriginalLabelInfo();
2325
	}
2326
	public void setOriginalLabelInfo(String originalLabelInfo) {
2327
		testDerivedUnit();
2328
		derivedUnit.setOriginalLabelInfo(originalLabelInfo);
2329
	}
2330

    
2331
	// **** sources **/
2332
	public void addSource(IdentifiableSource source) {
2333
		this.baseUnit().addSource(source);
2334
	}
2335

    
2336
	/**
2337
	 * Creates an {@link IOriginalSource orignal source} or type ,
2338
	 * adds it to the specimen and returns it.
2339
	 *
2340
	 * @param reference
2341
	 * @param microReference
2342
	 * @param originalNameString
2343
	 * @return
2344
	 */
2345
	public IdentifiableSource addSource(OriginalSourceType type, Reference reference, String microReference, String originalNameString) {
2346
		IdentifiableSource source = IdentifiableSource.NewInstance(type, null, null, reference, microReference);
2347
		source.setOriginalNameString(originalNameString);
2348
		addSource(source);
2349
		return source;
2350
	}
2351

    
2352
	@Transient
2353
	public Set<IdentifiableSource> getSources() {
2354
		return baseUnit().getSources();
2355
	}
2356

    
2357
	public void removeSource(IdentifiableSource source) {
2358
		this.baseUnit().removeSource(source);
2359
	}
2360

    
2361
	//*** identifiers ***/
2362

    
2363

    
2364
    public void addIdentifier(Identifier identifier) {
2365
        this.baseUnit().addIdentifier(identifier);
2366
    }
2367

    
2368
	@Transient
2369
	public List<Identifier> getIdentifiers() {
2370
	    return baseUnit().getIdentifiers();
2371
	}
2372

    
2373
	public void removeIdentifier(Identifier identifier) {
2374
	    this.baseUnit().removeIdentifier(identifier);
2375
	}
2376

    
2377
	@Transient
2378
	public Set<Rights> getRights() {
2379
		return baseUnit().getRights();
2380
	}
2381

    
2382
	/**
2383
	 * @return the collection
2384
	 */
2385
	@Transient
2386
	public Collection getCollection() {
2387
		return ! checkDerivedUnit()? null :  derivedUnit.getCollection();
2388
	}
2389

    
2390
	/**
2391
	 * @param collection
2392
	 *            the collection to set
2393
	 */
2394
	public void setCollection(Collection collection) {
2395
		testDerivedUnit();
2396
		derivedUnit.setCollection(collection);
2397
	}
2398

    
2399
	// annotation
2400
	public void addAnnotation(Annotation annotation) {
2401
		this.baseUnit().addAnnotation(annotation);
2402
	}
2403

    
2404
	@Transient
2405
	public void getAnnotations() {
2406
		this.baseUnit().getAnnotations();
2407
	}
2408

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

    
2413
	// ******************************* Events ***************************
2414

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

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

    
2449
	// **************** Other Collections ********************************
2450

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

    
2477
	public void addDuplicate(DerivedUnit duplicateSpecimen) {
2478
		testDerivedUnit();
2479
		getDerivationEvent(CREATE).addDerivative(duplicateSpecimen);
2480
	}
2481

    
2482
	@Transient
2483
	public Set<DerivedUnit> getDuplicates() {
2484
		if (! checkDerivedUnit()){
2485
			return new HashSet<>();
2486
		}
2487
		Set<DerivedUnit> result = new HashSet<>();
2488
		if (hasDerivationEvent()) {
2489
			for (DerivedUnit derivedUnit : getDerivationEvent(CREATE)
2490
					.getDerivatives()) {
2491
				if (derivedUnit.isInstanceOf(DerivedUnit.class)
2492
						&& !derivedUnit.equals(this.derivedUnit)) {
2493
					result.add(CdmBase.deproxy(derivedUnit, DerivedUnit.class));
2494
				}
2495
			}
2496
		}
2497
		return result;
2498
	}
2499

    
2500
	public void removeDuplicate(DerivedUnit duplicateSpecimen) {
2501
		testDerivedUnit();
2502
		if (hasDerivationEvent()) {
2503
			getDerivationEvent(CREATE).removeDerivative(duplicateSpecimen);
2504
		}
2505
	}
2506

    
2507
	public SpecimenOrObservationBase<?> baseUnit(){
2508
	    if(derivedUnit!=null){
2509
	        return derivedUnit;
2510
	    }
2511
	    else if(fieldUnit!=null){
2512
	        return fieldUnit;
2513
	    }
2514
	    else{
2515
	        throw new IllegalStateException("A DerivedUnitFacade must always have either a field unit or a derived unit");
2516
	    }
2517
	}
2518

    
2519
	/**
2520
	 * @return true if <code>this.derivedUnit</code> exists
2521
	 */
2522
	private boolean checkDerivedUnit()  {
2523
		if (derivedUnit == null){
2524
			return false;
2525
		}else{
2526
			return true;
2527
		}
2528
	}
2529

    
2530
	private void testDerivedUnit() /* throws MethodNotSupportedByDerivedUnitTypeException */ {
2531
		if (derivedUnit == null){
2532
			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");
2533
		}
2534
	}
2535

    
2536
	public void setType(SpecimenOrObservationType type) {
2537
		if (type == null){
2538
			throw new IllegalArgumentException("The type of a specimen or observation may not be null");
2539
		}
2540
		SpecimenOrObservationBase<?> baseUnit = baseUnit();
2541
		if(baseUnit.isInstanceOf(FieldUnit.class) && !type.isFieldUnit()){
2542
		    throw new IllegalArgumentException("A FieldUnit may only be of type FieldUnit") ;
2543
		}
2544
		else if(baseUnit.isInstanceOf(DerivedUnit.class) && type.isFieldUnit()){
2545
		    throw new IllegalArgumentException("A derived unit may not be of type FieldUnit") ;
2546
		}
2547
		baseUnit.setRecordBasis(type);
2548
	}
2549

    
2550
	public SpecimenOrObservationType getType() {
2551
	    return baseUnit().getRecordBasis();
2552
	}
2553

    
2554
	/**
2555
	 * Closes this facade. As a minimum this method removes all listeners created by this facade from their
2556
	 * listening objects.
2557
	 */
2558
	public void close(){
2559
		for (PropertyChangeListener listener : this.listeners.keySet()){
2560
			CdmBase listeningObject = listeners.get(listener);
2561
			listeningObject.removePropertyChangeListener(listener);
2562
		}
2563
	}
2564

    
2565

    
2566
	/**
2567
	 * Computes the correct distance string for given values for min, max and text.
2568
	 * If text is not blank, text is returned, otherwise "min - max" or a single value is returned.
2569
	 * @param min min value as number
2570
	 * @param max max value as number
2571
	 * @param text text representation of distance
2572
	 * @return the formatted distance string
2573
	 */
2574
	private String distanceString(Number min, Number max, String text, String unit) {
2575
		if (StringUtils.isNotBlank(text)){
2576
			return text;
2577
		}else{
2578
			String minStr = min == null? null : String.valueOf(min);
2579
			String maxStr = max == null? null : String.valueOf(max);
2580
			String result = CdmUtils.concat(UTF8.EN_DASH_SPATIUM.toString(), minStr, maxStr);
2581
			if (StringUtils.isNotBlank(result) && StringUtils.isNotBlank(unit)){
2582
				result = result + " " + unit;
2583
			}
2584
			return result;
2585
		}
2586
	}
2587

    
2588
	/**
2589
	 * First checks the inner field unit for the publish flag. If set to <code>true</code>
2590
	 * then <code>true</code> is returned. If the field unit is <code>null</code> the inner derived unit
2591
	 * is checked.
2592
	 * @return <code>true</code> if this facade can be published
2593
	 */
2594
	public boolean isPublish(){
2595
	    if(fieldUnit!=null){
2596
	        return fieldUnit.isPublish();
2597
	    }
2598
	    if(derivedUnit!=null){
2599
	        return derivedUnit.isPublish();
2600
	    }
2601
	    return false;
2602
	}
2603
}
(1-1/6)