merge trunk and bugfix linkbackuri jaxb
[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.Entity;
18 import javax.persistence.FetchType;
19 import javax.persistence.Inheritance;
20 import javax.persistence.InheritanceType;
21 import javax.persistence.ManyToMany;
22 import javax.persistence.ManyToOne;
23 import javax.persistence.MapKeyJoinColumn;
24 import javax.persistence.OneToMany;
25 import javax.persistence.Transient;
26 import javax.validation.constraints.Min;
27 import javax.validation.constraints.NotNull;
28 import javax.xml.bind.annotation.XmlAccessType;
29 import javax.xml.bind.annotation.XmlAccessorType;
30 import javax.xml.bind.annotation.XmlElement;
31 import javax.xml.bind.annotation.XmlElementWrapper;
32 import javax.xml.bind.annotation.XmlIDREF;
33 import javax.xml.bind.annotation.XmlRootElement;
34 import javax.xml.bind.annotation.XmlSchemaType;
35 import javax.xml.bind.annotation.XmlType;
36 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
37
38 import org.apache.log4j.Logger;
39 import org.hibernate.annotations.Cascade;
40 import org.hibernate.annotations.CascadeType;
41 import org.hibernate.annotations.Index;
42 import org.hibernate.annotations.Table;
43 import org.hibernate.envers.Audited;
44 import org.hibernate.search.annotations.Analyze;
45 import org.hibernate.search.annotations.Field;
46 import org.hibernate.search.annotations.IndexedEmbedded;
47
48 import eu.etaxonomy.cdm.jaxb.MultilanguageTextAdapter;
49 import eu.etaxonomy.cdm.model.common.IMultiLanguageTextHolder;
50 import eu.etaxonomy.cdm.model.common.Language;
51 import eu.etaxonomy.cdm.model.common.LanguageString;
52 import eu.etaxonomy.cdm.model.common.MultilanguageText;
53 import eu.etaxonomy.cdm.model.description.DescriptionBase;
54 import eu.etaxonomy.cdm.model.description.Sex;
55 import eu.etaxonomy.cdm.model.description.SpecimenDescription;
56 import eu.etaxonomy.cdm.model.description.Stage;
57 import eu.etaxonomy.cdm.model.description.TaxonDescription;
58 import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
59 import eu.etaxonomy.cdm.model.media.IdentifiableMediaEntity;
60 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
61
62 /**
63 * type figures are observations with at least a figure object in media
64 * @author m.doering
65 * @version 1.0
66 * @created 08-Nov-2007 13:06:41
67 */
68 @XmlAccessorType(XmlAccessType.FIELD)
69 @XmlType(name = "SpecimenOrObservationBase", propOrder = {
70 "sex",
71 "individualCount",
72 "lifeStage",
73 "definition",
74 "descriptions",
75 "determinations",
76 "derivationEvents"
77 })
78 @XmlRootElement(name = "SpecimenOrObservationBase")
79 @Entity
80 @Audited
81 @Inheritance(strategy=InheritanceType.SINGLE_TABLE)
82 @Table(appliesTo="SpecimenOrObservationBase", indexes = { @Index(name = "specimenOrObservationBaseTitleCacheIndex", columnNames = { "titleCache" }) })
83 public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCacheStrategy> extends IdentifiableMediaEntity<S> implements IMultiLanguageTextHolder{
84
85 private static final Logger logger = Logger.getLogger(SpecimenOrObservationBase.class);
86
87 @XmlElementWrapper(name = "Descriptions")
88 @XmlElement(name = "Description")
89 @ManyToMany(fetch = FetchType.LAZY,mappedBy="describedSpecimenOrObservations",targetEntity=DescriptionBase.class)
90 @Cascade(CascadeType.SAVE_UPDATE)
91 @NotNull
92 private Set<DescriptionBase> descriptions = new HashSet<DescriptionBase>();
93
94 @XmlElementWrapper(name = "Determinations")
95 @XmlElement(name = "Determination")
96 @OneToMany(mappedBy="identifiedUnit")
97 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE, CascadeType.DELETE_ORPHAN})
98 @IndexedEmbedded(depth = 2)
99 @NotNull
100 private Set<DeterminationEvent> determinations = new HashSet<DeterminationEvent>();
101
102 @XmlElement(name = "Sex")
103 @XmlIDREF
104 @XmlSchemaType(name = "IDREF")
105 @ManyToOne(fetch = FetchType.LAZY)
106 private Sex sex;
107
108 @XmlElement(name = "LifeStage")
109 @XmlIDREF
110 @XmlSchemaType(name = "IDREF")
111 @ManyToOne(fetch = FetchType.LAZY)
112 private Stage lifeStage;
113
114 @XmlElement(name = "IndividualCount")
115 @Field(analyze = Analyze.NO)
116 @Min(0)
117 private Integer individualCount;
118
119 // the verbatim description of this occurrence. Free text usable when no atomised data is available.
120 // in conjunction with titleCache which serves as the "citation" string for this object
121 @XmlElement(name = "Description")
122 @XmlJavaTypeAdapter(MultilanguageTextAdapter.class)
123 @OneToMany(fetch = FetchType.LAZY)
124 @MapKeyJoinColumn(name="definition_mapkey_id")
125 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE, CascadeType.DELETE_ORPHAN})
126 @IndexedEmbedded
127 @NotNull
128 protected Map<Language,LanguageString> definition = new HashMap<Language,LanguageString>();
129
130 // events that created derivedUnits from this unit
131 @XmlElementWrapper(name = "DerivationEvents")
132 @XmlElement(name = "DerivationEvent")
133 @XmlIDREF
134 @XmlSchemaType(name = "IDREF")
135 @ManyToMany(fetch=FetchType.LAZY)
136 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE, CascadeType.DELETE_ORPHAN})
137 @NotNull
138 protected Set<DerivationEvent> derivationEvents = new HashSet<DerivationEvent>();
139
140 /**
141 * Constructor
142 */
143 protected SpecimenOrObservationBase(){
144 super();
145 }
146
147 /**
148 * The descriptions this specimen or observation is part of.<BR>
149 * A specimen can not only have it's own {@link SpecimenDescription specimen description }
150 * but can also be part of a {@link TaxonDescription taxon description} or a
151 * {@link TaxonNameDescription taxon name description}.<BR>
152 * @see #getSpecimenDescriptions()
153 * @return
154 */
155 public Set<DescriptionBase> getDescriptions() {
156 if(descriptions == null) {
157 this.descriptions = new HashSet<DescriptionBase>();
158 }
159 return this.descriptions;
160 }
161
162 /**
163 * Returns the {@link SpecimenDescription specimen descriptions} this specimen is part of.
164 * @see #getDescriptions()
165 * @return
166 */
167 @Transient
168 public Set<SpecimenDescription> getSpecimenDescriptions() {
169 return getSpecimenDescriptions(true);
170 }
171
172 /**
173 * Returns the {@link SpecimenDescription specimen descriptions} this specimen is part of.
174 * @see #getDescriptions()
175 * @return
176 */
177 @Transient
178 public Set<SpecimenDescription> getSpecimenDescriptions(boolean includeImageGallery) {
179 Set<SpecimenDescription> specimenDescriptions = new HashSet<SpecimenDescription>();
180 for (DescriptionBase descriptionBase : getDescriptions()){
181 if (descriptionBase.isInstanceOf(SpecimenDescription.class)){
182 if (includeImageGallery || descriptionBase.isImageGallery() == false){
183 specimenDescriptions.add(descriptionBase.deproxy(descriptionBase, SpecimenDescription.class));
184 }
185
186 }
187 }
188 return specimenDescriptions;
189 }
190 /**
191 * Returns the {@link SpecimenDescription specimen descriptions} this specimen is part of.
192 * @see #getDescriptions()
193 * @return
194 */
195 @Transient
196 public Set<SpecimenDescription> getSpecimenDescriptionImageGallery() {
197 Set<SpecimenDescription> specimenDescriptions = new HashSet<SpecimenDescription>();
198 for (DescriptionBase descriptionBase : getDescriptions()){
199 if (descriptionBase.isInstanceOf(SpecimenDescription.class)){
200 if (descriptionBase.isImageGallery() == true){
201 specimenDescriptions.add(descriptionBase.deproxy(descriptionBase, SpecimenDescription.class));
202 }
203 }
204 }
205 return specimenDescriptions;
206 }
207
208 /**
209 * Adds a new description to this specimen or observation
210 * @param description
211 */
212 public void addDescription(DescriptionBase description) {
213 this.descriptions.add(description);
214 if (! description.getDescribedSpecimenOrObservations().contains(this)){
215 description.addDescribedSpecimenOrObservation(this);
216 }
217 // Method method = ReflectionUtils.findMethod(SpecimenDescription.class, "addDescribedSpecimenOrObservation", new Class[] {SpecimenOrObservationBase.class});
218 // ReflectionUtils.makeAccessible(method);
219 // ReflectionUtils.invokeMethod(method, description, new Object[] {this});
220 }
221
222 /**
223 * Removes a specimen from a description (removes a description from this specimen)
224 * @param description
225 */
226 public void removeDescription(DescriptionBase description) {
227 this.descriptions.remove(description);
228 if (description.getDescribedSpecimenOrObservations().contains(this)){
229 description.removeDescribedSpecimenOrObservation(this);
230 }
231 // Method method = ReflectionUtils.findMethod(SpecimenDescription.class, "removeDescribedSpecimenOrObservations", new Class[] {SpecimenOrObservationBase.class});
232 // ReflectionUtils.makeAccessible(method);
233 // ReflectionUtils.invokeMethod(method, description, new Object[] {this});
234 }
235
236 public Set<DerivationEvent> getDerivationEvents() {
237 if(derivationEvents == null) {
238 this.derivationEvents = new HashSet<DerivationEvent>();
239 }
240 return this.derivationEvents;
241 }
242
243 public void addDerivationEvent(DerivationEvent derivationEvent) {
244 if (! this.derivationEvents.contains(derivationEvent)){
245 this.derivationEvents.add(derivationEvent);
246 derivationEvent.addOriginal(this);
247 }
248 }
249
250 public void removeDerivationEvent(DerivationEvent derivationEvent) {
251 this.derivationEvents.remove(derivationEvent);
252 }
253
254 public Set<DeterminationEvent> getDeterminations() {
255 if(determinations == null) {
256 this.determinations = new HashSet<DeterminationEvent>();
257 }
258 return this.determinations;
259 }
260
261 public void addDetermination(DeterminationEvent determination) {
262 // FIXME bidirectional integrity. Use protected Determination setter
263 this.determinations.add(determination);
264 }
265
266 public void removeDetermination(DeterminationEvent determination) {
267 // FIXME bidirectional integrity. Use protected Determination setter
268 this.determinations.remove(determination);
269 }
270
271 public Sex getSex() {
272 return sex;
273 }
274
275 public void setSex(Sex sex) {
276 this.sex = sex;
277 }
278
279 public Stage getLifeStage() {
280 return lifeStage;
281 }
282
283 public void setLifeStage(Stage lifeStage) {
284 this.lifeStage = lifeStage;
285 }
286
287 @Override
288 public String generateTitle(){
289 return getCacheStrategy().getTitleCache(this);
290 }
291
292 public Integer getIndividualCount() {
293 return individualCount;
294 }
295
296 public void setIndividualCount(Integer individualCount) {
297 this.individualCount = individualCount;
298 }
299
300 public Map<Language,LanguageString> getDefinition(){
301 return this.definition;
302 }
303
304 /**
305 * adds the {@link LanguageString description} to the {@link MultilanguageText multilanguage text}
306 * used to define <i>this</i> specimen or observation.
307 *
308 * @param description the languageString in with the title string and the given language
309 *
310 * @see #getDefinition()
311 * @see #putDefinition(Language, String)
312 * @deprecated should follow the put semantic of maps, this method will be removed in v4.0
313 * Use the {@link #putDefinition(LanguageString) putDefinition} method instead
314 */
315 @Deprecated
316 public void addDefinition(LanguageString description){
317 this.putDefinition(description);
318 }
319 /**
320 * adds the {@link LanguageString description} to the {@link MultilanguageText multilanguage text}
321 * used to define <i>this</i> specimen or observation.
322 *
323 * @param description the languageString in with the title string and the given language
324 *
325 * @see #getDefinition()
326 * @see #putDefinition(Language, String)
327 */
328 public void putDefinition(LanguageString description){
329 this.definition.put(description.getLanguage(),description);
330 }
331 /**
332 * Creates a {@link LanguageString language string} based on the given text string
333 * and the given {@link Language language} and adds it to the {@link MultilanguageText multilanguage text}
334 * used to define <i>this</i> specimen or observation.
335 *
336 * @param language the language in which the title string is formulated
337 * @param text the definition in a particular language
338 *
339 * @see #getDefinition()
340 * @see #putDefinition(LanguageString)
341 * @deprecated should follow the put semantic of maps, this method will be removed in v4.0
342 * Use the {@link #putDefinition(Language String) putDefinition} method instead
343 */
344 @Deprecated
345 public void addDefinition( String text, Language language){
346 this.putDefinition(language, text);
347 }
348 /**
349 * Creates a {@link LanguageString language string} based on the given text string
350 * and the given {@link Language language} and adds it to the {@link MultilanguageText multilanguage text}
351 * used to define <i>this</i> specimen or observation.
352 *
353 * @param language the language in which the title string is formulated
354 * @param text the definition in a particular language
355 *
356 * @see #getDefinition()
357 * @see #putDefinition(LanguageString)
358 */
359 public void putDefinition(Language language, String text){
360 this.definition.put(language, LanguageString.NewInstance(text, language));
361 }
362
363
364 public void removeDefinition(Language lang){
365 this.definition.remove(lang);
366 }
367
368 /**
369 * for derived units get the single next higher parental/original unit.
370 * If multiple original units exist throw error
371 * @return
372 */
373 @Transient
374 public SpecimenOrObservationBase getOriginalUnit(){
375 logger.warn("GetOriginalUnit not yet implemented");
376 return null;
377 }
378
379
380 //******************** CLONE **********************************************/
381
382 /* (non-Javadoc)
383 * @see eu.etaxonomy.cdm.model.media.IdentifiableMediaEntity#clone()
384 * @see eu.etaxonomy.cdm.model.common.IdentifiableEntity#clone()
385 * @see java.lang.Object#clone()
386 */
387 @Override
388 public Object clone() throws CloneNotSupportedException {
389 SpecimenOrObservationBase result = null;
390 result = (SpecimenOrObservationBase)super.clone();
391
392 //defininion (description, languageString)
393 result.definition = new HashMap<Language,LanguageString>();
394 for(LanguageString languageString : this.definition.values()) {
395 LanguageString newLanguageString = (LanguageString)languageString.clone();
396 result.putDefinition(newLanguageString);
397 }
398
399 //sex
400 result.setSex(this.sex);
401 //life stage
402 result.setLifeStage(this.lifeStage);
403
404 //Descriptions
405 for(DescriptionBase description : this.descriptions) {
406 result.addDescription(description);
407 }
408
409 //DeterminationEvent FIXME should clone() the determination
410 // as the relationship is OneToMany
411 for(DeterminationEvent determination : this.determinations) {
412 result.addDetermination(determination);
413 }
414
415 //DerivationEvent
416 for(DerivationEvent derivationEvent : this.derivationEvents) {
417 result.addDerivationEvent(derivationEvent);
418 }
419
420 //no changes to: individualCount
421 return result;
422 }
423 }