Project

General

Profile

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

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

    
21
import javax.persistence.Transient;
22

    
23
import org.apache.commons.lang.StringUtils;
24
import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;
25

    
26
import eu.etaxonomy.cdm.api.service.IOccurrenceService;
27
import eu.etaxonomy.cdm.common.URI;
28
import eu.etaxonomy.cdm.format.occurrences.DistanceStringFormatter;
29
import eu.etaxonomy.cdm.model.agent.AgentBase;
30
import eu.etaxonomy.cdm.model.agent.Person;
31
import eu.etaxonomy.cdm.model.common.Annotation;
32
import eu.etaxonomy.cdm.model.common.CdmBase;
33
import eu.etaxonomy.cdm.model.common.IdentifiableSource;
34
import eu.etaxonomy.cdm.model.common.Identifier;
35
import eu.etaxonomy.cdm.model.common.Language;
36
import eu.etaxonomy.cdm.model.common.LanguageString;
37
import eu.etaxonomy.cdm.model.common.TimePeriod;
38
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
39
import eu.etaxonomy.cdm.model.description.Feature;
40
import eu.etaxonomy.cdm.model.description.SpecimenDescription;
41
import eu.etaxonomy.cdm.model.description.TextData;
42
import eu.etaxonomy.cdm.model.location.NamedArea;
43
import eu.etaxonomy.cdm.model.location.Point;
44
import eu.etaxonomy.cdm.model.location.ReferenceSystem;
45
import eu.etaxonomy.cdm.model.media.Media;
46
import eu.etaxonomy.cdm.model.media.Rights;
47
import eu.etaxonomy.cdm.model.name.TaxonName;
48
import eu.etaxonomy.cdm.model.occurrence.Collection;
49
import eu.etaxonomy.cdm.model.occurrence.DerivationEvent;
50
import eu.etaxonomy.cdm.model.occurrence.DerivationEventType;
51
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
52
import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
53
import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
54
import eu.etaxonomy.cdm.model.occurrence.GatheringEvent;
55
import eu.etaxonomy.cdm.model.occurrence.PreservationMethod;
56
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
57
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;
58
import eu.etaxonomy.cdm.model.reference.IOriginalSource;
59
import eu.etaxonomy.cdm.model.reference.OriginalSourceType;
60
import eu.etaxonomy.cdm.model.reference.Reference;
61
import eu.etaxonomy.cdm.model.term.DefinedTerm;
62
import eu.etaxonomy.cdm.ref.TypedEntityReference;
63
import eu.etaxonomy.cdm.strategy.cache.occurrence.DerivedUnitDefaultCacheStrategy;
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 = LogManager.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.getLabel());
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
	private void setCacheStrategy() {
476
		if (derivedUnit == null) {
477
			throw new NullPointerException(
478
					"Facade's derviedUnit must not be null to set cache strategy");
479
		}else{
480
			derivedUnit.setCacheStrategy(DerivedUnitFacadeCacheStrategy.NewInstance(false));
481
			setFieldUnitCacheStrategy();
482
		}
483
	}
484

    
485
	private void setFieldUnitCacheStrategy() {
486
		if (this.hasFieldObject()){
487
			DerivedUnitFacadeFieldUnitCacheStrategy strategy = new DerivedUnitFacadeFieldUnitCacheStrategy();
488
			this.fieldUnit.setCacheStrategy(strategy);
489
		}
490
	}
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 DistanceStringFormatter.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 DistanceStringFormatter.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 DistanceStringFormatter.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
	// gathering 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
	@Transient
1304
	public String getLocalityText() {
1305
		LanguageString locality = getLocality();
1306
		if (locality != null) {
1307
			return locality.getText();
1308
		}
1309
		return null;
1310
	}
1311

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

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

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

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

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

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

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

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

    
1387
	// ****************** Field Object ************************************/
1388

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

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

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

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

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

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

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

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

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

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

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

    
1515
	public void setPlantDescription(String plantDescription) {
1516
		setPlantDescription(plantDescription, null);
1517
	}
1518

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

    
1541
	public void removePlantDescription(Language language) {
1542
		setPlantDescription(null, language);
1543
	}
1544

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

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

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

    
1583
    public void setLifeform(String lifeform) {
1584
        setLifeform(lifeform, null);
1585
    }
1586

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

    
1609
    public void removeLifeform(Language language) {
1610
        setLifeform(null, language);
1611
    }
