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
;
19 import java
.util
.UUID
;
21 import javax
.persistence
.Column
;
22 import javax
.persistence
.Embedded
;
23 import javax
.persistence
.FetchType
;
24 import javax
.persistence
.MappedSuperclass
;
25 import javax
.persistence
.OneToMany
;
26 import javax
.persistence
.Transient
;
27 import javax
.validation
.constraints
.NotNull
;
28 import javax
.validation
.constraints
.Size
;
29 import javax
.xml
.bind
.annotation
.XmlAccessType
;
30 import javax
.xml
.bind
.annotation
.XmlAccessorType
;
31 import javax
.xml
.bind
.annotation
.XmlElement
;
32 import javax
.xml
.bind
.annotation
.XmlElementWrapper
;
33 import javax
.xml
.bind
.annotation
.XmlTransient
;
34 import javax
.xml
.bind
.annotation
.XmlType
;
35 import javax
.xml
.bind
.annotation
.adapters
.XmlJavaTypeAdapter
;
37 import org
.apache
.log4j
.Logger
;
38 import org
.hibernate
.annotations
.Cascade
;
39 import org
.hibernate
.annotations
.CascadeType
;
40 import org
.hibernate
.annotations
.IndexColumn
;
41 import org
.hibernate
.envers
.Audited
;
42 import org
.hibernate
.search
.annotations
.Analyze
;
43 import org
.hibernate
.search
.annotations
.Field
;
44 import org
.hibernate
.search
.annotations
.FieldBridge
;
45 import org
.hibernate
.search
.annotations
.Fields
;
46 import org
.hibernate
.search
.annotations
.Store
;
47 import org
.hibernate
.validator
.constraints
.NotEmpty
;
49 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
50 import eu
.etaxonomy
.cdm
.hibernate
.search
.StripHtmlBridge
;
51 import eu
.etaxonomy
.cdm
.jaxb
.FormattedTextAdapter
;
52 import eu
.etaxonomy
.cdm
.jaxb
.LSIDAdapter
;
53 import eu
.etaxonomy
.cdm
.model
.media
.Rights
;
54 import eu
.etaxonomy
.cdm
.model
.name
.NonViralName
;
55 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameBase
;
56 import eu
.etaxonomy
.cdm
.model
.reference
.Reference
;
57 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
58 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.IIdentifiableEntityCacheStrategy
;
59 import eu
.etaxonomy
.cdm
.strategy
.match
.Match
;
60 import eu
.etaxonomy
.cdm
.strategy
.match
.Match
.ReplaceMode
;
61 import eu
.etaxonomy
.cdm
.strategy
.match
.MatchMode
;
62 import eu
.etaxonomy
.cdm
.strategy
.merge
.Merge
;
63 import eu
.etaxonomy
.cdm
.strategy
.merge
.MergeMode
;
64 import eu
.etaxonomy
.cdm
.validation
.Level2
;
67 * 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.
68 * All subclasses inherit the ability to store additional properties that are stored as {@link Extension Extensions}, basically a string value with a type term.
69 * Any number of right statements can be attached as well as multiple {@link OriginalSourceBase} objects.
70 * 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).
71 * A Taxon for example that was taken from 2 sources like FaunaEuropaea and IPNI would have two originalSource objects.
72 * 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.
76 * @created 08-Nov-2007 13:06:27
78 @XmlAccessorType(XmlAccessType
.FIELD
)
79 @XmlType(name
= "IdentifiableEntity", propOrder
= {
82 "protectedTitleCache",
90 public abstract class IdentifiableEntity
<S
extends IIdentifiableEntityCacheStrategy
> extends AnnotatableEntity
91 implements IIdentifiableEntity
/*, ISourceable<IdentifiableSource> */ {
92 private static final long serialVersionUID
= -5610995424730659058L;
93 private static final Logger logger
= Logger
.getLogger(IdentifiableEntity
.class);
96 public static final boolean PROTECTED
= true;
98 public static final boolean NOT_PROTECTED
= false;
100 @XmlElement(name
= "LSID", type
= String
.class)
101 @XmlJavaTypeAdapter(LSIDAdapter
.class)
105 @XmlElement(name
= "TitleCache", required
= true)
106 @XmlJavaTypeAdapter(FormattedTextAdapter
.class)
107 @Column(length
=255, name
="titleCache")
108 @Match(value
=MatchMode
.CACHE
, cacheReplaceMode
=ReplaceMode
.ALL
)
109 @NotEmpty(groups
= Level2
.class) // implictly NotNull
112 @Field(store
=Store
.YES
),
113 @Field(name
= "titleCache__sort", analyze
= Analyze
.NO
, store
=Store
.YES
)
115 @FieldBridge(impl
=StripHtmlBridge
.class)
116 protected String titleCache
;
118 //if true titleCache will not be automatically generated/updated
119 @XmlElement(name
= "ProtectedTitleCache")
120 protected boolean protectedTitleCache
;
122 @XmlElementWrapper(name
= "Rights", nillable
= true)
123 @XmlElement(name
= "Rights")
124 @OneToMany(fetch
= FetchType
.LAZY
, orphanRemoval
=true)
125 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
})
127 @Merge(MergeMode
.ADD_CLONE
)
129 private Set
<Rights
> rights
= new HashSet
<Rights
>();
131 @XmlElementWrapper(name
= "Credits", nillable
= true)
132 @XmlElement(name
= "Credit")
133 @IndexColumn(name
="sortIndex", base
= 0)
134 @OneToMany(fetch
= FetchType
.LAZY
, orphanRemoval
=true)
135 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
})
137 @Merge(MergeMode
.ADD_CLONE
)
139 private List
<Credit
> credits
= new ArrayList
<Credit
>();
141 @XmlElementWrapper(name
= "Extensions", nillable
= true)
142 @XmlElement(name
= "Extension")
143 @OneToMany(fetch
= FetchType
.LAZY
, orphanRemoval
=true)
144 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
})
145 @Merge(MergeMode
.ADD_CLONE
)
147 private Set
<Extension
> extensions
= new HashSet
<Extension
>();
149 @XmlElementWrapper(name
= "Sources", nillable
= true)
150 @XmlElement(name
= "IdentifiableSource")
151 @OneToMany(fetch
= FetchType
.LAZY
, orphanRemoval
=true)
152 @Cascade({CascadeType
.SAVE_UPDATE
, CascadeType
.MERGE
, CascadeType
.DELETE
})
153 @Merge(MergeMode
.ADD_CLONE
)
155 private Set
<IdentifiableSource
> sources
= new HashSet
<IdentifiableSource
>();
159 protected S cacheStrategy
;
161 protected IdentifiableEntity(){
165 protected void initListener(){
166 PropertyChangeListener listener
= new PropertyChangeListener() {
168 public void propertyChange(PropertyChangeEvent e
) {
169 if (!e
.getPropertyName().equals("titleCache") && !e
.getPropertyName().equals("cacheStrategy") && ! isProtectedTitleCache()){
174 addPropertyChangeListener(listener
);
178 * By default, we expect most cdm objects to be abstract things
179 * i.e. unable to return a data representation.
181 * Specific subclasses (e.g. Sequence) can override if necessary.
184 public byte[] getData() {
188 //******************************** CACHE *****************************************************/
192 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getTitleCache()
194 // @Transient - must not be transient, since this property needs to to be included in all serializations produced by the remote layer
196 public String
getTitleCache(){
197 if (protectedTitleCache
){
198 return this.titleCache
;
200 // is title dirty, i.e. equal NULL?
201 if (titleCache
== null){
202 this.titleCache
= generateTitle();
203 this.titleCache
= getTruncatedCache(this.titleCache
) ;
209 * The titleCache will be regenerated from scratch if not protected
210 * @return <code>true</code> if title cache was regenerated, <code>false</code> otherwise
212 protected boolean regenerateTitleCache() {
213 if (!protectedTitleCache
) {
214 this.titleCache
= null;
217 return protectedTitleCache
;
220 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setTitleCache(java.lang.String)
223 public void setTitleCache(String titleCache
){
224 this.titleCache
= titleCache
;
228 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setTitleCache(java.lang.String, boolean)
231 public void setTitleCache(String titleCache
, boolean protectCache
){
232 titleCache
= getTruncatedCache(titleCache
);
233 this.titleCache
= titleCache
;
234 this.protectedTitleCache
= protectCache
;
243 protected String
getTruncatedCache(String cache
) {
244 if (cache
!= null && cache
.length() > 255){
245 logger
.warn("Truncation of cache: " + this.toString() + "/" + cache
);
246 cache
= cache
.substring(0, 252) + "...";
251 //**************************************************************************************
254 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getLsid()
257 public LSID
getLsid(){
261 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setLsid(java.lang.String)
264 public void setLsid(LSID lsid
){
268 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getRights()
271 public Set
<Rights
> getRights() {
273 this.rights
= new HashSet
<Rights
>();
279 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addRights(eu.etaxonomy.cdm.model.media.Rights)
282 public void addRights(Rights right
){
283 getRights().add(right
);
286 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeRights(eu.etaxonomy.cdm.model.media.Rights)
289 public void removeRights(Rights right
){
290 getRights().remove(right
);
295 public List
<Credit
> getCredits() {
296 if(credits
== null) {
297 this.credits
= new ArrayList
<Credit
>();
303 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getCredits(int)
306 public Credit
getCredits(Integer index
){
307 return getCredits().get(index
);
311 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addCredit(eu.etaxonomy.cdm.model.common.Credit)
314 public void addCredit(Credit credit
){
315 getCredits().add(credit
);
320 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addCredit(eu.etaxonomy.cdm.model.common.Credit, int)
323 public void addCredit(Credit credit
, int index
){
324 getCredits().add(index
, credit
);
328 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeCredit(eu.etaxonomy.cdm.model.common.Credit)
331 public void removeCredit(Credit credit
){
332 getCredits().remove(credit
);
336 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeCredit(int)
339 public void removeCredit(int index
){
340 getCredits().remove(index
);
345 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getExtensions()
348 public Set
<Extension
> getExtensions(){
349 if(extensions
== null) {
350 this.extensions
= new HashSet
<Extension
>();
352 return this.extensions
;
356 * @return a Set of extension value strings
358 public Set
<String
> getExtensions(ExtensionType type
){
359 return getExtensions(type
.getUuid());
362 * @param extensionTypeUuid
363 * @return a Set of extension value strings
365 public Set
<String
> getExtensions(UUID extensionTypeUuid
){
366 Set
<String
> result
= new HashSet
<String
>();
367 for (Extension extension
: getExtensions()){
368 if (extension
.getType().getUuid().equals(extensionTypeUuid
)){
369 result
.add(extension
.getValue());
375 public void addExtension(String value
, ExtensionType extensionType
){
376 Extension
.NewInstance(this, value
, extensionType
);
380 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addExtension(eu.etaxonomy.cdm.model.common.Extension)
383 public void addExtension(Extension extension
){
384 if (extension
!= null){
385 extension
.setExtendedObj(this);
386 getExtensions().add(extension
);
390 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeExtension(eu.etaxonomy.cdm.model.common.Extension)
393 public void removeExtension(Extension extension
){
394 if (extension
!= null){
395 extension
.setExtendedObj(null);
396 getExtensions().remove(extension
);
402 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#isProtectedTitleCache()
405 public boolean isProtectedTitleCache() {
406 return protectedTitleCache
;
410 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setProtectedTitleCache(boolean)
413 public void setProtectedTitleCache(boolean protectedTitleCache
) {
414 this.protectedTitleCache
= protectedTitleCache
;
418 * @see eu.etaxonomy.cdm.model.common.ISourceable#getSources()
421 public Set
<IdentifiableSource
> getSources() {
422 if(sources
== null) {
423 this.sources
= new HashSet
<IdentifiableSource
>();
429 * @see eu.etaxonomy.cdm.model.common.ISourceable#addSource(eu.etaxonomy.cdm.model.common.OriginalSourceBase)
432 public void addSource(IdentifiableSource source
) {
434 IdentifiableEntity oldSourcedObj
= source
.getSourcedObj();
435 if (oldSourcedObj
!= null && oldSourcedObj
!= this){
436 oldSourcedObj
.getSources().remove(source
);
438 getSources().add(source
);
439 source
.setSourcedObj(this);
444 * @see eu.etaxonomy.cdm.model.common.ISourceable#addSource(java.lang.String, java.lang.String, eu.etaxonomy.cdm.model.reference.Reference, java.lang.String)
447 public IdentifiableSource
addSource(String id
, String idNamespace
, Reference citation
, String microCitation
) {
448 if (id
== null && idNamespace
== null && citation
== null && microCitation
== null){
451 IdentifiableSource source
= IdentifiableSource
.NewInstance(id
, idNamespace
, citation
, microCitation
);
457 * @see eu.etaxonomy.cdm.model.common.ISourceable#removeSource(eu.etaxonomy.cdm.model.common.IOriginalSource)
460 public void removeSource(IdentifiableSource source
) {
461 getSources().remove(source
);
464 //******************************** TO STRING *****************************************************/
467 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#toString()
470 public String
toString() {
472 if (titleCache
== null){
473 result
= super.toString();
475 result
= this.titleCache
;
481 public int compareTo(IdentifiableEntity identifiableEntity
) {
485 if (identifiableEntity
== null) {
486 throw new NullPointerException("Cannot compare to null.");
489 // First, compare the name cache.
490 // TODO: Avoid using instanceof operator
491 // Use Class.getDeclaredMethod() instead to find out whether class has getNameCache() method?
493 String specifiedNameCache
= "";
494 String thisNameCache
= "";
495 String specifiedTitleCache
= "";
496 String thisTitleCache
= "";
497 String specifiedReferenceTitleCache
= "";
498 String thisReferenceTitleCache
= "";
499 String thisGenusString
= "";
500 String specifiedGenusString
= "";
501 int thisrank_order
= 0;
503 if(identifiableEntity
instanceof NonViralName
) {
504 specifiedNameCache
= HibernateProxyHelper
.deproxy(identifiableEntity
, NonViralName
.class).getNameCache();
505 specifiedTitleCache
= identifiableEntity
.getTitleCache();
507 } else if(identifiableEntity
instanceof TaxonBase
) {
508 TaxonBase taxonBase
= HibernateProxyHelper
.deproxy(identifiableEntity
, TaxonBase
.class);
510 TaxonNameBase
<?
,?
> taxonNameBase
= taxonBase
.getName();
513 NonViralName nonViralName
= HibernateProxyHelper
.deproxy(taxonNameBase
, NonViralName
.class);
514 specifiedNameCache
= nonViralName
.getNameCache();
515 specifiedTitleCache
= taxonNameBase
.getTitleCache();
517 specifiedReferenceTitleCache
= ((TaxonBase
)identifiableEntity
).getSec().getTitleCache();
518 Reference reference
= taxonBase
.getSec();
519 if (reference
!= null) {
520 reference
= HibernateProxyHelper
.deproxy(reference
, Reference
.class);
521 specifiedReferenceTitleCache
= reference
.getTitleCache();
525 if(this instanceof NonViralName
) {
526 thisNameCache
= HibernateProxyHelper
.deproxy(this, NonViralName
.class).getNameCache();
527 thisTitleCache
= getTitleCache();
528 } else if(this instanceof TaxonBase
) {
529 TaxonNameBase
<?
,?
> taxonNameBase
= HibernateProxyHelper
.deproxy(this, TaxonBase
.class).getName();
530 NonViralName nonViralName
= HibernateProxyHelper
.deproxy(taxonNameBase
, NonViralName
.class);
531 thisNameCache
= nonViralName
.getNameCache();
532 thisTitleCache
= taxonNameBase
.getTitleCache();
533 thisReferenceTitleCache
= getTitleCache();
534 thisGenusString
= nonViralName
.getGenusOrUninomial();
537 // Compare name cache of taxon names
539 if (!specifiedNameCache
.equals("") && !thisNameCache
.equals("")) {
540 result
= thisNameCache
.compareTo(specifiedNameCache
);
543 // Compare title cache of taxon names
545 if ((result
== 0) && (!specifiedTitleCache
.equals("") || !thisTitleCache
.equals(""))) {
546 result
= thisTitleCache
.compareTo(specifiedTitleCache
);
549 // Compare title cache of taxon references
551 if ((result
== 0) && (!specifiedReferenceTitleCache
.equals("") || !thisReferenceTitleCache
.equals(""))) {
552 result
= thisReferenceTitleCache
.compareTo(specifiedReferenceTitleCache
);
559 * Returns the {@link eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy cache strategy} used to generate
560 * several strings corresponding to <i>this</i> identifiable entity
561 * (in particular taxon name caches and author strings).
563 * @return the cache strategy used for <i>this</i> identifiable entity
564 * @see eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
566 public S
getCacheStrategy() {
567 return this.cacheStrategy
;
570 * @see #getCacheStrategy()
573 public void setCacheStrategy(S cacheStrategy
) {
574 this.cacheStrategy
= cacheStrategy
;
578 public String
generateTitle() {
579 if (cacheStrategy
== null){
580 //logger.warn("No CacheStrategy defined for "+ this.getClass() + ": " + this.getUuid());
581 return this.getClass() + ": " + this.getUuid();
583 return cacheStrategy
.getTitleCache(this);
587 //****************** CLONE ************************************************/
590 * @see eu.etaxonomy.cdm.model.common.AnnotatableEntity#clone()
593 public Object
clone() throws CloneNotSupportedException
{
594 IdentifiableEntity result
= (IdentifiableEntity
)super.clone();
597 result
.extensions
= new HashSet
<Extension
>();
598 for (Extension extension
: getExtensions() ){
599 Extension newExtension
= (Extension
)extension
.clone();
600 result
.addExtension(newExtension
);
604 result
.sources
= new HashSet
<IdentifiableSource
>();
605 for (IdentifiableSource source
: getSources()){
606 IdentifiableSource newSource
= (IdentifiableSource
)source
.clone();
607 result
.addSource(newSource
);
611 result
.rights
= new HashSet
<Rights
>();
612 for(Rights rights
: getRights()) {
613 result
.addRights(rights
);
618 result
.credits
= new ArrayList
<Credit
>();
619 for(Credit credit
: getCredits()) {
620 result
.addCredit(credit
);
623 //no changes to: lsid, titleCache, protectedTitleCache
626 if (! protectedTitleCache
){
627 result
.titleCache
= null;
630 result
.initListener();