include trunk
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / description / DescriptionElementBase.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.description;
11
12
13 import java.util.ArrayList;
14 import java.util.HashMap;
15 import java.util.HashSet;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Set;
19
20 import javax.persistence.Entity;
21 import javax.persistence.FetchType;
22 import javax.persistence.Inheritance;
23 import javax.persistence.InheritanceType;
24 import javax.persistence.JoinTable;
25 import javax.persistence.ManyToMany;
26 import javax.persistence.ManyToOne;
27 import javax.persistence.OneToMany;
28 import javax.persistence.Transient;
29 import javax.xml.bind.annotation.XmlAccessType;
30 import javax.xml.bind.annotation.XmlAccessorType;
31 import javax.xml.bind.annotation.XmlElement;
32 import javax.xml.bind.annotation.XmlElementWrapper;
33 import javax.xml.bind.annotation.XmlIDREF;
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.IndexColumn;
42 import org.hibernate.envers.Audited;
43 import org.hibernate.search.annotations.IndexedEmbedded;
44
45 import eu.etaxonomy.cdm.jaxb.MultilanguageTextAdapter;
46 import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
47 import eu.etaxonomy.cdm.model.common.DescriptionElementSource;
48 import eu.etaxonomy.cdm.model.common.ISourceable;
49 import eu.etaxonomy.cdm.model.common.Language;
50 import eu.etaxonomy.cdm.model.common.LanguageString;
51 import eu.etaxonomy.cdm.model.common.MultilanguageText;
52 import eu.etaxonomy.cdm.model.common.TermVocabulary;
53 import eu.etaxonomy.cdm.model.media.Media;
54 import eu.etaxonomy.cdm.model.name.TaxonNameBase;
55 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
56 import eu.etaxonomy.cdm.model.reference.ReferenceBase;
57 import eu.etaxonomy.cdm.model.taxon.Taxon;
58 import eu.etaxonomy.cdm.strategy.merge.Merge;
59 import eu.etaxonomy.cdm.strategy.merge.MergeMode;
60
61 /**
62 * The upmost (abstract) class for a piece of information) about
63 * a {@link SpecimenOrObservationBase specimen}, a {@link Taxon taxon} or even a {@link TaxonNameBase taxon name}.
64 * A concrete description element assigns descriptive data to one {@link Feature feature}.<BR>
65 * Experts use the word feature for the property itself but not for the actual
66 * description element. Therefore naming this class FeatureBase would have
67 * leaded to confusion.
68 * <P>
69 * This class corresponds to: <ul>
70 * <li> DescriptionsBaseType according to the the SDD schema
71 * <li> InfoItem according to the TDWG ontology
72 * <li> MeasurementOrFactAtomised according to the ABCD schema
73 * </ul>
74 *
75 * @author m.doering
76 * @version 1.0
77 * @created 08-Nov-2007 13:06:24
78 */
79 @XmlAccessorType(XmlAccessType.FIELD)
80 @XmlType(name = "DescriptionElementBase", propOrder = {
81 "feature",
82 "modifiers",
83 "modifyingText",
84 "media",
85 "inDescription",
86 "sources"
87 })
88 @Entity
89 @Audited
90 @Inheritance(strategy=InheritanceType.SINGLE_TABLE)
91 public abstract class DescriptionElementBase extends AnnotatableEntity implements ISourceable<DescriptionElementSource>, IModifiable {
92 private static final long serialVersionUID = 5000910777835755905L;
93 @SuppressWarnings("unused")
94 private static final Logger logger = Logger.getLogger(DescriptionElementBase.class);
95
96 //type, category of information. In structured descriptions characters
97 @XmlElement(name = "Feature")
98 @XmlIDREF
99 @XmlSchemaType(name = "IDREF")
100 @ManyToOne(fetch = FetchType.LAZY)
101 //@Cascade(CascadeType.SAVE_UPDATE)
102 @Cascade(CascadeType.MERGE)
103 @IndexedEmbedded
104 private Feature feature;
105
106 @XmlElementWrapper(name = "Modifiers")
107 @XmlElement(name = "Modifier")
108 @XmlIDREF
109 @XmlSchemaType(name = "IDREF")
110 @ManyToMany(fetch = FetchType.LAZY)
111 @JoinTable(name="DescriptionElementBase_Modifier")
112 private Set<Modifier> modifiers = new HashSet<Modifier>();
113
114 @XmlElement(name = "ModifyingText")
115 @XmlJavaTypeAdapter(MultilanguageTextAdapter.class)
116 @OneToMany(fetch = FetchType.LAZY)
117 @JoinTable(name = "DescriptionElementBase_ModifyingText")
118 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
119 private Map<Language,LanguageString> modifyingText = new HashMap<Language,LanguageString>();
120
121 @XmlElementWrapper(name = "Media")
122 @XmlElement(name = "Medium")
123 @XmlIDREF
124 @XmlSchemaType(name = "IDREF")
125 @ManyToMany(fetch = FetchType.LAZY)
126 @IndexColumn(name="sortIndex", base = 0)
127 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
128 private List<Media> media = new ArrayList<Media>();
129
130 @XmlElement(name = "InDescription")
131 @XmlIDREF
132 @XmlSchemaType(name = "IDREF")
133 @ManyToOne(fetch = FetchType.LAZY)
134 @Cascade(CascadeType.SAVE_UPDATE)
135 @IndexedEmbedded
136 private DescriptionBase inDescription;
137
138 @XmlElementWrapper(name = "Sources")
139 @XmlElement(name = "DescriptionElementSource")
140 @OneToMany(fetch = FetchType.LAZY)
141 @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE, CascadeType.DELETE_ORPHAN})
142 @Merge(MergeMode.ADD_CLONE)
143 private Set<DescriptionElementSource> sources = new HashSet<DescriptionElementSource>();
144
145
146
147 // ************* CONSTRUCTORS *************/
148 /**
149 * Class constructor: creates a new empty description element instance.
150 *
151 * @see #DescriptionElementBase(Feature)
152 */
153 protected DescriptionElementBase(){
154 }
155
156 /**
157 * Class constructor: creates a new description element instance with the
158 * given {@link Feature feature} that is described or measured.
159 *
160 * @param feature the feature described or measured
161 * @see #DescriptionElementBase()
162 */
163 protected DescriptionElementBase(Feature feature){
164 if (feature == null){
165 feature = Feature.UNKNOWN();
166 }
167 this.feature = feature;
168 }
169
170 /**
171 * Returns the list of {@link Media media} (that is pictures, movies,
172 * recorded sounds ...) <i>this</i> description element is based on.
173 */
174 public List<Media> getMedia(){
175 return this.media;
176 }
177
178 /**
179 * Adds a {@link Media media} to the list of {@link #getMedia() media}
180 * <i>this</i> description element is based on.
181 *
182 * @param media the media to be added to <i>this</i> description element
183 * @see #getMedia()
184 */
185 public void addMedia(Media media){
186 this.media.add(media);
187 }
188 /**
189 * Removes one element from the list of {@link #getMedia() media}
190 * <i>this</i> description element is based on.
191 *
192 * @param media the media which should be removed
193 * @see #getMedia()
194 * @see #addMedia(Media)
195 */
196 public void removeMedia(Media media){
197 this.media.remove(media);
198 }
199
200 /**
201 * Returns the {@link DescriptionBase description} that <i>this</i> DescriptionElement is
202 * part of.
203 * @return
204 */
205 public DescriptionBase getInDescription() {
206 return this.inDescription;
207 }
208
209 /**
210 * @see #setInDescription()
211 */
212 protected void setInDescription(DescriptionBase inDescription) {
213 this.inDescription = inDescription;
214 }
215
216 /**
217 * Does exactly the same as getFeature().
218 * @author ben.clark
219 * FIXME Is there a need to have two methods with different names which do the same thing?
220 *
221 * @see #getFeature()
222 */
223 @Transient
224 @Deprecated //will be removed in version 3.
225 public Feature getType(){
226 return this.getFeature();
227 }
228
229 /**
230 * Does exactly the same as setFeature(Feature).
231 *
232 * @param type the feature to be described or measured
233 * @see #setFeature(Feature)
234 * @see #getFeature()
235 */
236 @Deprecated //will be removed in version 3
237 public void setType(Feature type){
238 this.setFeature(type);
239 }
240
241 /**
242 * Returns the {@link Feature feature} <i>this</i> description element is for.
243 * A feature is a property that can be described or measured but not the
244 * description or the measurement itself.
245 */
246 public Feature getFeature(){
247 return this.feature;
248 }
249
250 /**
251 * @see #getFeature()
252 */
253 public void setFeature(Feature feature){
254 this.feature = feature;
255 }
256
257 /**
258 * Returns the set of {@link Modifier modifiers} used to qualify the validity of
259 * <i>this</i> description element. This is only metainformation.
260 */
261 public Set<Modifier> getModifiers(){
262 return this.modifiers;
263 }
264
265 /**
266 * Adds a {@link Modifier modifier} to the set of {@link #getModifiers() modifiers}
267 * used to qualify the validity of <i>this</i> description element.
268 *
269 * @param modifier the modifier to be added to <i>this</i> description element
270 * @see #getModifiers()
271 */
272 public void addModifier(Modifier modifier){
273 this.modifiers.add(modifier);
274 }
275 /**
276 * Removes one element from the set of {@link #getModifiers() modifiers}
277 * used to qualify the validity of <i>this</i> description element.
278 *
279 * @param modifier the modifier which should be removed
280 * @see #getModifiers()
281 * @see #addModifier(Modifier)
282 */
283 public void removeModifier(Modifier modifier){
284 this.modifiers.remove(modifier);
285 }
286
287
288 /**
289 * Returns the {@link MultilanguageText multilanguage text} used to qualify the validity
290 * of <i>this</i> description element. The different {@link LanguageString language strings}
291 * contained in the multilanguage text should all have the same meaning.<BR>
292 * A multilanguage text does not belong to a controlled {@link TermVocabulary term vocabulary}
293 * as a {@link Modifier modifier} does.
294 * <P>
295 * NOTE: the actual content of <i>this</i> description element is NOT
296 * stored in the modifying text. This is only metainformation
297 * (like "Some experts express doubt about this assertion").
298 */
299 public Map<Language,LanguageString> getModifyingText(){
300 return this.modifyingText;
301 }
302
303 /**
304 * Adds a translated {@link LanguageString text in a particular language}
305 * to the {@link MultilanguageText multilanguage text} used to qualify the validity
306 * of <i>this</i> description element.
307 *
308 * @param description the language string describing the validity
309 * in a particular language
310 * @see #getModifyingText()
311 * @see #addModifyingText(String, Language)
312 */
313 public LanguageString addModifyingText(LanguageString description){
314 return this.modifyingText.put(description.getLanguage(),description);
315 }
316 /**
317 * Creates a {@link LanguageString language string} based on the given text string
318 * and the given {@link Language language} and adds it to the {@link MultilanguageText multilanguage text}
319 * used to qualify the validity of <i>this</i> description element.
320 *
321 * @param text the string describing the validity
322 * in a particular language
323 * @param language the language in which the text string is formulated
324 * @see #getModifyingText()
325 * @see #addModifyingText(LanguageString)
326 */
327 public LanguageString addModifyingText(String text, Language language){
328 return this.modifyingText.put(language, LanguageString.NewInstance(text, language));
329 }
330 /**
331 * Removes from the {@link MultilanguageText multilanguage text} used to qualify the validity
332 * of <i>this</i> description element the one {@link LanguageString language string}
333 * with the given {@link Language language}.
334 *
335 * @param language the language in which the language string to be removed
336 * has been formulated
337 * @see #getModifyingText()
338 */
339 public LanguageString removeModifyingText(Language language){
340 return this.modifyingText.remove(language);
341 }
342
343 /* (non-Javadoc)
344 * @see eu.etaxonomy.cdm.model.common.ISourceable#getSources()
345 */
346 public Set<DescriptionElementSource> getSources() {
347 return this.sources;
348 }
349
350 /* (non-Javadoc)
351 * @see eu.etaxonomy.cdm.model.common.ISourceable#addSource(eu.etaxonomy.cdm.model.common.IOriginalSource)
352 */
353 public void addSource(DescriptionElementSource source) {
354 if (source != null){
355 DescriptionElementBase oldSourcedObj = source.getSourcedObj();
356 if (oldSourcedObj != null && oldSourcedObj != this){
357 oldSourcedObj.getSources().remove(source);
358 }
359 this.sources.add(source);
360 source.setSourcedObj(this);
361 }
362 }
363
364 /* (non-Javadoc)
365 * @see eu.etaxonomy.cdm.model.common.ISourceable#addSource(java.lang.String, java.lang.String, eu.etaxonomy.cdm.model.reference.ReferenceBase, java.lang.String)
366 */
367 public DescriptionElementSource addSource(String id, String idNamespace, ReferenceBase citation, String microCitation) {
368 if (id == null && idNamespace == null && citation == null && microCitation == null){
369 return null;
370 }
371 DescriptionElementSource source = DescriptionElementSource.NewInstance(id, idNamespace, citation, microCitation);
372 addSource(source);
373 return source;
374 }
375
376 public void addSource(String id, String idNamespace, ReferenceBase citation, String microReference, TaxonNameBase nameUsedInSource, String originalNameString){
377 DescriptionElementSource newSource = DescriptionElementSource.NewInstance(id, idNamespace, citation, microReference, nameUsedInSource, originalNameString);
378 addSource(newSource);
379 }
380
381 /* (non-Javadoc)
382 * @see eu.etaxonomy.cdm.model.common.ISourceable#removeSource(eu.etaxonomy.cdm.model.common.IOriginalSource)
383 */
384 public void removeSource(DescriptionElementSource source) {
385 this.sources.remove(source);
386 }
387
388
389 /**
390 * Gets the citation micro reference of the first source. This method is deprecated and exists only to be compliant with version 2.0.
391 * It will be removed in v2.3
392 * @return
393 */
394 @Transient
395 @Deprecated
396 public String getCitationMicroReference(){
397 if (this.sources.size() < 1){
398 return null;
399 }else{
400 return this.sources.iterator().next().getCitationMicroReference();
401 }
402 }
403
404 /**
405 * Sets the citation micro reference of the first source. This method is deprecated and exists only to be compliant with version 2.0.
406 * It will be removed in v2.3
407 * If more than one source exists an IllegalStateException is thrown
408 **/
409 @Transient
410 @Deprecated
411 public void setCitationMicroReference(String citationMicroReference){
412 if (this.sources.size() < 1){
413 ReferenceBase citation = null;
414 this.addSource(DescriptionElementSource.NewInstance(null, null, citation, citationMicroReference));
415 }else if (this.sources.size() > 1){
416 throw new IllegalStateException("When adding a microcitation via the setCitationMicroReference method there must be only one source available");
417 }else{
418 this.sources.iterator().next().setCitationMicroReference(citationMicroReference);
419 }
420 }
421
422 /**
423 * Gets the citation of the first source. This method is deprecated and exists only to be compliant with version 2.0.
424 * It will be removed in v2.3
425 */
426 @Transient
427 @Deprecated
428 public ReferenceBase getCitation(){
429 if (this.sources.size() < 1){
430 return null;
431 }else{
432 return this.sources.iterator().next().getCitation();
433 }
434 }
435
436 /**
437 * Sets the citation of the first source. This method is deprecated and exists only to be compliant with version 2.0.
438 * It will be removed in v2.3
439 * If more than one source exists an IllegalStateException is thrown
440 **/
441 @Deprecated
442 public void setCitation(ReferenceBase citation) {
443 if (this.sources.size() < 1){
444 this.addSource(DescriptionElementSource.NewInstance(null, null, citation, null));
445 }else if (this.sources.size() > 1){
446 throw new IllegalStateException("When adding a citation via the setCitation method there must be only one source available");
447 }else{
448 this.sources.iterator().next().setCitation(citation);
449 }
450 }
451
452
453 /**
454 * Gets the original name string of the first source. This method is deprecated and exists only to be compliant with version 2.0.
455 * It will be removed in v2.3
456 * @return
457 */
458 @Transient
459 @Deprecated
460 public String getOriginalNameString(){
461 if (this.sources.size() < 1){
462 return null;
463 }else{
464 return this.sources.iterator().next().getOriginalNameString();
465 }
466 }
467
468 /**
469 * Sets the original name string of the first source. This method is deprecated and exists only to be compliant with version 2.0.
470 * It will be removed in v2.3
471 * If more than one source exists an IllegalStateException is thrown
472 **/
473 @Transient
474 @Deprecated
475 public void setOriginalNameString(String originalNameString){
476 if (this.sources.size() < 1){
477 this.addSource(DescriptionElementSource.NewInstance(null, null, null, null, null, originalNameString));
478 }else if (this.sources.size() > 1){
479 throw new IllegalStateException("When adding a microcitation via the setCitationMicroReference method there must be only one source available");
480 }else{
481 this.sources.iterator().next().setOriginalNameString(originalNameString);
482 }
483 }
484
485
486 /**
487 * Gets the name used in source of the first source. This method is deprecated and exists only to be compliant with version 2.0.
488 * It will be removed in v2.3
489 */
490 @Transient
491 @Deprecated
492 public TaxonNameBase getNameUsedInReference(){
493 if (this.sources.size() < 1){
494 return null;
495 }else{
496 return this.sources.iterator().next().getNameUsedInSource();
497 }
498 }
499
500 /**
501 * Sets the name used in reference of the first source. This method is deprecated and exists only to be compliant with version 2.0.
502 * It will be removed in v2.3
503 * If more than one source exists an IllegalStateException is thrown
504 **/
505 @Deprecated
506 public void setNameUsedInReference(TaxonNameBase nameUsedInSource) {
507 if (this.sources.size() < 1){
508 this.addSource(DescriptionElementSource.NewInstance(null, null, null, null, nameUsedInSource, null));
509 }else if (this.sources.size() > 1){
510 throw new IllegalStateException("When adding a citation via the setCitation method there must be only one source available");
511 }else{
512 this.sources.iterator().next().setNameUsedInSource(nameUsedInSource);
513 }
514 }
515
516 //************************** CLONE **********************************************************/
517
518 /**
519 * Clones the description element. The element is <b>not</b> added to the same
520 * description as the orginal element (inDescription is set to <code>null</null>.
521 * @see eu.etaxonomy.cdm.model.common.AnnotatableEntity#clone()
522 */
523 @Override
524 public Object clone() throws CloneNotSupportedException{
525 DescriptionElementBase result = (DescriptionElementBase)super.clone();
526
527 //Sources
528 result.sources = new HashSet<DescriptionElementSource>();
529 for (DescriptionElementSource source : getSources()){
530 DescriptionElementSource newSource = (DescriptionElementSource)source.clone();
531 result.addSource(newSource);
532 }
533
534 //inDescription
535 result.inDescription = null;
536
537 return result;
538 }
539
540 /**
541 * Clones the description element.<BR>
542 * The new element is added to the <code>description</code>.<BR>
543 * @see eu.etaxonomy.cdm.model.common.AnnotatableEntity#clone()
544 */
545 public DescriptionElementBase clone(DescriptionBase description) throws CloneNotSupportedException{
546 DescriptionElementBase result = (DescriptionElementBase)clone();
547 description.addElement(result);
548 return result;
549 }
550
551
552 }