1612

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1790
	public void setIndividualCount(String individualCount) {
1791
		getFieldUnit(true).setIndividualCount(individualCount);
1792
	}
1793

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

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

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

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

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

    
1832

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

    
1844
    public void setDerivedUnitKindOfUnit(DefinedTerm kindOfUnit) {
1845
        testDerivedUnit();
1846

    
1847
        baseUnit().setKindOfUnit(kindOfUnit);
1848
    }
1849

    
1850

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

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

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

    
1877

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

    
1892
	// ****************** Specimen *******************************************
1893

    
1894

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

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

    
1905
	public TypedEntityReference<DerivedUnit> getDerivedUnitEntityReference() {
1906
	   return new TypedEntityReference(derivedUnit.getClass(), derivedUnit.getUuid());
1907
	}
1908

    
1909
    public TypedEntityReference<FieldUnit> getFieldUnitEntityReference() {
1910
        if(fieldUnit == null) {
1911
            return null;
1912
        } else {
1913
            return new TypedEntityReference(FieldUnit.class, fieldUnit.getUuid());
1914
        }
1915
     }
1916

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

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

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

    
1942
	@Transient
1943
	public DeterminationEvent getPreferredDetermination() {
1944
		return baseUnit().getPreferredDetermination();
1945
	}
1946

    
1947
	/**
1948
	 * This method returns the preferred determination.
1949
	 * @see #getOtherDeterminations()
1950
	 * @see #getDeterminations()
1951
	 */
1952
	@Transient
1953
	public void setPreferredDetermination(DeterminationEvent newEvent) {
1954
		baseUnit().setPreferredDetermination(newEvent);
1955
	}
1956

    
1957
	/**
1958
	 * This method returns all determinations except for the preferred one.
1959
	 * @see #getPreferredDetermination()
1960
	 * @see #getDeterminations()
1961
	 * @return
1962
	 */
1963
	@Transient
1964
	public Set<DeterminationEvent> getOtherDeterminations() {
1965
		return baseUnit().getOtherDeterminations();
1966
	}
1967

    
1968
	/**
1969
	 * This method returns all determination events. The preferred one {@link #getPreferredDetermination()}
1970
	 * and all others {@link #getOtherDeterminations()}.
1971
	 * @return
1972
	 */
1973
	@Transient
1974
	public Set<DeterminationEvent> getDeterminations() {
1975
		return baseUnit().getDeterminations();
1976
	}
1977

    
1978
	public void removeDetermination(DeterminationEvent determination) {
1979
		baseUnit().removeDetermination(determination);
1980
	}
1981

    
1982
	// Media
1983
	public boolean addDerivedUnitMedia(Media media) {
1984
		testDerivedUnit();
1985
		try {
1986
			return addMedia(media, derivedUnit);
1987
		} catch (DerivedUnitFacadeNotSupportedException e) {
1988
			throw new IllegalStateException(notSupportMessage, e);
1989
		}
1990
	}
1991

    
1992
	/**
1993
	 * Returns true, if an image gallery exists for the specimen.<BR>
1994
	 * Returns also <code>true</code> if the image gallery is empty.
1995
	 */
1996
	public boolean hasDerivedUnitImageGallery() {
1997
		return (getImageGallery(derivedUnit, false) != null);
1998
	}
1999

    
2000
	public SpecimenDescription getDerivedUnitImageGallery(boolean createIfNotExists) {
2001
		if (!checkDerivedUnit()){
2002
			return null;
2003
		}
2004
		TextData textData;
2005
		try {
2006
			textData = inititializeTextDataWithSupportTest(Feature.IMAGE(),
2007
					derivedUnit, createIfNotExists, true);
2008
		} catch (DerivedUnitFacadeNotSupportedException e) {
2009
			throw new IllegalStateException(notSupportMessage, e);
2010
		}
2011
		if (textData != null) {
2012
			return CdmBase.deproxy(textData.getInDescription(),
2013
					SpecimenDescription.class);
2014
		} else {
2015
			return null;
2016
		}
2017
	}
2018

    
2019
	public void setDerivedUnitImageGallery(SpecimenDescription imageGallery)
