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