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
.common
;
13 import java
.beans
.PropertyChangeEvent
;
14 import java
.beans
.PropertyChangeListener
;
15 import java
.util
.ArrayList
;
16 import java
.util
.HashSet
;
17 import java
.util
.List
;
20 import javax
.persistence
.Column
;
21 import javax
.persistence
.Embedded
;
22 import javax
.persistence
.FetchType
;
23 import javax
.persistence
.MappedSuperclass
;
24 import javax
.persistence
.OneToMany
;
25 import javax
.persistence
.Transient
;
26 import javax
.validation
.constraints
.NotNull
;
27 import javax
.validation
.constraints
.Size
;
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
.XmlTransient
;
33 import javax
.xml
.bind
.annotation
.XmlType
;
34 import javax
.xml
.bind
.annotation
.adapters
.XmlJavaTypeAdapter
;
36 import org
.apache
.log4j
.Logger
;
37 import org
.hibernate
.annotations
.Cascade
;
38 import org
.hibernate
.annotations
.CascadeType
;
39 import org
.hibernate
.annotations
.IndexColumn
;
40 import org
.hibernate
.search
.annotations
.Field
;
41 import org
.hibernate
.search
.annotations
.FieldBridge
;
42 import org
.hibernate
.search
.annotations
.Fields
;
43 import org
.hibernate
.validator
.constraints
.NotEmpty
;
45 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
46 import eu
.etaxonomy
.cdm
.hibernate
.StripHtmlBridge
;
47 import eu
.etaxonomy
.cdm
.jaxb
.FormattedTextAdapter
;
48 import eu
.etaxonomy
.cdm
.jaxb
.LSIDAdapter
;
49 import eu
.etaxonomy
.cdm
.model
.media
.Rights
;
50 import eu
.etaxonomy
.cdm
.model
.name
.NonViralName
;
51 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameBase
;
52 import eu
.etaxonomy
.cdm
.model
.reference
.ReferenceBase
;
53 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
54 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
55 import eu
.etaxonomy
.cdm
.strategy
.match
.Match
;
56 import eu
.etaxonomy
.cdm
.strategy
.match
.MatchMode
;
57 import eu
.etaxonomy
.cdm
.strategy
.match
.Match
.ReplaceMode
;
58 import eu
.etaxonomy
.cdm
.strategy
.merge
.Merge
;
59 import eu
.etaxonomy
.cdm
.strategy
.merge
.MergeMode
;
60 import eu
.etaxonomy
.cdm
.validation
.Level2
;
63 * Superclass for the primary CDM classes that can be referenced from outside via LSIDs and contain a simple generated title string as a label for human reading.
64 * All subclasses inherit the ability to store additional properties that are stored as {@link Extension Extensions}, basically a string value with a type term.
65 * Any number of right statements can be attached as well as multiple {@link OriginalSourceBase} objects.
66 * Original sources carry a reference to the source, an ID within that source and the original title/label of this object as it was used in that source (originalNameString).
67 * A Taxon for example that was taken from 2 sources like FaunaEuropaea and IPNI would have two originalSource objects.
68 * The originalSource representing that taxon as it was found in IPNI would contain IPNI as the reference, the IPNI id of the taxon and the name of the taxon exactly as it was used in IPNI.
72 * @created 08-Nov-2007 13:06:27
74 @XmlAccessorType(XmlAccessType
.FIELD
)
75 @XmlType(name
= "IdentifiableEntity", propOrder
= {
78 "protectedTitleCache",
85 public abstract class IdentifiableEntity
<S
extends IIdentifiableEntityCacheStrategy
> extends AnnotatableEntity
86 implements IIdentifiableEntity
/*, ISourceable<IdentifiableSource> */ {
87 private static final long serialVersionUID
= -5610995424730659058L;
88 private static final Logger logger
= Logger
.getLogger(IdentifiableEntity
.class);
91 public static final boolean PROTECTED
= true;
93 public static final boolean NOT_PROTECTED
= false;
95 @XmlElement(name
= "LSID", type
= String
.class)
96 @XmlJavaTypeAdapter(LSIDAdapter
.class)
100 @XmlElement(name
= "TitleCache", required
= true)
101 @XmlJavaTypeAdapter(FormattedTextAdapter
.class)
102 @Column(length
=255, name
="titleCache")
104 @FieldBridge(impl
=StripHtmlBridge
.class)
105 @Match(value
=MatchMode
.CACHE
, cacheReplaceMode
=ReplaceMode
.ALL
)
106 @NotEmpty(groups
= Level2
.class) // implictly NotNull
108 protected String titleCache
;
110 //if true titleCache will not be automatically generated/updated
111 @XmlElement(name
= "ProtectedTitleCache")
112 protected boolean protectedTitleCache
;
114 @XmlElementWrapper(name
= "Rights", nillable
= true)
115 @XmlElement(name
= "Rights")
116 @OneToMany(fetch
= FetchType
.LAZY
)
117 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
, CascadeType
.DELETE_ORPHAN
})
119 @Merge(MergeMode
.ADD_CLONE
)
121 private Set
<Rights
> rights
= new HashSet
<Rights
>();
123 @XmlElementWrapper(name
= "Credits", nillable
= true)
124 @XmlElement(name
= "Credit")
125 @IndexColumn(name
="sortIndex", base
= 0)
126 @OneToMany(fetch
= FetchType
.LAZY
)
127 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
, CascadeType
.DELETE_ORPHAN
})
129 @Merge(MergeMode
.ADD_CLONE
)
131 private List
<Credit
> credits
= new ArrayList
<Credit
>();
133 @XmlElementWrapper(name
= "Extensions", nillable
= true)
134 @XmlElement(name
= "Extension")
135 @OneToMany(fetch
= FetchType
.LAZY
)
136 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
, CascadeType
.DELETE_ORPHAN
})
137 @Merge(MergeMode
.ADD_CLONE
)
139 private Set
<Extension
> extensions
= new HashSet
<Extension
>();
141 @XmlElementWrapper(name
= "Sources", nillable
= true)
142 @XmlElement(name
= "IdentifiableSource")
143 @OneToMany(fetch
= FetchType
.LAZY
)
144 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
, CascadeType
.DELETE_ORPHAN
})
145 @Merge(MergeMode
.ADD_CLONE
)
147 private Set
<IdentifiableSource
> sources
= new HashSet
<IdentifiableSource
>();
151 protected S cacheStrategy
;
153 protected IdentifiableEntity(){
157 protected void initListener(){
158 PropertyChangeListener listener
= new PropertyChangeListener() {
159 public void propertyChange(PropertyChangeEvent e
) {
160 if (!e
.getPropertyName().equals("titleCache") && ! isProtectedTitleCache()){
165 addPropertyChangeListener(listener
);
169 * By default, we expect most cdm objects to be abstract things
170 * i.e. unable to return a data representation.
172 * Specific subclasses (e.g. Sequence) can override if necessary.
174 public byte[] getData() {
178 //******************************** CACHE *****************************************************/
182 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getTitleCache()
184 // @Transient - must not be transient, since this property needs to to be included in all serializations produced by the remote layer
185 public String
getTitleCache(){
186 if (protectedTitleCache
){
187 return this.titleCache
;
189 // is title dirty, i.e. equal NULL?
190 if (titleCache
== null){
191 this.titleCache
= generateTitle();
192 this.titleCache
= getTruncatedCache(this.titleCache
) ;
197 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setTitleCache(java.lang.String)
199 public void setTitleCache(String titleCache
){
200 this.titleCache
= titleCache
;
204 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setTitleCache(java.lang.String, boolean)
206 public void setTitleCache(String titleCache
, boolean protectCache
){
207 titleCache
= getTruncatedCache(titleCache
);
208 this.titleCache
= titleCache
;
209 this.protectedTitleCache
= protectCache
;
218 protected String
getTruncatedCache(String cache
) {
219 if (cache
!= null && cache
.length() > 255){
220 logger
.warn("Truncation of cache: " + this.toString() + "/" + cache
);
221 cache
= cache
.substring(0, 252) + "...";
226 //**************************************************************************************
229 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getLsid()
231 public LSID
getLsid(){
235 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setLsid(java.lang.String)
237 public void setLsid(LSID lsid
){
241 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getRights()
243 public Set
<Rights
> getRights() {
245 this.rights
= new HashSet
<Rights
>();
251 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addRights(eu.etaxonomy.cdm.model.media.Rights)
253 public void addRights(Rights right
){
254 getRights().add(right
);
257 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeRights(eu.etaxonomy.cdm.model.media.Rights)
259 public void removeRights(Rights right
){
260 getRights().remove(right
);
264 public List
<Credit
> getCredits() {
265 if(credits
== null) {
266 this.credits
= new ArrayList
<Credit
>();
272 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getCredits(int)
274 public Credit
getCredits(int index
){
275 return getCredits().get(index
);
279 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addCredit(eu.etaxonomy.cdm.model.common.Credit)
281 public void addCredit(Credit credit
){
282 getCredits().add(credit
);
287 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addCredit(eu.etaxonomy.cdm.model.common.Credit, int)
289 public void addCredit(Credit credit
, int index
){
290 getCredits().add(index
, credit
);
294 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeCredit(eu.etaxonomy.cdm.model.common.Credit)
296 public void removeCredit(Credit credit
){
297 getCredits().remove(credit
);
301 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeCredit(int)
303 public void removeCredit(int index
){
304 getCredits().remove(index
);
309 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getExtensions()
311 public Set
<Extension
> getExtensions(){
312 if(extensions
== null) {
313 this.extensions
= new HashSet
<Extension
>();
315 return this.extensions
;
318 public void addExtension(String value
, ExtensionType extensionType
){
319 Extension
.NewInstance(this, value
, extensionType
);
323 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addExtension(eu.etaxonomy.cdm.model.common.Extension)
325 public void addExtension(Extension extension
){
326 if (extension
!= null){
327 extension
.setExtendedObj(this);
328 getExtensions().add(extension
);
332 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeExtension(eu.etaxonomy.cdm.model.common.Extension)
334 public void removeExtension(Extension extension
){
335 if (extension
!= null){
336 extension
.setExtendedObj(null);
337 getExtensions().remove(extension
);
343 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#isProtectedTitleCache()
345 public boolean isProtectedTitleCache() {
346 return protectedTitleCache
;
350 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setProtectedTitleCache(boolean)
352 public void setProtectedTitleCache(boolean protectedTitleCache
) {
353 this.protectedTitleCache
= protectedTitleCache
;
357 * @see eu.etaxonomy.cdm.model.common.ISourceable#getSources()
359 public Set
<IdentifiableSource
> getSources() {
360 if(sources
== null) {
361 this.sources
= new HashSet
<IdentifiableSource
>();
367 * @see eu.etaxonomy.cdm.model.common.ISourceable#addSource(eu.etaxonomy.cdm.model.common.OriginalSourceBase)
369 public void addSource(IdentifiableSource source
) {
371 IdentifiableEntity oldSourcedObj
= source
.getSourcedObj();
372 if (oldSourcedObj
!= null && oldSourcedObj
!= this){
373 oldSourcedObj
.getSources().remove(source
);
375 getSources().add(source
);
376 source
.setSourcedObj(this);
381 * @see eu.etaxonomy.cdm.model.common.ISourceable#addSource(java.lang.String, java.lang.String, eu.etaxonomy.cdm.model.reference.ReferenceBase, java.lang.String)
383 public IdentifiableSource
addSource(String id
, String idNamespace
, ReferenceBase citation
, String microCitation
) {
384 if (id
== null && idNamespace
== null && citation
== null && microCitation
== null){
387 IdentifiableSource source
= IdentifiableSource
.NewInstance(id
, idNamespace
, citation
, microCitation
);
393 * @see eu.etaxonomy.cdm.model.common.ISourceable#removeSource(eu.etaxonomy.cdm.model.common.IOriginalSource)
395 public void removeSource(IdentifiableSource source
) {
396 getSources().remove(source
);
399 //******************************** TO STRING *****************************************************/
402 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#toString()
405 public String
toString() {
407 if (titleCache
== null){
408 result
= super.toString();
410 result
= this.titleCache
;
416 public int compareTo(IdentifiableEntity identifiableEntity
) {
420 if (identifiableEntity
== null) {
421 throw new NullPointerException("Cannot compare to null.");
424 // First, compare the name cache.
425 // TODO: Avoid using instanceof operator
426 // Use Class.getDeclaredMethod() instead to find out whether class has getNameCache() method?
428 String specifiedNameCache
= "";
429 String thisNameCache
= "";
430 String specifiedTitleCache
= "";
431 String thisTitleCache
= "";
432 String specifiedReferenceTitleCache
= "";
433 String thisReferenceTitleCache
= "";
434 String thisGenusString
= "";
435 String specifiedGenusString
= "";
436 int thisrank_order
= 0;
438 if(identifiableEntity
instanceof NonViralName
) {
439 specifiedNameCache
= HibernateProxyHelper
.deproxy(identifiableEntity
, NonViralName
.class).getNameCache();
440 specifiedTitleCache
= identifiableEntity
.getTitleCache();
442 } else if(identifiableEntity
instanceof TaxonBase
) {
443 TaxonBase taxonBase
= HibernateProxyHelper
.deproxy(identifiableEntity
, TaxonBase
.class);
445 TaxonNameBase
<?
,?
> taxonNameBase
= taxonBase
.getName();
448 NonViralName nonViralName
= HibernateProxyHelper
.deproxy(taxonNameBase
, NonViralName
.class);
449 specifiedNameCache
= nonViralName
.getNameCache();
450 specifiedTitleCache
= taxonNameBase
.getTitleCache();
452 specifiedReferenceTitleCache
= ((TaxonBase
)identifiableEntity
).getSec().getTitleCache();
453 ReferenceBase referenceBase
= taxonBase
.getSec();
454 if (referenceBase
!= null) {
455 referenceBase
= HibernateProxyHelper
.deproxy(referenceBase
, ReferenceBase
.class);
456 specifiedReferenceTitleCache
= referenceBase
.getTitleCache();
460 if(this instanceof NonViralName
) {
461 thisNameCache
= HibernateProxyHelper
.deproxy(this, NonViralName
.class).getNameCache();
462 thisTitleCache
= getTitleCache();
463 } else if(this instanceof TaxonBase
) {
464 TaxonNameBase
<?
,?
> taxonNameBase
= HibernateProxyHelper
.deproxy(this, TaxonBase
.class).getName();
465 NonViralName nonViralName
= HibernateProxyHelper
.deproxy(taxonNameBase
, NonViralName
.class);
466 thisNameCache
= nonViralName
.getNameCache();
467 thisTitleCache
= taxonNameBase
.getTitleCache();
468 thisReferenceTitleCache
= getTitleCache();
469 thisGenusString
= nonViralName
.getGenusOrUninomial();
472 // Compare name cache of taxon names
474 if (!specifiedNameCache
.equals("") && !thisNameCache
.equals("")) {
475 result
= thisNameCache
.compareTo(specifiedNameCache
);
478 // Compare title cache of taxon names
480 if ((result
== 0) && (!specifiedTitleCache
.equals("") || !thisTitleCache
.equals(""))) {
481 result
= thisTitleCache
.compareTo(specifiedTitleCache
);
484 // Compare title cache of taxon references
486 if ((result
== 0) && (!specifiedReferenceTitleCache
.equals("") || !thisReferenceTitleCache
.equals(""))) {
487 result
= thisReferenceTitleCache
.compareTo(specifiedReferenceTitleCache
);
494 * Returns the {@link eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy cache strategy} used to generate
495 * several strings corresponding to <i>this</i> identifiable entity
496 * (in particular taxon name caches and author strings).
498 * @return the cache strategy used for <i>this</i> identifiable entity
499 * @see eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
501 public S
getCacheStrategy() {
502 return this.cacheStrategy
;
505 * @see #getCacheStrategy()
508 public void setCacheStrategy(S cacheStrategy
) {
509 this.cacheStrategy
= cacheStrategy
;
512 public String
generateTitle() {
513 if (cacheStrategy
== null){
514 //logger.warn("No CacheStrategy defined for "+ this.getClass() + ": " + this.getUuid());
515 return this.getClass() + ": " + this.getUuid();
517 return cacheStrategy
.getTitleCache(this);
521 //****************** CLONE ************************************************/
524 * @see eu.etaxonomy.cdm.model.common.AnnotatableEntity#clone()
527 public Object
clone() throws CloneNotSupportedException
{
528 IdentifiableEntity result
= (IdentifiableEntity
)super.clone();
531 result
.extensions
= new HashSet
<Extension
>();
532 for (Extension extension
: getExtensions() ){
533 Extension newExtension
= (Extension
)extension
.clone();
534 result
.addExtension(newExtension
);
538 result
.sources
= new HashSet
<IdentifiableSource
>();
539 for (IdentifiableSource source
: getSources()){
540 IdentifiableSource newSource
= (IdentifiableSource
)source
.clone();
541 result
.addSource(newSource
);
545 result
.rights
= new HashSet
<Rights
>();
546 for(Rights rights
: getRights()) {
547 result
.addRights(rights
);
552 result
.credits
= new ArrayList
<Credit
>();
553 for(Credit credit
: getCredits()) {
554 result
.addCredit(credit
);
557 //no changes to: lsid, titleCache, protectedTitleCache
560 if (! protectedTitleCache
){
561 result
.titleCache
= null;