2020
			throws DerivedUnitFacadeNotSupportedException {
2021
		testDerivedUnit();
2022
		SpecimenDescription existingGallery = getDerivedUnitImageGallery(false);
2023

    
2024
		// test attached specimens contain this.derivedUnit
2025
		SpecimenOrObservationBase facadeDerivedUnit = innerDerivedUnit();
2026
		testSpecimenInImageGallery(imageGallery, facadeDerivedUnit);
2027

    
2028
		if (existingGallery != null) {
2029
			if (existingGallery != imageGallery) {
2030
				throw new DerivedUnitFacadeNotSupportedException(
2031
						"DerivedUnitFacade does not allow more than one image gallery");
2032
			} else {
2033
				// do nothing
2034
			}
2035
		} else {
2036
			TextData textData = testImageGallery(imageGallery);
2037
			this.derivedUnitMediaTextData = textData;
2038
		}
2039
	}
2040

    
2041
	/**
2042
	 * @param imageGallery
2043
	 * @throws DerivedUnitFacadeNotSupportedException
2044
	 */
2045
	private void testSpecimenInImageGallery(SpecimenDescription imageGallery, SpecimenOrObservationBase specimen)
2046
				throws DerivedUnitFacadeNotSupportedException {
2047
		SpecimenOrObservationBase imageGallerySpecimen = imageGallery.getDescribedSpecimenOrObservation();
2048
		if (imageGallerySpecimen == null) {
2049
			throw new DerivedUnitFacadeNotSupportedException(
2050
					"Image Gallery has no Specimen attached. Please attache according specimen or field unit.");
2051
		}
2052
		if (! imageGallerySpecimen.equals(specimen)) {
2053
			throw new DerivedUnitFacadeNotSupportedException(
2054
					"Image Gallery has not the facade's field object attached. Please add field object first " +
2055
					"to image gallery specimenOrObservation list.");
2056
		}
2057
	}
2058

    
2059
	/**
2060
	 * Returns the media for the specimen.<BR>
2061
	 *
2062
	 * @return
2063
	 */
2064
	@Transient
2065
	public List<Media> getDerivedUnitMedia() {
2066
		if (! checkDerivedUnit()){
2067
			return new ArrayList<>();
2068
		}
2069
		try {
2070
			List<Media> result = getMediaList(derivedUnit, false);
2071
			return result == null ? new ArrayList<>() : result;
2072
		} catch (DerivedUnitFacadeNotSupportedException e) {
2073
			throw new IllegalStateException(notSupportMessage, e);
2074
		}
2075
	}
2076

    
2077
	public boolean removeDerivedUnitMedia(Media media) {
2078
		testDerivedUnit();
2079
		try {
2080
			return removeMedia(media, derivedUnit);
2081
		} catch (DerivedUnitFacadeNotSupportedException e) {
2082
			throw new IllegalStateException(notSupportMessage, e);
2083
		}
2084
	}
2085

    
2086
	// Accession Number
2087
	@Transient
2088
	public String getAccessionNumber() {
2089
		return ! checkDerivedUnit()? null : derivedUnit.getAccessionNumber();
2090
	}
2091

    
2092

    
2093
	/**
2094
	 * The combination of collection code with accession number, barcode or collection number.
2095
	 *
2096
	 * @return
2097
	 */
2098
	public String getSpecimenLabel() {
2099
	    if(checkDerivedUnit()){
2100
	        if(derivedUnit.cacheStrategy() instanceof DerivedUnitFacadeCacheStrategy){
2101
	            return ((DerivedUnitFacadeCacheStrategy)derivedUnit.cacheStrategy()).getSpecimenLabel(this);
2102
	        }
2103

    
2104
	    }
2105
	    return null;
2106
	}
2107

    
2108
	public void setAccessionNumber(String accessionNumber) {
2109
		testDerivedUnit();
2110
		derivedUnit.setAccessionNumber(accessionNumber);
2111
	}
2112

    
2113
	@Transient
2114
	public String getCatalogNumber() {
2115
		return ! checkDerivedUnit()? null : derivedUnit.getCatalogNumber();
2116
	}
2117

    
2118
	public void setCatalogNumber(String catalogNumber) {
2119
		testDerivedUnit();
2120
		derivedUnit.setCatalogNumber(catalogNumber);
2121
	}
2122

    
2123
	@Transient
2124
	public String getBarcode() {
2125
		return ! checkDerivedUnit()? null : derivedUnit.getBarcode();
2126
	}
2127

    
2128
	public void setBarcode(String barcode) {
2129
		testDerivedUnit();
2130
		derivedUnit.setBarcode(barcode);
2131
	}
2132

    
2133
	// Preservation Method
2134

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

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

    
2175
	//preferred stable URI  #5606
2176
    public URI getPreferredStableUri(){
2177
        return baseUnit().getPreferredStableUri();
2178
    }
