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
.FieldBridge
;
41 import org
.hibernate
.validator
.constraints
.NotEmpty
;
43 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
44 import eu
.etaxonomy
.cdm
.hibernate
.StripHtmlBridge
;
45 import eu
.etaxonomy
.cdm
.jaxb
.FormattedTextAdapter
;
46 import eu
.etaxonomy
.cdm
.jaxb
.LSIDAdapter
;
47 import eu
.etaxonomy
.cdm
.model
.media
.Rights
;
48 import eu
.etaxonomy
.cdm
.model
.name
.NonViralName
;
49 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameBase
;
50 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
51 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
52 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
53 import eu
.etaxonomy
.cdm
.strategy
.match
.Match
;
54 import eu
.etaxonomy
.cdm
.strategy
.match
.Match
.ReplaceMode
;
55 import eu
.etaxonomy
.cdm
.strategy
.match
.MatchMode
;
56 import eu
.etaxonomy
.cdm
.strategy
.merge
.Merge
;
57 import eu
.etaxonomy
.cdm
.strategy
.merge
.MergeMode
;
58 import eu
.etaxonomy
.cdm
.validation
.Level2
;
61 * 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.
62 * All subclasses inherit the ability to store additional properties that are stored as {@link Extension Extensions}, basically a string value with a type term.
63 * Any number of right statements can be attached as well as multiple {@link OriginalSourceBase} objects.
64 * 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).
65 * A Taxon for example that was taken from 2 sources like FaunaEuropaea and IPNI would have two originalSource objects.
66 * 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.
70 * @created 08-Nov-2007 13:06:27
72 @XmlAccessorType(XmlAccessType
.FIELD
)
73 @XmlType(name
= "IdentifiableEntity", propOrder
= {
76 "protectedTitleCache",
83 public abstract class IdentifiableEntity
<S
extends IIdentifiableEntityCacheStrategy
> extends AnnotatableEntity
84 implements IIdentifiableEntity
/*, ISourceable<IdentifiableSource> */ {
85 private static final long serialVersionUID
= -5610995424730659058L;
86 private static final Logger logger
= Logger
.getLogger(IdentifiableEntity
.class);
89 public static final boolean PROTECTED
= true;
91 public static final boolean NOT_PROTECTED
= false;
93 @XmlElement(name
= "LSID", type
= String
.class)
94 @XmlJavaTypeAdapter(LSIDAdapter
.class)
98 @XmlElement(name
= "TitleCache", required
= true)
99 @XmlJavaTypeAdapter(FormattedTextAdapter
.class)
100 @Column(length
=255, name
="titleCache")
102 @FieldBridge(impl
=StripHtmlBridge
.class)
103 @Match(value
=MatchMode
.CACHE
, cacheReplaceMode
=ReplaceMode
.ALL
)
104 @NotEmpty(groups
= Level2
.class) // implictly NotNull
106 protected String titleCache
;
108 //if true titleCache will not be automatically generated/updated
109 @XmlElement(name
= "ProtectedTitleCache")
110 protected boolean protectedTitleCache
;
112 @XmlElementWrapper(name
= "Rights", nillable
= true)
113 @XmlElement(name
= "Rights")
114 @OneToMany(fetch
= FetchType
.LAZY
)
115 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
, CascadeType
.DELETE_ORPHAN
})
117 @Merge(MergeMode
.ADD_CLONE
)
119 private Set
<Rights
> rights
= new HashSet
<Rights
>();
121 @XmlElementWrapper(name
= "Credits", nillable
= true)
122 @XmlElement(name
= "Credit")
123 @IndexColumn(name
="sortIndex", base
= 0)
124 @OneToMany(fetch
= FetchType
.LAZY
)
125 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
, CascadeType
.DELETE_ORPHAN
})
127 @Merge(MergeMode
.ADD_CLONE
)
129 private List
<Credit
> credits
= new ArrayList
<Credit
>();
131 @XmlElementWrapper(name
= "Extensions", nillable
= true)
132 @XmlElement(name
= "Extension")
133 @OneToMany(fetch
= FetchType
.LAZY
)
134 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
, CascadeType
.DELETE_ORPHAN
})
135 @Merge(MergeMode
.ADD_CLONE
)
137 private Set
<Extension
> extensions
= new HashSet
<Extension
>();
139 @XmlElementWrapper(name
= "Sources", nillable
= true)
140 @XmlElement(name
= "IdentifiableSource")
141 @OneToMany(fetch
= FetchType
.LAZY
)
142 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
, CascadeType
.DELETE_ORPHAN
})
143 @Merge(MergeMode
.ADD_CLONE
)
145 private Set
<IdentifiableSource
> sources
= new HashSet
<IdentifiableSource
>();
149 protected S cacheStrategy
;
151 protected IdentifiableEntity(){
155 protected void initListener(){
156 PropertyChangeListener listener
= new PropertyChangeListener() {
157 public void propertyChange(PropertyChangeEvent e
) {
158 if (!e
.getPropertyName().equals("titleCache") && ! isProtectedTitleCache()){
163 addPropertyChangeListener(listener
);
167 * By default, we expect most cdm objects to be abstract things
168 * i.e. unable to return a data representation.
170 * Specific subclasses (e.g. Sequence) can override if necessary.
172 public byte[] getData() {
176 //******************************** CACHE *****************************************************/
180 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getTitleCache()
182 // @Transient - must not be transient, since this property needs to to be included in all serializations produced by the remote layer
183 public String
getTitleCache(){
184 if (protectedTitleCache
){
185 return this.titleCache
;
187 // is title dirty, i.e. equal NULL?
188 if (titleCache
== null){
189 this.titleCache
= generateTitle();
190 this.titleCache
= getTruncatedCache(this.titleCache
) ;
195 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setTitleCache(java.lang.String)
197 public void setTitleCache(String titleCache
){
198 this.titleCache
= titleCache
;
202 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setTitleCache(java.lang.String, boolean)
204 public void setTitleCache(String titleCache
, boolean protectCache
){
205 titleCache
= getTruncatedCache(titleCache
);
206 this.titleCache
= titleCache
;
207 this.protectedTitleCache
= protectCache
;
216 protected String
getTruncatedCache(String cache
) {
217 if (cache
!= null && cache
.length() > 255){
218 logger
.warn("Truncation of cache: " + this.toString() + "/" + cache
);
219 cache
= cache
.substring(0, 252) + "...";
224 //**************************************************************************************
227 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getLsid()
229 public LSID
getLsid(){
233 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setLsid(java.lang.String)
235 public void setLsid(LSID lsid
){
239 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getRights()
241 public Set
<Rights
> getRights() {
243 this.rights
= new HashSet
<Rights
>();
249 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addRights(eu.etaxonomy.cdm.model.media.Rights)
251 public void addRights(Rights right
){
252 getRights().add(right
);
255 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeRights(eu.etaxonomy.cdm.model.media.Rights)
257 public void removeRights(Rights right
){
258 getRights().remove(right
);
262 public List
<Credit
> getCredits() {
263 if(credits
== null) {
264 this.credits
= new ArrayList
<Credit
>();
270 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getCredits(int)
272 public Credit
getCredits(Integer index
){
273 return getCredits().get(index
);
277 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addCredit(eu.etaxonomy.cdm.model.common.Credit)
279 public void addCredit(Credit credit
){
280 getCredits().add(credit
);
285 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addCredit(eu.etaxonomy.cdm.model.common.Credit, int)
287 public void addCredit(Credit credit
, int index
){
288 getCredits().add(index
, credit
);
292 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeCredit(eu.etaxonomy.cdm.model.common.Credit)
294 public void removeCredit(Credit credit
){
295 getCredits().remove(credit
);
299 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeCredit(int)
301 public void removeCredit(int index
){
302 getCredits().remove(index
);
307 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getExtensions()
309 public Set
<Extension
> getExtensions(){
310 if(extensions
== null) {
311 this.extensions
= new HashSet
<Extension
>();
313 return this.extensions
;
316 public void addExtension(String value
, ExtensionType extensionType
){
317 Extension
.NewInstance(this, value
, extensionType
);
321 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addExtension(eu.etaxonomy.cdm.model.common.Extension)
323 public void addExtension(Extension extension
){
324 if (extension
!= null){
325 extension
.setExtendedObj(this);
326 getExtensions().add(extension
);
330 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeExtension(eu.etaxonomy.cdm.model.common.Extension)
332 public void removeExtension(Extension extension
){
333 if (extension
!= null){
334 extension
.setExtendedObj(null);
335 getExtensions().remove(extension
);
341 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#isProtectedTitleCache()
343 public boolean isProtectedTitleCache() {
344 return protectedTitleCache
;
348 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setProtectedTitleCache(boolean)
350 public void setProtectedTitleCache(boolean protectedTitleCache
) {
351 this.protectedTitleCache
= protectedTitleCache
;
355 * @see eu.etaxonomy.cdm.model.common.ISourceable#getSources()
357 public Set
<IdentifiableSource
> getSources() {
358 if(sources
== null) {
359 this.sources
= new HashSet
<IdentifiableSource
>();
365 * @see eu.etaxonomy.cdm.model.common.ISourceable#addSource(eu.etaxonomy.cdm.model.common.OriginalSourceBase)
367 public void addSource(IdentifiableSource source
) {
369 IdentifiableEntity oldSourcedObj
= source
.getSourcedObj();
370 if (oldSourcedObj
!= null && oldSourcedObj
!= this){
371 oldSourcedObj
.getSources().remove(source
);
373 getSources().add(source
);
374 source
.setSourcedObj(this);
379 * @see eu.etaxonomy.cdm.model.common.ISourceable#addSource(java.lang.String, java.lang.String, eu.etaxonomy.cdm.model.reference.Reference, java.lang.String)
381 public IdentifiableSource
addSource(String id
, String idNamespace
, Reference citation
, String microCitation
) {
382 if (id
== null && idNamespace
== null && citation
== null && microCitation
== null){
385 IdentifiableSource source
= IdentifiableSource
.NewInstance(id
, idNamespace
, citation
, microCitation
);
391 * @see eu.etaxonomy.cdm.model.common.ISourceable#removeSource(eu.etaxonomy.cdm.model.common.IOriginalSource)
393 public void removeSource(IdentifiableSource source
) {
394 getSources().remove(source
);
397 //******************************** TO STRING *****************************************************/
400 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#toString()
403 public String
toString() {
405 if (titleCache
== null){
406 result
= super.toString();
408 result
= this.titleCache
;
414 public int compareTo(IdentifiableEntity identifiableEntity
) {
418 if (identifiableEntity
== null) {
419 throw new NullPointerException("Cannot compare to null.");
422 // First, compare the name cache.
423 // TODO: Avoid using instanceof operator
424 // Use Class.getDeclaredMethod() instead to find out whether class has getNameCache() method?
426 String specifiedNameCache
= "";
427 String thisNameCache
= "";
428 String specifiedTitleCache
= "";
429 String thisTitleCache
= "";
430 String specifiedReferenceTitleCache
= "";
431 String thisReferenceTitleCache
= "";
432 String thisGenusString
= "";
433 String specifiedGenusString
= "";
434 int thisrank_order
= 0;
436 if(identifiableEntity
instanceof NonViralName
) {
437 specifiedNameCache
= HibernateProxyHelper
.deproxy(identifiableEntity
, NonViralName
.class).getNameCache();
438 specifiedTitleCache
= identifiableEntity
.getTitleCache();
440 } else if(identifiableEntity
instanceof TaxonBase
) {
441 TaxonBase taxonBase
= HibernateProxyHelper
.deproxy(identifiableEntity
, TaxonBase
.class);
443 TaxonNameBase
<?
,?
> taxonNameBase
= taxonBase
.getName();
446 NonViralName nonViralName
= HibernateProxyHelper
.deproxy(taxonNameBase
, NonViralName
.class);
447 specifiedNameCache
= nonViralName
.getNameCache();
448 specifiedTitleCache
= taxonNameBase
.getTitleCache();
450 specifiedReferenceTitleCache
= ((TaxonBase
)identifiableEntity
).getSec().getTitleCache();
451 Reference reference
= taxonBase
.getSec();
452 if (reference
!= null) {
453 reference
= HibernateProxyHelper
.deproxy(reference
, Reference
.class);
454 specifiedReferenceTitleCache
= reference
.getTitleCache();
458 if(this instanceof NonViralName
) {
459 thisNameCache
= HibernateProxyHelper
.deproxy(this, NonViralName
.class).getNameCache();
460 thisTitleCache
= getTitleCache();
461 } else if(this instanceof TaxonBase
) {
462 TaxonNameBase
<?
,?
> taxonNameBase
= HibernateProxyHelper
.deproxy(this, TaxonBase
.class).getName();
463 NonViralName nonViralName
= HibernateProxyHelper
.deproxy(taxonNameBase
, NonViralName
.class);
464 thisNameCache
= nonViralName
.getNameCache();
465 thisTitleCache
= taxonNameBase
.getTitleCache();
466 thisReferenceTitleCache
= getTitleCache();
467 thisGenusString
= nonViralName
.getGenusOrUninomial();
470 // Compare name cache of taxon names
472 if (!specifiedNameCache
.equals("") && !thisNameCache
.equals("")) {
473 result
= thisNameCache
.compareTo(specifiedNameCache
);
476 // Compare title cache of taxon names
478 if ((result
== 0) && (!specifiedTitleCache
.equals("") || !thisTitleCache
.equals(""))) {
479 result
= thisTitleCache
.compareTo(specifiedTitleCache
);
482 // Compare title cache of taxon references
484 if ((result
== 0) && (!specifiedReferenceTitleCache
.equals("") || !thisReferenceTitleCache
.equals(""))) {
485 result
= thisReferenceTitleCache
.compareTo(specifiedReferenceTitleCache
);
492 * Returns the {@link eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy cache strategy} used to generate
493 * several strings corresponding to <i>this</i> identifiable entity
494 * (in particular taxon name caches and author strings).
496 * @return the cache strategy used for <i>this</i> identifiable entity
497 * @see eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
499 public S
getCacheStrategy() {
500 return this.cacheStrategy
;
503 * @see #getCacheStrategy()
506 public void setCacheStrategy(S cacheStrategy
) {
507 this.cacheStrategy
= cacheStrategy
;
510 public String
generateTitle() {
511 if (cacheStrategy
== null){
512 //logger.warn("No CacheStrategy defined for "+ this.getClass() + ": " + this.getUuid());
513 return this.getClass() + ": " + this.getUuid();
515 return cacheStrategy
.getTitleCache(this);
519 //****************** CLONE ************************************************/
522 * @see eu.etaxonomy.cdm.model.common.AnnotatableEntity#clone()
525 public Object
clone() throws CloneNotSupportedException
{
526 IdentifiableEntity result
= (IdentifiableEntity
)super.clone();
529 result
.extensions
= new HashSet
<Extension
>();
530 for (Extension extension
: getExtensions() ){
531 Extension newExtension
= (Extension
)extension
.clone();
532 result
.addExtension(newExtension
);
536 result
.sources
= new HashSet
<IdentifiableSource
>();
537 for (IdentifiableSource source
: getSources()){
538 IdentifiableSource newSource
= (IdentifiableSource
)source
.clone();
539 result
.addSource(newSource
);
543 result
.rights
= new HashSet
<Rights
>();
544 for(Rights rights
: getRights()) {
545 result
.addRights(rights
);
550 result
.credits
= new ArrayList
<Credit
>();
551 for(Credit credit
: getCredits()) {
552 result
.addCredit(credit
);
555 //no changes to: lsid, titleCache, protectedTitleCache
558 if (! protectedTitleCache
){
559 result
.titleCache
= null;
562 result
.initListener();