Add IDescribable interface #3917
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / occurrence / SpecimenOrObservationBase.java
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.model.occurrence;
11
12 import java.util.HashMap;
13 import java.util.HashSet;
14 import java.util.Map;
15 import java.util.Set;
16
17 import javax.persistence.Column;
18 import javax.persistence.Entity;
19 import javax.persistence.FetchType;
20 import javax.persistence.Inheritance;
21 import javax.persistence.InheritanceType;
22 import javax.persistence.ManyToMany;
23 import javax.persistence.ManyToOne;
24 import javax.persistence.MapKeyJoinColumn;
25 import javax.persistence.OneToMany;
26 import javax.persistence.Transient;
27 import javax.validation.constraints.Min;
28 import javax.validation.constraints.NotNull;
29 import javax.xml.bind.annotation.XmlAccessType;
30 import javax.xml.bind.annotation.XmlAccessorType;
31 import javax.xml.bind.annotation.XmlAttribute;
32 import javax.xml.bind.annotation.XmlElement;
33 import javax.xml.bind.annotation.XmlElementWrapper;
34 import javax.xml.bind.annotation.XmlIDREF;
35 import javax.xml.bind.annotation.XmlRootElement;
36 import javax.xml.bind.annotation.XmlSchemaType;
37 import javax.xml.bind.annotation.XmlType;
38 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
39
40 import org.apache.log4j.Logger;
41 import org.hibernate.annotations.Cascade;
42 import org.hibernate.annotations.CascadeType;
43 import org.hibernate.annotations.Index;
44 import org.hibernate.annotations.Table;
45 import org.hibernate.annotations.Type;
46 import org.hibernate.envers.Audited;
47 import org.hibernate.search.annotations.Analyze;
48 import org.hibernate.search.annotations.ContainedIn;
49 import org.hibernate.search.annotations.Field;
50 import org.hibernate.search.annotations.IndexedEmbedded;
51 import org.hibernate.search.annotations.NumericField;
52
53 import eu.etaxonomy.cdm.jaxb.MultilanguageTextAdapter;
54 import eu.etaxonomy.cdm.model.common.DefinedTerm;
55 import eu.etaxonomy.cdm.model.common.IMultiLanguageTextHolder;
56 import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
57 import eu.etaxonomy.cdm.model.common.Language;
58 import eu.etaxonomy.cdm.model.common.LanguageString;
59 import eu.etaxonomy.cdm.model.common.MultilanguageText;
60 import eu.etaxonomy.cdm.model.common.TermType;
61 import eu.etaxonomy.cdm.model.description.DescriptionBase;
62 import eu.etaxonomy.cdm.model.description.IDescribable;
63 import eu.etaxonomy.cdm.model.description.SpecimenDescription;
64 import eu.etaxonomy.cdm.model.description.TaxonDescription;
65 import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
66 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
67
68 /**
69 * type figures are observations with at least a figure object in media
70 * @author m.doering
71 * @created 08-Nov-2007 13:06:41
72 */
73 @XmlAccessorType(XmlAccessType.FIELD)
74 @XmlType(name = "SpecimenOrObservationBase", propOrder = {
75 "recordBasis",
76 "publish",
77 "sex",
78 "lifeStage",
79 "kindOfUnit",
80 "individualCount",
81 "definition",
82 "descriptions",
83 "determinations",
84 "derivationEvents"
85 })
86 @XmlRootElement(name = "SpecimenOrObservationBase")
87 @Entity
88 @Audited
89 @Inheritance(strategy=InheritanceType.SINGLE_TABLE)
90 @Table(appliesTo="SpecimenOrObservationBase", indexes = { @Index(name = "specimenOrObservationBaseTitleCacheIndex", columnNames = { "titleCache" }) })
91 public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCacheStrategy> extends IdentifiableEntity<S>
92 implements IMultiLanguageTextHolder, IDescribable<DescriptionBase> {
93 private static final long serialVersionUID = 6932680139334408031L;
94 private static final Logger logger = Logger.getLogger(SpecimenOrObservationBase.class);
95
96 /**
97 * An indication of what the unit record describes.
98 *
99 * NOTE: The name of the attribute was chosen against the common naming conventions of the CDM
100 * as it is well known in common standards like ABCD and DarwinCore. According to CDM naming
101 * conventions it would specimenOrObservationType.
102 *
103 * @see ABCD: DataSets/DataSet/Units/Unit/RecordBasis
104 * @see Darwin Core: http://wiki.tdwg.org/twiki/bin/view/DarwinCore/BasisOfRecord
105 */
106 @XmlAttribute(name ="RecordBasis")
107 @Column(name="recordBasis")
108 @NotNull
109 @Type(type = "eu.etaxonomy.cdm.hibernate.EnumUserType",
110 parameters = {@org.hibernate.annotations.Parameter(name = "enumClass", value = "eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType")}
111 )
112 private SpecimenOrObservationType recordBasis;
113
114
115 @XmlElementWrapper(name = "Descriptions")
116 @XmlElement(name = "Description")
117 @OneToMany(mappedBy="describedSpecimenOrObservation", fetch = FetchType.LAZY)
118 @Cascade(CascadeType.SAVE_UPDATE)
119 @ContainedIn
120 @NotNull
121 private Set<DescriptionBase> descriptions = new HashSet<DescriptionBase>();
122
123
124 @XmlElementWrapper(name = "Determinations")
125 @XmlElement(name = "Determination")
126 @OneToMany(mappedBy="identifiedUnit", orphanRemoval=true)
127 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
128 @IndexedEmbedded(depth = 2)
129 @NotNull
130 private Set<DeterminationEvent> determinations = new HashSet<DeterminationEvent>();
131
132 @XmlElement(name = "Sex")
133 @XmlIDREF
134 @XmlSchemaType(name = "IDREF")
135 @ManyToOne(fetch = FetchType.LAZY)
136 private DefinedTerm sex;
137
138 @XmlElement(name = "LifeStage")
139 @XmlIDREF
140 @XmlSchemaType(name = "IDREF")
141 @ManyToOne(fetch = FetchType.LAZY)
142 private DefinedTerm lifeStage;
143
144 /**
145 * Part(s) of organism or class of materials represented by this unit.
146 * Example: fruits, seeds, tissue, gDNA, leaves
147 *
148 * @see ABCD: DataSets/DataSet/Units/Unit/KindOfUnit
149 * @see TermType#KindOfUnit
150 */
151 @XmlElement(name = "KindOfUnit")
152 @XmlIDREF
153 @XmlSchemaType(name = "IDREF")
154 @ManyToOne(fetch = FetchType.LAZY)
155 // @IndexedEmbedded(depth=1)
156 private DefinedTerm kindOfUnit;
157
158 @XmlElement(name = "IndividualCount")
159 @Field(analyze = Analyze.NO)
160 @NumericField
161 @Min(0)
162 private Integer individualCount;
163
164 // the verbatim description of this occurrence. Free text usable when no atomised data is available.
165 // in conjunction with titleCache which serves as the "citation" string for this object
166 @XmlElement(name = "Description")
167 @XmlJavaTypeAdapter(MultilanguageTextAdapter.class)
168 @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
169 @MapKeyJoinColumn(name="definition_mapkey_id")
170 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE})
171 @IndexedEmbedded
172 @NotNull
173 protected Map<Language,LanguageString> definition = new HashMap<Language,LanguageString>();
174
175 // events that created derivedUnits from this unit
176 @XmlElementWrapper(name = "DerivationEvents")
177 @XmlElement(name = "DerivationEvent")
178 @XmlIDREF
179 @XmlSchemaType(name = "IDREF")
180 @ManyToMany(fetch=FetchType.LAZY)
181 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE})
182 @NotNull
183 protected Set<DerivationEvent> derivationEvents = new HashSet<DerivationEvent>();
184
185 @XmlAttribute(name = "publish")
186 private boolean publish = true;
187
188
189 //********************************** CONSTRUCTOR *********************************/
190
191 //for hibernate use only
192 @Deprecated
193 protected SpecimenOrObservationBase(){super();}
194
195 protected SpecimenOrObservationBase(SpecimenOrObservationType recordBasis) {
196 super();
197 if (recordBasis == null){ throw new IllegalArgumentException("RecordBasis must not be null");}
198 this.recordBasis = recordBasis;
199 }
200
201 //************************* GETTER / SETTER ***********************/
202
203 /**
204 * @see #recordBasis
205 * @return
206 */
207 public SpecimenOrObservationType getRecordBasis() {
208 return recordBasis;
209 }
210
211 /**
212 * @see #recordBasis
213 * @param recordBasis
214 */
215 public void setRecordBasis(SpecimenOrObservationType recordBasis) {
216 this.recordBasis = recordBasis;
217 }
218
219
220 /**
221 * Returns the boolean value indicating if this specimen or observation should be withheld
222 * (<code>publish=false</code>) or not (<code>publish=true</code>) during any publication
223 * process to the general public.
224 * This publish flag implementation is preliminary and may be replaced by a more general
225 * implementation of READ rights in future.<BR>
226 * The default value is <code>true</code>.
227 */
228 public boolean isPublish() {
229 return publish;
230 }
231
232 /**
233 * @see #isPublish()
234 * @param publish
235 */
236 public void setPublish(boolean publish) {
237 this.publish = publish;
238 }
239
240 /**
241 * The descriptions this specimen or observation is part of.<BR>
242 * A specimen can not only have it's own {@link SpecimenDescription specimen description }
243 * but can also be part of a {@link TaxonDescription taxon description} or a
244 * {@link TaxonNameDescription taxon name description}.<BR>
245 * @see #getSpecimenDescriptions()
246 * @return
247 */
248 public Set<DescriptionBase> getDescriptions() {
249 if(descriptions == null) {
250 this.descriptions = new HashSet<DescriptionBase>();
251 }
252 return this.descriptions;
253 }
254
255 /**
256 * Returns the {@link SpecimenDescription specimen descriptions} this specimen is part of.
257 * @see #getDescriptions()
258 * @return
259 */
260 @Transient
261 public Set<SpecimenDescription> getSpecimenDescriptions() {
262 return getSpecimenDescriptions(true);
263 }
264
265 /**
266 * Returns the {@link SpecimenDescription specimen descriptions} this specimen is part of.
267 * @see #getDescriptions()
268 * @return
269 */
270 @Transient
271 public Set<SpecimenDescription> getSpecimenDescriptions(boolean includeImageGallery) {
272 Set<SpecimenDescription> specimenDescriptions = new HashSet<SpecimenDescription>();
273 for (DescriptionBase descriptionBase : getDescriptions()){
274 if (descriptionBase.isInstanceOf(SpecimenDescription.class)){
275 if (includeImageGallery || descriptionBase.isImageGallery() == false){
276 specimenDescriptions.add(descriptionBase.deproxy(descriptionBase, SpecimenDescription.class));
277 }
278
279 }
280 }
281 return specimenDescriptions;
282 }
283 /**
284 * Returns the {@link SpecimenDescription specimen descriptions} which act as an image gallery
285 * and which this specimen is part of.
286 * @see #getDescriptions()
287 * @return
288 */
289 @Transient
290 public Set<SpecimenDescription> getSpecimenDescriptionImageGallery() {
291 Set<SpecimenDescription> specimenDescriptions = new HashSet<SpecimenDescription>();
292 for (DescriptionBase descriptionBase : getDescriptions()){
293 if (descriptionBase.isInstanceOf(SpecimenDescription.class)){
294 if (descriptionBase.isImageGallery() == true){
295 specimenDescriptions.add(descriptionBase.deproxy(descriptionBase, SpecimenDescription.class));
296 }
297 }
298 }
299 return specimenDescriptions;
300 }
301
302 /**
303 * Adds a new description to this specimen or observation
304 * @param description
305 */
306 public void addDescription(DescriptionBase description) {
307 if (description.getDescribedSpecimenOrObservation() != null){
308 description.getDescribedSpecimenOrObservation().removeDescription(description);
309 }
310 descriptions.add(description);
311 description.setDescribedSpecimenOrObservation(this);
312 }
313
314 /**
315 * Removes a specimen from a description (removes a description from this specimen)
316 * @param description
317 */
318 public void removeDescription(DescriptionBase description) {
319 boolean existed = descriptions.remove(description);
320 if (existed){
321 description.setDescribedSpecimenOrObservation(null);
322 }
323 }
324
325
326 public Set<DerivationEvent> getDerivationEvents() {
327 if(derivationEvents == null) {
328 this.derivationEvents = new HashSet<DerivationEvent>();
329 }
330 return this.derivationEvents;
331 }
332
333 public void addDerivationEvent(DerivationEvent derivationEvent) {
334 if (! this.derivationEvents.contains(derivationEvent)){
335 this.derivationEvents.add(derivationEvent);
336 derivationEvent.addOriginal(this);
337 }
338 }
339
340 public void removeDerivationEvent(DerivationEvent derivationEvent) {
341 this.derivationEvents.remove(derivationEvent);
342 }
343
344 public Set<DeterminationEvent> getDeterminations() {
345 if(determinations == null) {
346 this.determinations = new HashSet<DeterminationEvent>();
347 }
348 return this.determinations;
349 }
350
351 public void addDetermination(DeterminationEvent determination) {
352 // FIXME bidirectional integrity. Use protected Determination setter
353 this.determinations.add(determination);
354 }
355
356 public void removeDetermination(DeterminationEvent determination) {
357 // FIXME bidirectional integrity. Use protected Determination setter
358 this.determinations.remove(determination);
359 }
360
361 public DefinedTerm getSex() {
362 return sex;
363 }
364
365 public void setSex(DefinedTerm sex) {
366 this.sex = sex;
367 }
368
369 public DefinedTerm getLifeStage() {
370 return lifeStage;
371 }
372
373 public void setLifeStage(DefinedTerm lifeStage) {
374 this.lifeStage = lifeStage;
375 }
376
377
378 /**
379 * @see #kindOfUnit
380 * @return
381 */
382 public DefinedTerm getKindOfUnit() {
383 return kindOfUnit;
384 }
385
386 /**
387 * @see #kindOfUnit
388 * @param kindOfUnit
389 */
390 public void setKindOfUnit(DefinedTerm kindOfUnit) {
391 this.kindOfUnit = kindOfUnit;
392 }
393
394 public Integer getIndividualCount() {
395 return individualCount;
396 }
397
398 public void setIndividualCount(Integer individualCount) {
399 this.individualCount = individualCount;
400 }
401
402 public Map<Language,LanguageString> getDefinition(){
403 return this.definition;
404 }
405
406 /**
407 * adds the {@link LanguageString description} to the {@link MultilanguageText multilanguage text}
408 * used to define <i>this</i> specimen or observation.
409 *
410 * @param description the languageString in with the title string and the given language
411 *
412 * @see #getDefinition()
413 * @see #putDefinition(Language, String)
414 */
415 public void putDefinition(LanguageString description){
416 this.definition.put(description.getLanguage(),description);
417 }
418
419 /**
420 * Creates a {@link LanguageString language string} based on the given text string
421 * and the given {@link Language language} and adds it to the {@link MultilanguageText multilanguage text}
422 * used to define <i>this</i> specimen or observation.
423 *
424 * @param language the language in which the title string is formulated
425 * @param text the definition in a particular language
426 *
427 * @see #getDefinition()
428 * @see #putDefinition(LanguageString)
429 */
430 public void putDefinition(Language language, String text){
431 this.definition.put(language, LanguageString.NewInstance(text, language));
432 }
433
434
435 public void removeDefinition(Language lang){
436 this.definition.remove(lang);
437 }
438
439 /**
440 * for derived units get the single next higher parental/original unit.
441 * If multiple original units exist throw error
442 * @return
443 */
444 @Transient
445 public SpecimenOrObservationBase getOriginalUnit(){
446 logger.warn("GetOriginalUnit not yet implemented");
447 return null;
448 }
449
450
451 //******************** CLONE **********************************************/
452
453 /* (non-Javadoc)
454 * @see eu.etaxonomy.cdm.model.media.IdentifiableMediaEntity#clone()
455 * @see eu.etaxonomy.cdm.model.common.IdentifiableEntity#clone()
456 * @see java.lang.Object#clone()
457 */
458 @Override
459 public Object clone() throws CloneNotSupportedException {
460 SpecimenOrObservationBase result = null;
461 result = (SpecimenOrObservationBase)super.clone();
462
463 //defininion (description, languageString)
464 result.definition = new HashMap<Language,LanguageString>();
465 for(LanguageString languageString : this.definition.values()) {
466 LanguageString newLanguageString = (LanguageString)languageString.clone();
467 result.putDefinition(newLanguageString);
468 }
469
470 //sex
471 result.setSex(this.sex);
472 //life stage
473 result.setLifeStage(this.lifeStage);
474
475 //Descriptions
476 for(DescriptionBase description : this.descriptions) {
477 result.addDescription(description);
478 }
479
480 //DeterminationEvent FIXME should clone() the determination
481 // as the relationship is OneToMany
482 for(DeterminationEvent determination : this.determinations) {
483 result.addDetermination(determination);
484 }
485
486 //DerivationEvent
487 for(DerivationEvent derivationEvent : this.derivationEvents) {
488 result.addDerivationEvent(derivationEvent);
489 }
490
491 //no changes to: individualCount
492 return result;
493 }
494
495
496 }