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
110 @Size(max
= 800) //see #1592
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() > 1023){
245 logger
.warn("Truncation of cache: " + this.toString() + "/" + cache
);
246 cache
= cache
.substring(0, 1020) + "...";
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(OriginalSourceType type
, String id
, String idNamespace
, Reference citation
, String microCitation
) {
448 if (id
== null && idNamespace
== null && citation
== null && microCitation
== null){
451 IdentifiableSource source
= IdentifiableSource
.NewInstance(type
, id
, idNamespace
, citation
, microCitation
);
458 * @see eu.etaxonomy.cdm.model.common.ISourceable#addImportSource(java.lang.String, java.lang.String, eu.etaxonomy.cdm.model.reference.Reference, java.lang.String)
461 public IdentifiableSource
addImportSource(String id
, String idNamespace
, Reference
<?
> citation
, String microCitation
) {
462 if (id
== null && idNamespace
== null && citation
== null && microCitation
== null){
465 IdentifiableSource source
= IdentifiableSource
.NewInstance(OriginalSourceType
.Import
, id
, idNamespace
, citation
, microCitation
);
471 * @see eu.etaxonomy.cdm.model.common.ISourceable#removeSource(eu.etaxonomy.cdm.model.common.IOriginalSource)
474 public void removeSource(IdentifiableSource source
) {
475 getSources().remove(source
);
478 //******************************** TO STRING *****************************************************/
481 * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#toString()
484 public String
toString() {
486 if (titleCache
== null){
487 result
= super.toString();
489 result
= this.titleCache
;
495 public int compareTo(IdentifiableEntity identifiableEntity
) {
499 if (identifiableEntity
== null) {
500 throw new NullPointerException("Cannot compare to null.");
503 // First, compare the name cache.
504 // TODO: Avoid using instanceof operator
505 // Use Class.getDeclaredMethod() instead to find out whether class has getNameCache() method?
507 String specifiedNameCache
= "";
508 String thisNameCache
= "";
509 String specifiedTitleCache
= "";
510 String thisTitleCache
= "";
511 String specifiedReferenceTitleCache
= "";
512 String thisReferenceTitleCache
= "";
513 String thisGenusString
= "";
514 String specifiedGenusString
= "";
515 int thisrank_order
= 0;
517 if(identifiableEntity
instanceof NonViralName
) {
518 specifiedNameCache
= HibernateProxyHelper
.deproxy(identifiableEntity
, NonViralName
.class).getNameCache();
519 specifiedTitleCache
= identifiableEntity
.getTitleCache();
521 } else if(identifiableEntity
instanceof TaxonBase
) {
522 TaxonBase taxonBase
= HibernateProxyHelper
.deproxy(identifiableEntity
, TaxonBase
.class);
524 TaxonNameBase
<?
,?
> taxonNameBase
= taxonBase
.getName();
527 NonViralName nonViralName
= HibernateProxyHelper
.deproxy(taxonNameBase
, NonViralName
.class);
528 specifiedNameCache
= nonViralName
.getNameCache();
529 specifiedTitleCache
= taxonNameBase
.getTitleCache();
531 specifiedReferenceTitleCache
= ((TaxonBase
)identifiableEntity
).getSec().getTitleCache();
532 Reference reference
= taxonBase
.getSec();
533 if (reference
!= null) {
534 reference
= HibernateProxyHelper
.deproxy(reference
, Reference
.class);
535 specifiedReferenceTitleCache
= reference
.getTitleCache();
539 if(this instanceof NonViralName
) {
540 thisNameCache
= HibernateProxyHelper
.deproxy(this, NonViralName
.class).getNameCache();
541 thisTitleCache
= getTitleCache();
542 } else if(this instanceof TaxonBase
) {
543 TaxonNameBase
<?
,?
> taxonNameBase
= HibernateProxyHelper
.deproxy(this, TaxonBase
.class).getName();
544 NonViralName nonViralName
= HibernateProxyHelper
.deproxy(taxonNameBase
, NonViralName
.class);
545 thisNameCache
= nonViralName
.getNameCache();
546 thisTitleCache
= taxonNameBase
.getTitleCache();
547 thisReferenceTitleCache
= getTitleCache();
548 thisGenusString
= nonViralName
.getGenusOrUninomial();
551 // Compare name cache of taxon names
553 if (!specifiedNameCache
.equals("") && !thisNameCache
.equals("")) {
554 result
= thisNameCache
.compareTo(specifiedNameCache
);
557 // Compare title cache of taxon names
559 if ((result
== 0) && (!specifiedTitleCache
.equals("") || !thisTitleCache
.equals(""))) {
560 result
= thisTitleCache
.compareTo(specifiedTitleCache
);
563 // Compare title cache of taxon references
565 if ((result
== 0) && (!specifiedReferenceTitleCache
.equals("") || !thisReferenceTitleCache
.equals(""))) {
566 result
= thisReferenceTitleCache
.compareTo(specifiedReferenceTitleCache
);
573 * Returns the {@link eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy cache strategy} used to generate
574 * several strings corresponding to <i>this</i> identifiable entity
575 * (in particular taxon name caches and author strings).
577 * @return the cache strategy used for <i>this</i> identifiable entity
578 * @see eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
580 public S
getCacheStrategy() {
581 return this.cacheStrategy
;
584 * @see #getCacheStrategy()
587 public void setCacheStrategy(S cacheStrategy
) {
588 this.cacheStrategy
= cacheStrategy
;
592 public String
generateTitle() {
593 if (cacheStrategy
== null){
594 //logger.warn("No CacheStrategy defined for "+ this.getClass() + ": " + this.getUuid());
595 return this.getClass() + ": " + this.getUuid();
597 return cacheStrategy
.getTitleCache(this);
601 //****************** CLONE ************************************************/
604 * @see eu.etaxonomy.cdm.model.common.AnnotatableEntity#clone()
607 public Object
clone() throws CloneNotSupportedException
{
608 IdentifiableEntity result
= (IdentifiableEntity
)super.clone();
611 result
.extensions
= new HashSet
<Extension
>();
612 for (Extension extension
: getExtensions() ){
613 Extension newExtension
= (Extension
)extension
.clone();
614 result
.addExtension(newExtension
);
618 result
.sources
= new HashSet
<IdentifiableSource
>();
619 for (IdentifiableSource source
: getSources()){
620 IdentifiableSource newSource
= (IdentifiableSource
)source
.clone();
621 result
.addSource(newSource
);
625 result
.rights
= new HashSet
<Rights
>();
626 for(Rights rights
: getRights()) {
627 result
.addRights(rights
);
632 result
.credits
= new ArrayList
<Credit
>();
633 for(Credit credit
: getCredits()) {
634 result
.addCredit(credit
);
637 //no changes to: lsid, titleCache, protectedTitleCache
640 if (! protectedTitleCache
){
641 result
.titleCache
= null;
644 result
.initListener();