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
.taxon
.TaxonBase
;
53 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
54 import eu
.etaxonomy
.cdm
.strategy
.match
.Match
;
55 import eu
.etaxonomy
.cdm
.strategy
.match
.MatchMode
;
56 import eu
.etaxonomy
.cdm
.strategy
.match
.Match
.ReplaceMode
;
57 import eu
.etaxonomy
.cdm
.strategy
.merge
.Merge
;
58 import eu
.etaxonomy
.cdm
.strategy
.merge
.MergeMode
;
59 import eu
.etaxonomy
.cdm
.validation
.Level2
;
62 * 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.
63 * All subclasses inherit the ability to store additional properties that are stored as {@link Extension Extensions}, basically a string value with a type term.
64 * Any number of right statements can be attached as well as multiple {@link OriginalSourceBase} objects.
65 * 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).
66 * A Taxon for example that was taken from 2 sources like FaunaEuropaea and IPNI would have two originalSource objects.
67 * 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.
71 * @created 08-Nov-2007 13:06:27
73 @XmlAccessorType(XmlAccessType
.FIELD
)
74 @XmlType(name
= "IdentifiableEntity", propOrder
= {
77 "protectedTitleCache",
84 public abstract class IdentifiableEntity
<S
extends IIdentifiableEntityCacheStrategy
> extends AnnotatableEntity
85 implements IIdentifiableEntity
/*, ISourceable<IdentifiableSource> */ {
86 private static final long serialVersionUID
= -5610995424730659058L;
87 private static final Logger logger
= Logger
.getLogger(IdentifiableEntity
.class);
90 public static final boolean PROTECTED
= true;
92 public static final boolean NOT_PROTECTED
= false;
94 @XmlElement(name
= "LSID", type
= String
.class)
95 @XmlJavaTypeAdapter(LSIDAdapter
.class)
99 @XmlElement(name
= "TitleCache", required
= true)
100 @XmlJavaTypeAdapter(FormattedTextAdapter
.class)
101 @Column(length
=255, name
="titleCache")
102 @Fields({@Field(index
= org
.hibernate
.search
.annotations
.Index
.TOKENIZED
),
103 @Field(name
= "titleCache_forSort", index
= org
.hibernate
.search
.annotations
.Index
.UN_TOKENIZED
)
105 @FieldBridge(impl
=StripHtmlBridge
.class)
106 @Match(value
=MatchMode
.CACHE
, cacheReplaceMode
=ReplaceMode
.ALL
)
107 @NotEmpty(groups
= Level2
.class) // implictly NotNull
109 protected String titleCache
;
111 //if true titleCache will not be automatically generated/updated
112 @XmlElement(name
= "ProtectedTitleCache")
113 protected boolean protectedTitleCache
;
115 @XmlElementWrapper(name
= "Rights")
116 @XmlElement(name
= "Rights")
117 @OneToMany(fetch
= FetchType
.LAZY
)
118 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
, CascadeType
.DELETE_ORPHAN
})
120 @Merge(MergeMode
.ADD_CLONE
)
122 private Set
<Rights
> rights
= new HashSet
<Rights
>();
124 @XmlElementWrapper(name
= "Credits")
125 @XmlElement(name
= "Credit")
126 @IndexColumn(name
="sortIndex", base
= 0)
127 @OneToMany(fetch
= FetchType
.LAZY
)
128 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
, CascadeType
.DELETE_ORPHAN
})
130 @Merge(MergeMode
.ADD_CLONE
)
132 private List
<Credit
> credits
= new ArrayList
<Credit
>();
134 @XmlElementWrapper(name
= "Extensions")
135 @XmlElement(name
= "Extension")
136 @OneToMany(fetch
= FetchType
.LAZY
)
137 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
, CascadeType
.DELETE_ORPHAN
})
138 @Merge(MergeMode
.ADD_CLONE
)
140 private Set
<Extension
> extensions
= new HashSet
<Extension
>();
142 @XmlElementWrapper(name
= "Sources")
143 @XmlElement(name
= "IdentifiableSource")
144 @OneToMany(fetch
= FetchType
.LAZY
)
145 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
, CascadeType
.DELETE_ORPHAN
})
146 @Merge(MergeMode
.ADD_CLONE
)
148 private Set
<IdentifiableSource
> sources
= new HashSet
<IdentifiableSource
>();
152 protected S cacheStrategy
;
154 protected IdentifiableEntity(){
158 protected void initListener(){
159 PropertyChangeListener listener
= new PropertyChangeListener() {
160 public void propertyChange(PropertyChangeEvent e
) {
161 if (!e
.getPropertyName().equals("titleCache") && ! isProtectedTitleCache()){
166 addPropertyChangeListener(listener
);
170 * By default, we expect most cdm objects to be abstract things
171 * i.e. unable to return a data representation.
173 * Specific subclasses (e.g. Sequence) can override if necessary.
175 public byte[] getData() {
179 //******************************** CACHE *****************************************************/
183 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getTitleCache()
185 // @Transient - must not be transient, since this property needs to to be included in all serializations produced by the remote layer
186 public String
getTitleCache(){
187 if (protectedTitleCache
){
188 return this.titleCache
;
190 // is title dirty, i.e. equal NULL?
191 if (titleCache
== null){
192 this.titleCache
= generateTitle();
193 this.titleCache
= getTruncatedCache(this.titleCache
) ;
198 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setTitleCache(java.lang.String)
200 public void setTitleCache(String titleCache
){
201 setTitleCache(titleCache
, PROTECTED
);
205 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setTitleCache(java.lang.String, boolean)
207 public void setTitleCache(String titleCache
, boolean protectCache
){
208 titleCache
= getTruncatedCache(titleCache
);
209 this.titleCache
= titleCache
;
210 this.protectedTitleCache
= protectCache
;
219 protected String
getTruncatedCache(String cache
) {
220 if (cache
!= null && cache
.length() > 252){
221 logger
.warn("Truncation of cache: " + this.toString() + "/" + cache
);
222 cache
= cache
.substring(0, 252) + "...";
227 //**************************************************************************************
230 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getLsid()
232 public LSID
getLsid(){
236 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setLsid(java.lang.String)
238 public void setLsid(LSID lsid
){
242 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getRights()
244 public Set
<Rights
> getRights() {
246 this.rights
= new HashSet
<Rights
>();
252 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addRights(eu.etaxonomy.cdm.model.media.Rights)
254 public void addRights(Rights right
){
255 this.rights
.add(right
);
258 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeRights(eu.etaxonomy.cdm.model.media.Rights)
260 public void removeRights(Rights right
){
261 this.rights
.remove(right
);
265 public List
<Credit
> getCredits() {
266 if(credits
== null) {
267 this.credits
= new ArrayList
<Credit
>();
273 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getCredits(int)
275 public Credit
getCredits(int index
){
276 return this.credits
.get(index
);
280 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addCredit(eu.etaxonomy.cdm.model.common.Credit)
282 public void addCredit(Credit credit
){
283 this.credits
.add(credit
);
288 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addCredit(eu.etaxonomy.cdm.model.common.Credit, int)
290 public void addCredit(Credit credit
, int index
){
291 this.credits
.add(index
, credit
);
295 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeCredit(eu.etaxonomy.cdm.model.common.Credit)
297 public void removeCredit(Credit credit
){
298 this.credits
.remove(credit
);
302 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeCredit(int)
304 public void removeCredit(int index
){
305 this.credits
.remove(index
);
310 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getExtensions()
312 public Set
<Extension
> getExtensions(){
313 if(extensions
== null) {
314 this.extensions
= new HashSet
<Extension
>();
316 return this.extensions
;
319 public void addExtension(String value
, ExtensionType extensionType
){
320 Extension
.NewInstance(this, value
, extensionType
);
324 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addExtension(eu.etaxonomy.cdm.model.common.Extension)
326 public void addExtension(Extension extension
){
327 if (extension
!= null){
328 extension
.setExtendedObj(this);
329 this.extensions
.add(extension
);
333 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeExtension(eu.etaxonomy.cdm.model.common.Extension)
335 public void removeExtension(Extension extension
){
336 if (extension
!= null){
337 extension
.setExtendedObj(null);
338 this.extensions
.remove(extension
);
344 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#isProtectedTitleCache()
346 public boolean isProtectedTitleCache() {
347 return protectedTitleCache
;
351 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setProtectedTitleCache(boolean)
353 public void setProtectedTitleCache(boolean protectedTitleCache
) {
354 this.protectedTitleCache
= protectedTitleCache
;
358 * @see eu.etaxonomy.cdm.model.common.ISourceable#getSources()
360 public Set
<IdentifiableSource
> getSources() {
361 if(sources
== null) {
362 this.sources
= new HashSet
<IdentifiableSource
>();
368 * @see eu.etaxonomy.cdm.model.common.ISourceable#addSource(eu.etaxonomy.cdm.model.common.OriginalSourceBase)
370 public void addSource(IdentifiableSource source
) {
372 IdentifiableEntity oldSourcedObj
= source
.getSourcedObj();
373 if (oldSourcedObj
!= null && oldSourcedObj
!= this){
374 oldSourcedObj
.getSources().remove(source
);
376 this.sources
.add(source
);
377 source
.setSourcedObj(this);
382 * @see eu.etaxonomy.cdm.model.common.ISourceable#removeSource(eu.etaxonomy.cdm.model.common.IOriginalSource)
384 public void removeSource(IdentifiableSource source
) {
385 this.sources
.remove(source
);
388 //******************************** TO STRING *****************************************************/
391 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#toString()
394 public String
toString() {
396 if (titleCache
== null){
397 result
= super.toString();
399 result
= this.titleCache
;
405 public int compareTo(IdentifiableEntity identifiableEntity
) {
409 if (identifiableEntity
== null) {
410 throw new NullPointerException("Cannot compare to null.");
413 // First, compare the name cache.
414 // TODO: Avoid using instanceof operator
415 // Use Class.getDeclaredMethod() instead to find out whether class has getNameCache() method?
417 String specifiedNameCache
= "";
418 String thisNameCache
= "";
419 String specifiedTitleCache
= "";
420 String thisTitleCache
= "";
421 String specifiedReferenceTitleCache
= "";
422 String thisReferenceTitleCache
= "";
424 if(identifiableEntity
instanceof NonViralName
) {
425 specifiedNameCache
= HibernateProxyHelper
.deproxy(identifiableEntity
, NonViralName
.class).getNameCache();
426 specifiedTitleCache
= identifiableEntity
.getTitleCache();
428 } else if(identifiableEntity
instanceof TaxonBase
) {
429 TaxonBase taxonBase
= HibernateProxyHelper
.deproxy(identifiableEntity
, TaxonBase
.class);
431 TaxonNameBase
<?
,?
> taxonNameBase
= taxonBase
.getName();
432 specifiedNameCache
= HibernateProxyHelper
.deproxy(taxonNameBase
, NonViralName
.class).getNameCache();
433 specifiedTitleCache
= taxonNameBase
.getTitleCache();
435 //specifiedReferenceTitleCache = ((TaxonBase)identifiableEntity).getSec().getTitleCache();
436 // ReferenceBase referenceBase = taxonBase.getSec();
437 // if (referenceBase != null) {
438 // FIXME: HibernateProxyHelper.deproxy(referenceBase, ReferenceBase.class) throws exception
439 // referenceBase = HibernateProxyHelper.deproxy(referenceBase, ReferenceBase.class);
440 // specifiedReferenceTitleCache = referenceBase.getTitleCache();
444 if(this instanceof NonViralName
) {
445 thisNameCache
= HibernateProxyHelper
.deproxy(this, NonViralName
.class).getNameCache();
446 thisTitleCache
= getTitleCache();
447 } else if(this instanceof TaxonBase
) {
448 TaxonNameBase
<?
,?
> taxonNameBase
= HibernateProxyHelper
.deproxy(this, TaxonBase
.class).getName();
449 thisNameCache
= HibernateProxyHelper
.deproxy(taxonNameBase
, NonViralName
.class).getNameCache();
450 thisTitleCache
= taxonNameBase
.getTitleCache();
451 thisReferenceTitleCache
= getTitleCache();
454 // Compare name cache of taxon names
456 if (!specifiedNameCache
.equals("") && !thisNameCache
.equals("")) {
457 result
= thisNameCache
.compareTo(specifiedNameCache
);
460 // Compare title cache of taxon names
462 if ((result
== 0) && (!specifiedTitleCache
.equals("") || !thisTitleCache
.equals(""))) {
463 result
= thisTitleCache
.compareTo(specifiedTitleCache
);
466 // Compare title cache of taxon references
468 if ((result
== 0) && (!specifiedReferenceTitleCache
.equals("") || !thisReferenceTitleCache
.equals(""))) {
469 result
= thisReferenceTitleCache
.compareTo(specifiedReferenceTitleCache
);
476 * Returns the {@link eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy cache strategy} used to generate
477 * several strings corresponding to <i>this</i> identifiable entity
478 * (in particular taxon name caches and author strings).
480 * @return the cache strategy used for <i>this</i> identifiable entity
481 * @see eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
483 public S
getCacheStrategy() {
484 return this.cacheStrategy
;
487 * @see #getCacheStrategy()
490 public void setCacheStrategy(S cacheStrategy
) {
491 this.cacheStrategy
= cacheStrategy
;
494 public String
generateTitle() {
495 if (cacheStrategy
== null){
496 //logger.warn("No CacheStrategy defined for "+ this.getClass() + ": " + this.getUuid());
497 return this.getClass() + ": " + this.getUuid();
499 return cacheStrategy
.getTitleCache(this);
503 //****************** CLONE ************************************************/
506 * @see eu.etaxonomy.cdm.model.common.AnnotatableEntity#clone()
509 public Object
clone() throws CloneNotSupportedException
{
510 IdentifiableEntity result
= (IdentifiableEntity
)super.clone();
513 result
.extensions
= new HashSet
<Extension
>();
514 for (Extension extension
: this.extensions
){
515 Extension newExtension
= (Extension
)extension
.clone();
516 result
.addExtension(newExtension
);
520 result
.sources
= new HashSet
<IdentifiableSource
>();
521 for (IdentifiableSource source
: this.sources
){
522 IdentifiableSource newSource
= (IdentifiableSource
)source
.clone();
523 result
.addSource(newSource
);
527 result
.rights
= new HashSet
<Rights
>();
528 for(Rights rights
: this.rights
) {
529 result
.addRights(rights
);
534 result
.credits
= new ArrayList
<Credit
>();
535 for(Credit credit
: this.credits
) {
536 result
.addCredit(credit
);
540 //result.setLsid(lsid);
541 //result.setTitleCache(titleCache);
542 //result.setProtectedTitleCache(protectedTitleCache); //must be after setTitleCache
544 //no changes to: lsid, titleCache, protectedTitleCache
547 if (! protectedTitleCache
){
548 result
.titleCache
= null;