2179
    public void setPreferredStableUri(URI stableUri){
2180
        baseUnit().setPreferredStableUri(stableUri);
2181
    }
2182

    
2183

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

    
2190
	public void setStoredUnder(TaxonName storedUnder) {
2191
		testDerivedUnit();
2192
		derivedUnit.setStoredUnder(storedUnder);
2193
	}
2194

    
2195
	// title cache
2196
	public String getTitleCache() {
2197
		SpecimenOrObservationBase<?> titledUnit = baseUnit();
2198

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

    
2208
	public boolean isProtectedTitleCache() {
2209
		return baseUnit().isProtectedTitleCache();
2210
	}
2211

    
2212
	public void setTitleCache(String titleCache, boolean isProtected) {
2213
		this.baseUnit().setTitleCache(titleCache, isProtected);
2214
	}
2215

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

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

    
2245
	private boolean hasDerivationEvent() {
2246
		return getDerivationEvent() == null ? false : true;
2247
	}
2248

    
2249
	private DerivationEvent getDerivationEvent() {
2250
		return getDerivationEvent(CREATE_NOT);
2251
	}
2252

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

    
2272
			result = DerivationEvent.NewInstance(type);
2273
			derivedUnit.setDerivedFrom(result);
2274
		}
2275
		return result;
2276
	}
2277

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

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

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

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

    
2334
	// **** sources **/
2335
	public void addSource(IdentifiableSource source) {
2336
		this.baseUnit().addSource(source);
2337
	}
2338

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

    
2355
	@Transient
2356
	public Set<IdentifiableSource> getSources() {
2357
		return baseUnit().getSources();
2358
	}
2359

    
2360
	public void removeSource(IdentifiableSource source) {
2361
		this.baseUnit().removeSource(source);
2362
	}
2363

    
2364
	//*** identifiers ***/
2365

    
2366

    
2367
    public void addIdentifier(Identifier identifier) {
2368
        this.baseUnit().addIdentifier(identifier);
2369
    }
2370

    
2371
	@Transient
2372
	public List<Identifier> getIdentifiers() {
2373
	    return baseUnit().getIdentifiers();
2374
	}
2375

    
2376
	public void removeIdentifier(Identifier identifier) {
2377
	    this.baseUnit().removeIdentifier(identifier);
2378
	}
2379

    
2380
	@Transient
2381
	public Set<Rights> getRights() {
2382
		return baseUnit().getRights();
2383
	}
2384

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

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

    
2402
	// annotation
2403
	public void addAnnotation(Annotation annotation) {
2404
		this.baseUnit().addAnnotation(annotation);
2405
	}
2406

    
2407
	@Transient
2408
	public void getAnnotations() {
2409
		this.baseUnit().getAnnotations();
2410
	}
2411

    
2412
	public void removeAnnotation(Annotation annotation) {
2413
		this.baseUnit().removeAnnotation(annotation);
2414
	}
2415

    
2416
	// ******************************* Events ***************************
2417

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

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

    
2452
	// **************** Other Collections ********************************
2453

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

    
2480
	public void addDuplicate(DerivedUnit duplicateSpecimen) {
2481
		testDerivedUnit();
2482
		getDerivationEvent(CREATE).addDerivative(duplicateSpecimen);
2483
	}
2484

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

    
2503
	public void removeDuplicate(DerivedUnit duplicateSpecimen) {
2504
		testDerivedUnit();
2505
		if (hasDerivationEvent()) {
2506
			getDerivationEvent(CREATE).removeDerivative(duplicateSpecimen);
2507
		}
2508
	}
2509

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

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

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

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

    
2553
	public SpecimenOrObservationType getType() {
2554
	    return baseUnit().getRecordBasis();
2555
	}
2556

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

    
2568

    
2569

    
2570

    
2571
	/**
2572
	 * First checks the inner field unit for the publish flag. If set to <code>true</code>
2573
	 * then <code>true</code> is returned. If the field unit is <code>null</code> the inner derived unit
2574
	 * is checked.
2575
	 * @return <code>true</code> if this facade can be published
2576
	 */
2577
	public boolean isPublish(){
2578
	    if(fieldUnit!=null){
2579
	        return fieldUnit.isPublish();
2580
	    }
2581
	    if(derivedUnit!=null){
2582
	        return derivedUnit.isPublish();
2583
	    }
2584
	    return false;
2585
	}
2586
}
(1-1/6)