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 OriginalSourceBase} 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 IIdentifiableEntity
/*, ISourceable<IdentifiableSource> */ {
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
<IdentifiableSource
> sources
= new HashSet
<IdentifiableSource
>();
142 protected S cacheStrategy
;
144 protected IdentifiableEntity(){
148 protected void initListener(){
149 PropertyChangeListener listener
= new PropertyChangeListener() {
150 public void propertyChange(PropertyChangeEvent e
) {
151 if (!e
.getPropertyName().equals("titleCache") && ! isProtectedTitleCache()){
156 addPropertyChangeListener(listener
);
161 * By default, we expect most cdm objects to be abstract things
162 * i.e. unable to return a data representation.
164 * Specific subclasses (e.g. Sequence) can override if necessary.
166 public byte[] getData() {
170 //******************************** CACHE *****************************************************/
174 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getTitleCache()
177 public String
getTitleCache(){
178 if (protectedTitleCache
){
179 return this.titleCache
;
181 // is title dirty, i.e. equal NULL?
182 if (titleCache
== null){
183 this.titleCache
= getTruncatedCache(generateTitle()) ;
188 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setTitleCache(java.lang.String)
190 public void setTitleCache(String titleCache
){
191 setTitleCache(titleCache
, PROTECTED
);
195 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setTitleCache(java.lang.String, boolean)
197 public void setTitleCache(String titleCache
, boolean protectCache
){
198 titleCache
= getTruncatedCache(titleCache
);
199 this.titleCache
= titleCache
;
200 this.protectedTitleCache
= protectCache
;
209 protected String
getTruncatedCache(String cache
) {
210 if (cache
!= null && cache
.length() > 252){
211 logger
.warn("Truncation of cache: " + this.toString() + "/" + cache
);
212 cache
= cache
.substring(0, 252) + "...";
217 //**************************************************************************************
220 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getLsid()
222 public LSID
getLsid(){
226 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setLsid(java.lang.String)
228 public void setLsid(LSID lsid
){
234 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getRights()
236 public Set
<Rights
> getRights() {
241 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addRights(eu.etaxonomy.cdm.model.media.Rights)
243 public void addRights(Rights right
){
244 this.rights
.add(right
);
247 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeRights(eu.etaxonomy.cdm.model.media.Rights)
249 public void removeRights(Rights right
){
250 this.rights
.remove(right
);
254 public List
<Credit
> getCredits() {
259 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getCredits(int)
261 public Credit
getCredits(int index
){
262 return this.credits
.get(index
);
266 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addCredit(eu.etaxonomy.cdm.model.common.Credit)
268 public void addCredit(Credit credit
){
269 this.credits
.add(credit
);
274 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addCredit(eu.etaxonomy.cdm.model.common.Credit, int)
276 public void addCredit(Credit credit
, int index
){
277 this.credits
.add(index
, credit
);
281 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeCredit(eu.etaxonomy.cdm.model.common.Credit)
283 public void removeCredit(Credit credit
){
284 this.credits
.remove(credit
);
288 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeCredit(int)
290 public void removeCredit(int index
){
291 this.credits
.remove(index
);
296 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getExtensions()
298 public Set
<Extension
> getExtensions(){
299 return this.extensions
;
302 public void addExtension(String value
, ExtensionType extensionType
){
303 Extension
.NewInstance(this, value
, extensionType
);
307 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addExtension(eu.etaxonomy.cdm.model.common.Extension)
309 public void addExtension(Extension extension
){
310 if (extension
!= null){
311 extension
.setExtendedObj(this);
312 this.extensions
.add(extension
);
316 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeExtension(eu.etaxonomy.cdm.model.common.Extension)
318 public void removeExtension(Extension extension
){
319 if (extension
!= null){
320 extension
.setExtendedObj(null);
321 this.extensions
.remove(extension
);
327 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#isProtectedTitleCache()
329 public boolean isProtectedTitleCache() {
330 return protectedTitleCache
;
334 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setProtectedTitleCache(boolean)
336 public void setProtectedTitleCache(boolean protectedTitleCache
) {
337 this.protectedTitleCache
= protectedTitleCache
;
341 * @see eu.etaxonomy.cdm.model.common.ISourceable#getSources()
343 public Set
<IdentifiableSource
> getSources() {
348 * @see eu.etaxonomy.cdm.model.common.ISourceable#addSource(eu.etaxonomy.cdm.model.common.OriginalSourceBase)
350 public void addSource(IdentifiableSource source
) {
352 IdentifiableEntity oldSourcedObj
= source
.getSourcedObj();
353 if (oldSourcedObj
!= null && oldSourcedObj
!= this){
354 oldSourcedObj
.getSources().remove(source
);
356 this.sources
.add(source
);
357 source
.setSourcedObj(this);
362 * @see eu.etaxonomy.cdm.model.common.ISourceable#removeSource(eu.etaxonomy.cdm.model.common.IOriginalSource)
364 public void removeSource(IdentifiableSource source
) {
365 this.sources
.remove(source
);
369 //******************************** TO STRING *****************************************************/
372 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#toString()
375 public String
toString() {
377 if (titleCache
== null){
378 result
= super.toString();
380 result
= this.titleCache
;
386 public int compareTo(IdentifiableEntity identifiableEntity
) {
390 if (identifiableEntity
== null) {
391 throw new NullPointerException("Cannot compare to null.");
394 // First, compare the name cache.
395 // TODO: Avoid using instanceof operator
396 // Use Class.getDeclaredMethod() instead to find out whether class has getNameCache() method?
398 String specifiedNameCache
= "";
399 String thisNameCache
= "";
400 String specifiedTitleCache
= "";
401 String thisTitleCache
= "";
402 String specifiedReferenceTitleCache
= "";
403 String thisReferenceTitleCache
= "";
405 if(identifiableEntity
instanceof NonViralName
) {
406 specifiedNameCache
= HibernateProxyHelper
.deproxy(identifiableEntity
, NonViralName
.class).getNameCache();
407 specifiedTitleCache
= identifiableEntity
.getTitleCache();
409 } else if(identifiableEntity
instanceof TaxonBase
) {
410 TaxonBase taxonBase
= HibernateProxyHelper
.deproxy(identifiableEntity
, TaxonBase
.class);
412 TaxonNameBase
<?
,?
> taxonNameBase
= taxonBase
.getName();
413 specifiedNameCache
= HibernateProxyHelper
.deproxy(taxonNameBase
, NonViralName
.class).getNameCache();
414 specifiedTitleCache
= taxonNameBase
.getTitleCache();
416 //specifiedReferenceTitleCache = ((TaxonBase)identifiableEntity).getSec().getTitleCache();
417 // ReferenceBase referenceBase = taxonBase.getSec();
418 // if (referenceBase != null) {
419 // FIXME: HibernateProxyHelper.deproxy(referenceBase, ReferenceBase.class) throws exception
420 // referenceBase = HibernateProxyHelper.deproxy(referenceBase, ReferenceBase.class);
421 // specifiedReferenceTitleCache = referenceBase.getTitleCache();
425 if(this instanceof NonViralName
) {
426 thisNameCache
= HibernateProxyHelper
.deproxy(this, NonViralName
.class).getNameCache();
427 thisTitleCache
= getTitleCache();
428 } else if(this instanceof TaxonBase
) {
429 TaxonNameBase
<?
,?
> taxonNameBase
= HibernateProxyHelper
.deproxy(this, TaxonBase
.class).getName();
430 thisNameCache
= HibernateProxyHelper
.deproxy(taxonNameBase
, NonViralName
.class).getNameCache();
431 thisTitleCache
= taxonNameBase
.getTitleCache();
432 thisReferenceTitleCache
= getTitleCache();
435 // Compare name cache of taxon names
437 if (!specifiedNameCache
.equals("") && !thisNameCache
.equals("")) {
438 result
= thisNameCache
.compareTo(specifiedNameCache
);
441 // Compare title cache of taxon names
443 if ((result
== 0) && (!specifiedTitleCache
.equals("") || !thisTitleCache
.equals(""))) {
444 result
= thisTitleCache
.compareTo(specifiedTitleCache
);
447 // Compare title cache of taxon references
449 if ((result
== 0) && (!specifiedReferenceTitleCache
.equals("") || !thisReferenceTitleCache
.equals(""))) {
450 result
= thisReferenceTitleCache
.compareTo(specifiedReferenceTitleCache
);
457 * Returns the {@link eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy cache strategy} used to generate
458 * several strings corresponding to <i>this</i> identifiable entity
459 * (in particular taxon name caches and author strings).
461 * @return the cache strategy used for <i>this</i> identifiable entity
462 * @see eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
464 public S
getCacheStrategy() {
465 return this.cacheStrategy
;
468 * @see #getCacheStrategy()
471 public void setCacheStrategy(S cacheStrategy
) {
472 this.cacheStrategy
= cacheStrategy
;
475 public String
generateTitle() {
476 if (cacheStrategy
== null){
477 //logger.warn("No CacheStrategy defined for "+ this.getClass() + ": " + this.getUuid());
478 return this.getClass() + ": " + this.getUuid();
480 return cacheStrategy
.getTitleCache(this);
484 //****************** CLONE ************************************************/
487 * @see eu.etaxonomy.cdm.model.common.AnnotatableEntity#clone()
490 public Object
clone() throws CloneNotSupportedException
{
491 IdentifiableEntity result
= (IdentifiableEntity
)super.clone();
494 result
.extensions
= new HashSet
<Extension
>();
495 for (Extension extension
: this.extensions
){
496 Extension newExtension
= (Extension
)extension
.clone();
497 result
.addExtension(newExtension
);
501 result
.sources
= new HashSet
<IdentifiableSource
>();
502 for (IdentifiableSource source
: this.sources
){
503 IdentifiableSource newSource
= (IdentifiableSource
)source
.clone();
504 result
.addSource(newSource
);
508 result
.rights
= new HashSet
<Rights
>();
509 for(Rights rights
: this.rights
) {
510 result
.addRights(rights
);
515 result
.credits
= new ArrayList
<Credit
>();
516 for(Credit credit
: this.credits
) {
517 result
.addCredit(credit
);
521 //result.setLsid(lsid);
522 //result.setTitleCache(titleCache);
523 //result.setProtectedTitleCache(protectedTitleCache); //must be after setTitleCache
525 //no changes to: lsid, titleCache, protectedTitleCache
528 if (! protectedTitleCache
){
529 result
.titleCache
= null;