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
.xml
.bind
.annotation
.XmlAccessType
;
27 import javax
.xml
.bind
.annotation
.XmlAccessorType
;
28 import javax
.xml
.bind
.annotation
.XmlElement
;
29 import javax
.xml
.bind
.annotation
.XmlElementWrapper
;
30 import javax
.xml
.bind
.annotation
.XmlTransient
;
31 import javax
.xml
.bind
.annotation
.XmlType
;
32 import javax
.xml
.bind
.annotation
.adapters
.XmlJavaTypeAdapter
;
34 import org
.apache
.log4j
.Logger
;
35 import org
.hibernate
.annotations
.Cascade
;
36 import org
.hibernate
.annotations
.CascadeType
;
37 import org
.hibernate
.annotations
.IndexColumn
;
38 import org
.hibernate
.search
.annotations
.Field
;
39 import org
.hibernate
.search
.annotations
.FieldBridge
;
40 import org
.hibernate
.search
.annotations
.Fields
;
42 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
43 import eu
.etaxonomy
.cdm
.hibernate
.StripHtmlBridge
;
44 import eu
.etaxonomy
.cdm
.jaxb
.FormattedTextAdapter
;
45 import eu
.etaxonomy
.cdm
.jaxb
.LSIDAdapter
;
46 import eu
.etaxonomy
.cdm
.model
.media
.Rights
;
47 import eu
.etaxonomy
.cdm
.model
.name
.NonViralName
;
48 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameBase
;
49 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
50 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
51 import eu
.etaxonomy
.cdm
.strategy
.match
.Match
;
52 import eu
.etaxonomy
.cdm
.strategy
.match
.MatchMode
;
53 import eu
.etaxonomy
.cdm
.strategy
.match
.Match
.ReplaceMode
;
54 import eu
.etaxonomy
.cdm
.strategy
.merge
.Merge
;
55 import eu
.etaxonomy
.cdm
.strategy
.merge
.MergeMode
;
58 * 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.
59 * All subclasses inherit the ability to store additional properties that are stored as {@link Extension Extensions}, basically a string value with a type term.
60 * Any number of right statements can be attached as well as multiple {@link OriginalSource} objects.
61 * 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).
62 * A Taxon for example that was taken from 2 sources like FaunaEuropaea and IPNI would have two originalSource objects.
63 * 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.
67 * @created 08-Nov-2007 13:06:27
69 @XmlAccessorType(XmlAccessType
.FIELD
)
70 @XmlType(name
= "IdentifiableEntity", propOrder
= {
73 "protectedTitleCache",
80 public abstract class IdentifiableEntity
<S
extends IIdentifiableEntityCacheStrategy
> extends AnnotatableEntity
81 implements ISourceable
, IIdentifiableEntity
/*, Comparable<IdentifiableEntity> */{
82 private static final long serialVersionUID
= -5610995424730659058L;
83 private static final Logger logger
= Logger
.getLogger(IdentifiableEntity
.class);
86 public static final boolean PROTECTED
= true;
88 public static final boolean NOT_PROTECTED
= false;
90 @XmlElement(name
= "LSID", type
= String
.class)
91 @XmlJavaTypeAdapter(LSIDAdapter
.class)
95 @XmlElement(name
= "TitleCache", required
= true)
96 @XmlJavaTypeAdapter(FormattedTextAdapter
.class)
97 @Column(length
=255, name
="titleCache")
98 @Fields({@Field(index
= org
.hibernate
.search
.annotations
.Index
.TOKENIZED
),
99 @Field(name
= "titleCache_forSort", index
= org
.hibernate
.search
.annotations
.Index
.UN_TOKENIZED
)
101 @FieldBridge(impl
=StripHtmlBridge
.class)
102 @Match(value
=MatchMode
.CACHE
, cacheReplaceMode
=ReplaceMode
.ALL
)
103 protected String titleCache
;
105 //if true titleCache will not be automatically generated/updated
106 @XmlElement(name
= "ProtectedTitleCache")
107 private boolean protectedTitleCache
;
109 @XmlElementWrapper(name
= "Rights")
110 @XmlElement(name
= "Rights")
111 @OneToMany(fetch
= FetchType
.LAZY
)
112 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
})
114 @Merge(MergeMode
.ADD_CLONE
)
115 private Set
<Rights
> rights
= new HashSet
<Rights
>();
117 @XmlElementWrapper(name
= "Credits")
118 @XmlElement(name
= "Credit")
119 @IndexColumn(name
="sortIndex", base
= 0)
120 @OneToMany(fetch
= FetchType
.LAZY
)
121 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
})
123 @Merge(MergeMode
.ADD_CLONE
)
124 private List
<Credit
> credits
= new ArrayList
<Credit
>();
126 @XmlElementWrapper(name
= "Extensions")
127 @XmlElement(name
= "Extension")
128 @OneToMany(fetch
= FetchType
.LAZY
)
129 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
})
130 @Merge(MergeMode
.ADD_CLONE
)
131 private Set
<Extension
> extensions
= new HashSet
<Extension
>();
133 @XmlElementWrapper(name
= "Sources")
134 @XmlElement(name
= "OriginalSource")
135 @OneToMany(fetch
= FetchType
.LAZY
)
136 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
})
137 @Merge(MergeMode
.ADD_CLONE
)
138 private Set
<OriginalSource
> sources
= new HashSet
<OriginalSource
>();
142 protected S cacheStrategy
;
144 protected IdentifiableEntity(){
145 PropertyChangeListener listener
= new PropertyChangeListener() {
146 public void propertyChange(PropertyChangeEvent e
) {
147 if ( !e
.getPropertyName().equals("titleCache") && ! isProtectedTitleCache()){
152 addPropertyChangeListener(listener
);
156 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getLsid()
158 public LSID
getLsid(){
162 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setLsid(java.lang.String)
164 public void setLsid(LSID lsid
){
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() {
179 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getTitleCache()
182 public String
getTitleCache(){
183 if (protectedTitleCache
){
184 return this.titleCache
;
186 // is title dirty, i.e. equal NULL?
187 if (titleCache
== null){
188 this.setTitleCache(generateTitle(),protectedTitleCache
) ; //for truncating
193 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setTitleCache(java.lang.String)
195 public void setTitleCache(String titleCache
){
196 setTitleCache(titleCache
, PROTECTED
);
200 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setTitleCache(java.lang.String, boolean)
202 public void setTitleCache(String titleCache
, boolean protectCache
){
203 //TODO truncation of title cache
204 if (titleCache
!= null && titleCache
.length() > 254){
205 logger
.warn("Truncation of title cache: " + this.toString() + "/" + titleCache
);
206 titleCache
= titleCache
.substring(0, 251) + "...";
208 this.titleCache
= titleCache
;
209 this.protectedTitleCache
= protectCache
;
213 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getRights()
215 public Set
<Rights
> getRights() {
220 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addRights(eu.etaxonomy.cdm.model.media.Rights)
222 public void addRights(Rights right
){
223 this.rights
.add(right
);
226 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeRights(eu.etaxonomy.cdm.model.media.Rights)
228 public void removeRights(Rights right
){
229 this.rights
.remove(right
);
233 public List
<Credit
> getCredits() {
238 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getCredits(int)
240 public Credit
getCredits(int index
){
241 return this.credits
.get(index
);
245 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addCredit(eu.etaxonomy.cdm.model.common.Credit)
247 public void addCredit(Credit credit
){
248 this.credits
.add(credit
);
253 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addCredit(eu.etaxonomy.cdm.model.common.Credit, int)
255 public void addCredit(Credit credit
, int index
){
256 this.credits
.add(index
, credit
);
260 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeCredit(eu.etaxonomy.cdm.model.common.Credit)
262 public void removeCredit(Credit credit
){
263 this.credits
.remove(credit
);
267 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeCredit(int)
269 public void removeCredit(int index
){
270 this.credits
.remove(index
);
275 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getExtensions()
277 public Set
<Extension
> getExtensions(){
278 return this.extensions
;
281 public void addExtension(String value
, ExtensionType extensionType
){
282 Extension
.NewInstance(this, value
, extensionType
);
286 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addExtension(eu.etaxonomy.cdm.model.common.Extension)
288 public void addExtension(Extension extension
){
289 if (extension
!= null){
290 extension
.setExtendedObj(this);
291 this.extensions
.add(extension
);
295 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeExtension(eu.etaxonomy.cdm.model.common.Extension)
297 public void removeExtension(Extension extension
){
298 if (extension
!= null){
299 extension
.setExtendedObj(null);
300 this.extensions
.remove(extension
);
306 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#isProtectedTitleCache()
308 public boolean isProtectedTitleCache() {
309 return protectedTitleCache
;
313 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setProtectedTitleCache(boolean)
315 public void setProtectedTitleCache(boolean protectedTitleCache
) {
316 this.protectedTitleCache
= protectedTitleCache
;
320 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getSources()
322 public Set
<OriginalSource
> getSources() {
326 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addSource(eu.etaxonomy.cdm.model.common.OriginalSource)
328 public void addSource(OriginalSource source
) {
330 IdentifiableEntity oldSourcedObj
= source
.getSourcedObj();
331 if (oldSourcedObj
!= null && oldSourcedObj
!= this){
332 oldSourcedObj
.getSources().remove(source
);
334 this.sources
.add(source
);
335 source
.setSourcedObj(this);
339 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeSource(eu.etaxonomy.cdm.model.common.OriginalSource)
341 public void removeSource(OriginalSource source
) {
342 this.sources
.remove(source
);
346 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#toString()
349 public String
toString() {
351 if (titleCache
== null){
352 result
= super.toString();
354 result
= this.titleCache
;
359 public int compareTo(IdentifiableEntity identifiableEntity
) {
363 if (identifiableEntity
== null) {
364 throw new NullPointerException("Cannot compare to null.");
367 // First, compare the name cache.
368 // TODO: Avoid using instanceof operator
369 // Use Class.getDeclaredMethod() instead to find out whether class has getNameCache() method?
371 String specifiedNameCache
= "";
372 String thisNameCache
= "";
373 String specifiedTitleCache
= "";
374 String thisTitleCache
= "";
375 String specifiedReferenceTitleCache
= "";
376 String thisReferenceTitleCache
= "";
378 if(identifiableEntity
instanceof NonViralName
) {
379 specifiedNameCache
= HibernateProxyHelper
.deproxy(identifiableEntity
, NonViralName
.class).getNameCache();
380 specifiedTitleCache
= identifiableEntity
.getTitleCache();
382 } else if(identifiableEntity
instanceof TaxonBase
) {
383 TaxonBase taxonBase
= HibernateProxyHelper
.deproxy(identifiableEntity
, TaxonBase
.class);
385 TaxonNameBase
<?
,?
> taxonNameBase
= taxonBase
.getName();
386 specifiedNameCache
= HibernateProxyHelper
.deproxy(taxonNameBase
, NonViralName
.class).getNameCache();
387 specifiedTitleCache
= taxonNameBase
.getTitleCache();
389 //specifiedReferenceTitleCache = ((TaxonBase)identifiableEntity).getSec().getTitleCache();
390 // ReferenceBase referenceBase = taxonBase.getSec();
391 // if (referenceBase != null) {
392 // FIXME: HibernateProxyHelper.deproxy(referenceBase, ReferenceBase.class) throws exception
393 // referenceBase = HibernateProxyHelper.deproxy(referenceBase, ReferenceBase.class);
394 // specifiedReferenceTitleCache = referenceBase.getTitleCache();
398 if(this instanceof NonViralName
) {
399 thisNameCache
= HibernateProxyHelper
.deproxy(this, NonViralName
.class).getNameCache();
400 thisTitleCache
= getTitleCache();
401 } else if(this instanceof TaxonBase
) {
402 TaxonNameBase
<?
,?
> taxonNameBase
= HibernateProxyHelper
.deproxy(this, TaxonBase
.class).getName();
403 thisNameCache
= HibernateProxyHelper
.deproxy(taxonNameBase
, NonViralName
.class).getNameCache();
404 thisTitleCache
= taxonNameBase
.getTitleCache();
405 thisReferenceTitleCache
= getTitleCache();
408 // Compare name cache of taxon names
410 if (!specifiedNameCache
.equals("") && !thisNameCache
.equals("")) {
411 result
= thisNameCache
.compareTo(specifiedNameCache
);
414 // Compare title cache of taxon names
416 if ((result
== 0) && (!specifiedTitleCache
.equals("") || !thisTitleCache
.equals(""))) {
417 result
= thisTitleCache
.compareTo(specifiedTitleCache
);
420 // Compare title cache of taxon references
422 if ((result
== 0) && (!specifiedReferenceTitleCache
.equals("") || !thisReferenceTitleCache
.equals(""))) {
423 result
= thisReferenceTitleCache
.compareTo(specifiedReferenceTitleCache
);
430 * Returns the {@link eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy cache strategy} used to generate
431 * several strings corresponding to <i>this</i> identifiable entity
432 * (in particular taxon name caches and author strings).
434 * @return the cache strategy used for <i>this</i> identifiable entity
435 * @see eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
437 public S
getCacheStrategy() {
438 return this.cacheStrategy
;
441 * @see #getCacheStrategy()
444 public void setCacheStrategy(S cacheStrategy
) {
445 this.cacheStrategy
= cacheStrategy
;
448 public String
generateTitle() {
449 if (cacheStrategy
== null){
450 //logger.warn("No CacheStrategy defined for "+ this.getClass() + ": " + this.getUuid());
451 return this.getClass() + ": " + this.getUuid();
453 return cacheStrategy
.getTitleCache(this);
457 //****************** CLONE ************************************************/
460 * @see eu.etaxonomy.cdm.model.common.AnnotatableEntity#clone()
463 public Object
clone() throws CloneNotSupportedException
{
464 IdentifiableEntity result
= (IdentifiableEntity
)super.clone();
467 result
.extensions
= new HashSet
<Extension
>();
468 for (Extension extension
: this.extensions
){
469 Extension newExtension
= (Extension
)extension
.clone();
470 result
.addExtension(newExtension
);
474 result
.sources
= new HashSet
<OriginalSource
>();
475 for (OriginalSource originalSource
: this.sources
){
476 OriginalSource newSource
= (OriginalSource
)originalSource
.clone();
477 result
.addSource(newSource
);
481 result
.rights
= new HashSet
<Rights
>();
482 for(Rights rights
: this.rights
) {
483 result
.addRights(rights
);
488 result
.credits
= new ArrayList
<Credit
>();
489 for(Credit credit
: this.credits
) {
490 result
.addCredit(credit
);
494 //result.setLsid(lsid);
495 //result.setTitleCache(titleCache);
496 //result.setProtectedTitleCache(protectedTitleCache); //must be after setTitleCache
498 //no changes to: lsid, titleCache, protectedTitleCache
501 if (! protectedTitleCache
){
502 result
.titleCache
= null;