2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
10 package eu
.etaxonomy
.cdm
.model
.occurrence
;
13 import java
.util
.ArrayList
;
14 import java
.util
.Collection
;
15 import java
.util
.HashMap
;
16 import java
.util
.HashSet
;
20 import javax
.persistence
.Column
;
21 import javax
.persistence
.Entity
;
22 import javax
.persistence
.FetchType
;
23 import javax
.persistence
.Index
;
24 import javax
.persistence
.Inheritance
;
25 import javax
.persistence
.InheritanceType
;
26 import javax
.persistence
.ManyToMany
;
27 import javax
.persistence
.ManyToOne
;
28 import javax
.persistence
.MapKeyJoinColumn
;
29 import javax
.persistence
.OneToMany
;
30 import javax
.persistence
.Table
;
31 import javax
.persistence
.Transient
;
32 import javax
.validation
.constraints
.NotNull
;
33 import javax
.xml
.bind
.annotation
.XmlAccessType
;
34 import javax
.xml
.bind
.annotation
.XmlAccessorType
;
35 import javax
.xml
.bind
.annotation
.XmlAttribute
;
36 import javax
.xml
.bind
.annotation
.XmlElement
;
37 import javax
.xml
.bind
.annotation
.XmlElementWrapper
;
38 import javax
.xml
.bind
.annotation
.XmlIDREF
;
39 import javax
.xml
.bind
.annotation
.XmlRootElement
;
40 import javax
.xml
.bind
.annotation
.XmlSchemaType
;
41 import javax
.xml
.bind
.annotation
.XmlType
;
42 import javax
.xml
.bind
.annotation
.adapters
.XmlJavaTypeAdapter
;
44 import org
.apache
.log4j
.Logger
;
45 import org
.hibernate
.annotations
.Cascade
;
46 import org
.hibernate
.annotations
.CascadeType
;
47 import org
.hibernate
.annotations
.Type
;
48 import org
.hibernate
.envers
.Audited
;
49 import org
.hibernate
.search
.annotations
.Analyze
;
50 import org
.hibernate
.search
.annotations
.Field
;
51 import org
.hibernate
.search
.annotations
.FieldBridge
;
52 import org
.hibernate
.search
.annotations
.Fields
;
53 import org
.hibernate
.search
.annotations
.IndexedEmbedded
;
54 import org
.hibernate
.search
.annotations
.SortableField
;
55 import org
.hibernate
.search
.annotations
.Store
;
56 import org
.hibernate
.search
.bridge
.builtin
.BooleanBridge
;
58 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
59 import eu
.etaxonomy
.cdm
.hibernate
.search
.StripHtmlBridge
;
60 import eu
.etaxonomy
.cdm
.jaxb
.FormattedTextAdapter
;
61 import eu
.etaxonomy
.cdm
.jaxb
.MultilanguageTextAdapter
;
62 import eu
.etaxonomy
.cdm
.model
.common
.IIntextReferenceTarget
;
63 import eu
.etaxonomy
.cdm
.model
.common
.IMultiLanguageTextHolder
;
64 import eu
.etaxonomy
.cdm
.model
.common
.IPublishable
;
65 import eu
.etaxonomy
.cdm
.model
.common
.IdentifiableEntity
;
66 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
67 import eu
.etaxonomy
.cdm
.model
.common
.LanguageString
;
68 import eu
.etaxonomy
.cdm
.model
.common
.MultilanguageText
;
69 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionBase
;
70 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementBase
;
71 import eu
.etaxonomy
.cdm
.model
.description
.IDescribable
;
72 import eu
.etaxonomy
.cdm
.model
.description
.SpecimenDescription
;
73 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
74 import eu
.etaxonomy
.cdm
.model
.description
.TaxonNameDescription
;
75 import eu
.etaxonomy
.cdm
.model
.term
.DefinedTerm
;
76 import eu
.etaxonomy
.cdm
.model
.term
.TermType
;
77 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
78 import eu
.etaxonomy
.cdm
.strategy
.match
.Match
;
79 import eu
.etaxonomy
.cdm
.strategy
.match
.Match
.ReplaceMode
;
80 import eu
.etaxonomy
.cdm
.strategy
.match
.MatchMode
;
83 * type figures are observations with at least a figure object in media
85 * @since 08-Nov-2007 13:06:41
87 @XmlAccessorType(XmlAccessType
.FIELD
)
88 @XmlType(name
= "SpecimenOrObservationBase", propOrder
= {
91 "protectedIdentityCache",
103 @XmlRootElement(name
= "SpecimenOrObservationBase")
106 @Inheritance(strategy
=InheritanceType
.SINGLE_TABLE
)
107 @Table(name
="SpecimenOrObservationBase", indexes
= { @Index(name
= "specimenOrObservationBaseTitleCacheIndex", columnList
= "titleCache"),
108 @Index(name
= "specimenOrObservationBaseIdentityCacheIndex", columnList
= "identityCache") })
109 public abstract class SpecimenOrObservationBase
<S
extends IIdentifiableEntityCacheStrategy
<?
>>
110 extends IdentifiableEntity
<S
>
111 implements IMultiLanguageTextHolder
, IIntextReferenceTarget
, IDescribable
<DescriptionBase
<S
>>, IPublishable
{
113 private static final long serialVersionUID
= 6932680139334408031L;
114 private static final Logger logger
= Logger
.getLogger(SpecimenOrObservationBase
.class);
117 * An indication of what the unit record describes.
119 * NOTE: The name of the attribute was chosen against the common naming conventions of the CDM
120 * as it is well known in common standards like ABCD and DarwinCore. According to CDM naming
121 * conventions it would be specimenOrObservationType.
123 * @see ABCD: DataSets/DataSet/Units/Unit/RecordBasis
124 * @see Darwin Core: http://wiki.tdwg.org/twiki/bin/view/DarwinCore/BasisOfRecord
126 @XmlAttribute(name
="RecordBasis")
127 @Column(name
="recordBasis")
129 @Type(type
= "eu.etaxonomy.cdm.hibernate.EnumUserType",
130 parameters
= {@org.hibernate
.annotations
.Parameter(name
= "enumClass", value
= "eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType")}
133 private SpecimenOrObservationType recordBasis
;
136 @XmlElementWrapper(name
= "Descriptions")
137 @XmlElement(name
= "Description")
138 @OneToMany(mappedBy
="describedSpecimenOrObservation", fetch
= FetchType
.LAZY
)
139 @Cascade({CascadeType
.SAVE_UPDATE
,CascadeType
.MERGE
})
141 private Set
<DescriptionBase
<S
>> descriptions
= new HashSet
<>();
144 @XmlElementWrapper(name
= "Determinations")
145 @XmlElement(name
= "Determination")
146 @OneToMany(mappedBy
="identifiedUnit", orphanRemoval
=true)
147 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
})
148 @IndexedEmbedded(depth
= 2)
150 private Set
<DeterminationEvent
> determinations
= new HashSet
<>();
152 @XmlElement(name
= "Sex")
154 @XmlSchemaType(name
= "IDREF")
155 @ManyToOne(fetch
= FetchType
.LAZY
)
156 private DefinedTerm sex
;
158 @XmlElement(name
= "LifeStage")
160 @XmlSchemaType(name
= "IDREF")
161 @ManyToOne(fetch
= FetchType
.LAZY
)
162 private DefinedTerm lifeStage
;
165 * Part(s) of organism or class of materials represented by this unit.
166 * Example: fruits, seeds, tissue, gDNA, leaves
168 * @see ABCD: DataSets/DataSet/Units/Unit/KindOfUnit
169 * @see TermType#KindOfUnit
171 @XmlElement(name
= "KindOfUnit")
173 @XmlSchemaType(name
= "IDREF")
174 @ManyToOne(fetch
= FetchType
.LAZY
)
175 // @IndexedEmbedded(depth=1)
176 private DefinedTerm kindOfUnit
;
178 @XmlElement(name
= "IndividualCount")
179 @Field(analyze
= Analyze
.NO
)
180 private String individualCount
;
183 * The preferred stable identifier (URI) as discussed in
184 * {@link http://dev.e-taxonomy.eu/trac/ticket/5606}
186 @XmlElement(name
= "PreferredStableUri")
187 @Field(analyze
= Analyze
.NO
)
188 @Type(type
="uriUserType")
189 private URI preferredStableUri
;
191 // the verbatim description of this occurrence. Free text usable when no atomised data is available.
192 // in conjunction with titleCache which serves as the "citation" string for this object
193 @XmlElement(name
= "Description")
194 @XmlJavaTypeAdapter(MultilanguageTextAdapter
.class)
195 @OneToMany(fetch
= FetchType
.LAZY
, orphanRemoval
=true)
196 @MapKeyJoinColumn(name
="definition_mapkey_id")
197 @Cascade({CascadeType
.SAVE_UPDATE
,CascadeType
.MERGE
, CascadeType
.DELETE
})
200 protected Map
<Language
,LanguageString
> definition
= new HashMap
<>();
202 // events that created derivedUnits from this unit
203 @XmlElementWrapper(name
= "DerivationEvents")
204 @XmlElement(name
= "DerivationEvent")
206 @XmlSchemaType(name
= "IDREF")
207 @ManyToMany(fetch
=FetchType
.LAZY
)
208 @Cascade({CascadeType
.SAVE_UPDATE
,CascadeType
.MERGE
, CascadeType
.DELETE
})
210 protected Set
<DerivationEvent
> derivationEvents
= new HashSet
<>();
212 @XmlAttribute(name
= "publish")
213 @Field(analyze
= Analyze
.NO
)
214 @FieldBridge(impl
=BooleanBridge
.class)
215 private boolean publish
= true;
217 @XmlElement(name
= "IdentityCache", required
= false)
218 @XmlJavaTypeAdapter(FormattedTextAdapter
.class)
219 @Match(value
=MatchMode
.CACHE
, cacheReplaceMode
=ReplaceMode
.ALL
)
220 // @NotEmpty(groups = Level2.class) // implictly NotNull
222 @Field(store
=Store
.YES
),
223 // If the field is only needed for sorting and nothing else, you may configure it as
224 // un-indexed and un-stored, thus avoid unnecessary index growth.
225 @Field(name
= "identityCache__sort", analyze
= Analyze
.NO
, store
=Store
.NO
, index
= org
.hibernate
.search
.annotations
.Index
.NO
)
227 @SortableField(forField
= "identityCache__sort")
228 @FieldBridge(impl
=StripHtmlBridge
.class)
229 private String identityCache
;
232 //if true identityCache will not be automatically generated/updated
233 @XmlElement(name
= "ProtectedIdentityCache")
234 private boolean protectedIdentityCache
;
237 //********************************** CONSTRUCTOR *********************************/
239 //for hibernate use only
241 protected SpecimenOrObservationBase(){
245 protected SpecimenOrObservationBase(SpecimenOrObservationType recordBasis
) {
247 if (recordBasis
== null){ throw new IllegalArgumentException("RecordBasis must not be null");}
248 this.recordBasis
= recordBasis
;
253 * Subclasses should implement setting the default cache strate
255 protected abstract void initDefaultCacheStrategy();
258 //************************* GETTER / SETTER ***********************/
261 /**@see #recordBasis */
262 public SpecimenOrObservationType
getRecordBasis() {
265 /**@see #recordBasis */
266 public void setRecordBasis(SpecimenOrObservationType recordBasis
) {
267 this.recordBasis
= recordBasis
;
272 * @return the identityCache
274 public String
getIdentityCache() {
275 return identityCache
;
278 * @Deprecated For special use only.
279 * Use {@link #setIdentityCache(String, boolean)} instead
282 public void setIdentityCache(String identityCache
) {
283 this.identityCache
= identityCache
;
286 public void setIdentityCache(String identityCache
, boolean isProtected
) {
287 this.protectedIdentityCache
= isProtected
;
288 setIdentityCache(identityCache
);
292 * @return the protectedIdentityCache
294 public boolean isProtectedIdentityCache() {
295 return protectedIdentityCache
;
298 * @param protectedIdentityCache the protectedIdentityCache to set
300 public void setProtectedIdentityCache(boolean protectedIdentityCache
) {
301 this.protectedIdentityCache
= protectedIdentityCache
;
304 /**@see #preferredStableUri */
305 public URI
getPreferredStableUri() {
306 return preferredStableUri
;
308 /**@see #preferredStableUri */
309 public void setPreferredStableUri(URI preferredStableUri
) {
310 this.preferredStableUri
= preferredStableUri
;
315 * Returns the boolean value indicating if this specimen or observation should be withheld
316 * (<code>publish=false</code>) or not (<code>publish=true</code>) during any publication
317 * process to the general public.
318 * This publish flag implementation is preliminary and may be replaced by a more general
319 * implementation of READ rights in future.<BR>
320 * The default value is <code>true</code>.
323 public boolean isPublish() {
332 public void setPublish(boolean publish
) {
333 this.publish
= publish
;
337 * The descriptions this specimen or observation is part of.<BR>
338 * A specimen can not only have it's own {@link SpecimenDescription specimen description }
339 * but can also be part of a {@link TaxonDescription taxon description} or a
340 * {@link TaxonNameDescription taxon name description}.<BR>
341 * @see #getSpecimenDescriptions()
345 public Set
<DescriptionBase
<S
>> getDescriptions() {
346 if(descriptions
== null) {
347 this.descriptions
= new HashSet
<>();
349 return this.descriptions
;
353 * Returns the {@link SpecimenDescription specimen descriptions} this specimen is part of.
354 * @see #getDescriptions()
358 public Set
<SpecimenDescription
> getSpecimenDescriptions() {
359 return getSpecimenDescriptions(true);
363 * Returns the {@link SpecimenDescription specimen descriptions} this specimen is part of.
364 * @see #getDescriptions()
368 public Set
<SpecimenDescription
> getSpecimenDescriptions(boolean includeImageGallery
) {
369 Set
<SpecimenDescription
> specimenDescriptions
= new HashSet
<>();
370 for (DescriptionBase
<?
> descriptionBase
: getDescriptions()){
371 if (descriptionBase
.isInstanceOf(SpecimenDescription
.class)){
372 if (includeImageGallery
|| descriptionBase
.isImageGallery() == false){
373 specimenDescriptions
.add(descriptionBase
.deproxy(descriptionBase
, SpecimenDescription
.class));
378 return specimenDescriptions
;
381 * Returns the {@link SpecimenDescription specimen descriptions} which act as an image gallery
382 * and which this specimen is part of.
383 * @see #getDescriptions()
387 public Set
<SpecimenDescription
> getSpecimenDescriptionImageGallery() {
388 Set
<SpecimenDescription
> specimenDescriptions
= new HashSet
<>();
389 for (DescriptionBase
<?
> descriptionBase
: getDescriptions()){
390 if (descriptionBase
.isInstanceOf(SpecimenDescription
.class)){
391 if (descriptionBase
.isImageGallery() == true){
392 specimenDescriptions
.add(descriptionBase
.deproxy(descriptionBase
, SpecimenDescription
.class));
396 return specimenDescriptions
;
400 * Adds a new description to this specimen or observation
404 public void addDescription(DescriptionBase description
) {
405 if (description
.getDescribedSpecimenOrObservation() != null){
406 description
.getDescribedSpecimenOrObservation().removeDescription(description
);
408 descriptions
.add(description
);
409 description
.setDescribedSpecimenOrObservation(this);
413 * Removes a specimen from a description (removes a description from this specimen)
417 public void removeDescription(DescriptionBase description
) {
418 boolean existed
= descriptions
.remove(description
);
420 description
.setDescribedSpecimenOrObservation(null);
425 public Set
<DerivationEvent
> getDerivationEvents() {
426 if(derivationEvents
== null) {
427 this.derivationEvents
= new HashSet
<>();
429 return this.derivationEvents
;
432 public void addDerivationEvent(DerivationEvent derivationEvent
) {
433 if (! this.derivationEvents
.contains(derivationEvent
)){
434 this.derivationEvents
.add(derivationEvent
);
435 derivationEvent
.addOriginal(this);
439 public void removeDerivationEvent(DerivationEvent derivationEvent
) {
440 if (this.derivationEvents
.contains(derivationEvent
)){
441 this.derivationEvents
.remove(derivationEvent
);
442 derivationEvent
.removeOriginal(this);
446 public Set
<DeterminationEvent
> getDeterminations() {
447 if(determinations
== null) {
448 this.determinations
= new HashSet
<>();
450 return this.determinations
;
453 public void addDetermination(DeterminationEvent determination
) {
454 // FIXME bidirectional integrity. Use protected Determination setter
455 this.determinations
.add(determination
);
458 public void removeDetermination(DeterminationEvent determination
) {
459 // FIXME bidirectional integrity. Use protected Determination setter
460 this.determinations
.remove(determination
);
463 public DefinedTerm
getSex() {
467 public void setSex(DefinedTerm sex
) {
471 public DefinedTerm
getLifeStage() {
475 public void setLifeStage(DefinedTerm lifeStage
) {
476 this.lifeStage
= lifeStage
;
484 public DefinedTerm
getKindOfUnit() {
492 public void setKindOfUnit(DefinedTerm kindOfUnit
) {
493 this.kindOfUnit
= kindOfUnit
;
496 public String
getIndividualCount() {
497 return individualCount
;
500 public void setIndividualCount(String individualCount
) {
501 this.individualCount
= individualCount
;
504 public Map
<Language
,LanguageString
> getDefinition(){
505 return this.definition
;
509 * adds the {@link LanguageString description} to the {@link MultilanguageText multilanguage text}
510 * used to define <i>this</i> specimen or observation.
512 * @param description the languageString in with the title string and the given language
514 * @see #getDefinition()
515 * @see #putDefinition(Language, String)
517 public void putDefinition(LanguageString description
){
518 this.definition
.put(description
.getLanguage(),description
);
522 * Creates a {@link LanguageString language string} based on the given text string
523 * and the given {@link Language language} and adds it to the {@link MultilanguageText multilanguage text}
524 * used to define <i>this</i> specimen or observation.
526 * @param language the language in which the title string is formulated
527 * @param text the definition in a particular language
529 * @see #getDefinition()
530 * @see #putDefinition(LanguageString)
532 public void putDefinition(Language language
, String text
){
533 this.definition
.put(language
, LanguageString
.NewInstance(text
, language
));
537 public void removeDefinition(Language lang
){
538 this.definition
.remove(lang
);
542 * for derived units get the single next higher parental/original unit.
543 * If multiple original units exist throw error
547 public SpecimenOrObservationBase
getOriginalUnit(){
548 logger
.warn("GetOriginalUnit not yet implemented");
553 public boolean hasCharacterData() {
554 Set
<DescriptionBase
<S
>> descriptions
= this.getDescriptions();
555 for (DescriptionBase
<?
> descriptionBase
: descriptions
) {
556 if (descriptionBase
.isInstanceOf(SpecimenDescription
.class)) {
557 SpecimenDescription specimenDescription
= HibernateProxyHelper
.deproxy(descriptionBase
, SpecimenDescription
.class);
558 Set
<DescriptionElementBase
> elements
= specimenDescription
.getElements();
559 for (DescriptionElementBase descriptionElementBase
: elements
) {
560 if (descriptionElementBase
.isCharacterData()){
570 * Returns a list of all description items which
574 public Collection
<DescriptionElementBase
> characterData() {
575 Collection
<DescriptionElementBase
> states
= new ArrayList
<>();
576 Set
<DescriptionBase
<S
>> descriptions
= this.getDescriptions();
577 for (DescriptionBase
<?
> descriptionBase
: descriptions
) {
578 if (descriptionBase
.isInstanceOf(SpecimenDescription
.class)) {
579 SpecimenDescription specimenDescription
= HibernateProxyHelper
.deproxy(descriptionBase
, SpecimenDescription
.class);
580 Set
<DescriptionElementBase
> elements
= specimenDescription
.getElements();
581 for (DescriptionElementBase descriptionElementBase
: elements
) {
582 if(descriptionElementBase
.isCharacterData()){
583 states
.add(descriptionElementBase
);
592 public boolean updateCaches(){
593 boolean result
= super.updateCaches();
594 // if (this.protectedIdentityCache == false){
595 // String oldIdentityCache = this.identityCache;
597 // String newIdentityCache = cacheStrategy.getIdentityCache(this);
599 // if ( oldIdentityCache == null || ! oldIdentityCache.equals(newIdentityCache) ){
600 // this.setIdentityCache(null, false);
601 // String newCache = this.getIdentityCache();
603 // if (newCache == null){
604 // logger.warn("New identityCache should never be null");
606 // if (oldIdentityCache == null){
607 // logger.info("Old abbrevTitleCache should never be null");
615 //******************** CLONE **********************************************/
618 public Object
clone() throws CloneNotSupportedException
{
619 SpecimenOrObservationBase
<S
> result
= (SpecimenOrObservationBase
<S
>)super.clone();
621 //defininion (description, languageString)
622 result
.definition
= cloneLanguageString(this.definition
);
625 result
.setSex(this.sex
);
627 result
.setLifeStage(this.lifeStage
);
629 result
.descriptions
= new HashSet
<>();
631 for(DescriptionBase
<S
> description
: this.descriptions
) {
632 result
.addDescription((SpecimenDescription
)description
.clone());
635 result
.determinations
= new HashSet
<>();
636 for(DeterminationEvent determination
: this.determinations
) {
637 result
.addDetermination(determination
.clone());
640 result
.derivationEvents
= new HashSet
<>();
642 for(DerivationEvent derivationEvent
: this.derivationEvents
) {
643 //TODO should we clone this?
644 result
.addDerivationEvent(derivationEvent
);
647 //no changes to: individualCount