- re-enabled identifier set in IdentifiableEntity
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / common / IdentifiableEntity.java
index bafddf13ce30362b61f9aeb7449d74ef0036d0e4..5cbec1f13b8f2c02d5304bb0ab93478dda18cc52 100644 (file)
@@ -1,8 +1,8 @@
 /**
 * Copyright (C) 2007 EDIT
-* European Distributed Institute of Taxonomy 
+* European Distributed Institute of Taxonomy
 * http://www.e-taxonomy.eu
-* 
+*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * See LICENSE.TXT at the top of this package for the full license terms.
 */
 package eu.etaxonomy.cdm.model.common;
 
 
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.UUID;
 
 import javax.persistence.Column;
 import javax.persistence.Embedded;
@@ -21,6 +24,8 @@ import javax.persistence.FetchType;
 import javax.persistence.MappedSuperclass;
 import javax.persistence.OneToMany;
 import javax.persistence.Transient;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlElement;
@@ -33,32 +38,40 @@ import org.apache.log4j.Logger;
 import org.hibernate.annotations.Cascade;
 import org.hibernate.annotations.CascadeType;
 import org.hibernate.annotations.IndexColumn;
+import org.hibernate.envers.Audited;
+import org.hibernate.search.annotations.Analyze;
 import org.hibernate.search.annotations.Field;
 import org.hibernate.search.annotations.FieldBridge;
 import org.hibernate.search.annotations.Fields;
+import org.hibernate.search.annotations.Store;
+import org.hibernate.validator.constraints.NotEmpty;
 
 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
-import eu.etaxonomy.cdm.hibernate.StripHtmlBridge;
+import eu.etaxonomy.cdm.hibernate.search.StripHtmlBridge;
 import eu.etaxonomy.cdm.jaxb.FormattedTextAdapter;
 import eu.etaxonomy.cdm.jaxb.LSIDAdapter;
-import eu.etaxonomy.cdm.model.media.IdentifiableMediaEntity;
 import eu.etaxonomy.cdm.model.media.Rights;
+import eu.etaxonomy.cdm.model.name.BotanicalName;
 import eu.etaxonomy.cdm.model.name.NonViralName;
 import eu.etaxonomy.cdm.model.name.TaxonNameBase;
+import eu.etaxonomy.cdm.model.reference.Reference;
 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
-import eu.etaxonomy.cdm.strategy.merge.IMergeStrategy;
+import eu.etaxonomy.cdm.strategy.match.Match;
+import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
+import eu.etaxonomy.cdm.strategy.match.MatchMode;
 import eu.etaxonomy.cdm.strategy.merge.Merge;
 import eu.etaxonomy.cdm.strategy.merge.MergeMode;
+import eu.etaxonomy.cdm.validation.Level2;
 
 /**
  * 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.
  * All subclasses inherit the ability to store additional properties that are stored as {@link Extension Extensions}, basically a string value with a type term.
- * Any number of right statements can be attached as well as multiple {@link OriginalSource} objects. 
+ * Any number of right statements can be attached as well as multiple {@link OriginalSourceBase} objects.
  * 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).
  * A Taxon for example that was taken from 2 sources like FaunaEuropaea and IPNI would have two originalSource objects.
  * 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.
- *  
+ *
  * @author m.doering
  * @version 1.0
  * @created 08-Nov-2007 13:06:27
@@ -73,416 +86,606 @@ import eu.etaxonomy.cdm.strategy.merge.MergeMode;
     "credits",
     "sources"
 })
+@Audited
 @MappedSuperclass
-public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrategy> extends AnnotatableEntity 
-               implements ISourceable, IIdentifiableEntity, Comparable<IdentifiableEntity> {
-       private static final long serialVersionUID = -5610995424730659058L;
-       private static final Logger logger = Logger.getLogger(IdentifiableEntity.class);
-
-       @XmlTransient
-       public static final boolean PROTECTED = true;
-       @XmlTransient
-       public static final boolean NOT_PROTECTED = false;
-       
-       @XmlElement(name = "LSID", type = String.class)
-       @XmlJavaTypeAdapter(LSIDAdapter.class)
-       @Embedded
-       private LSID lsid;
-       
-       @XmlElement(name = "TitleCache", required = true)
-       @XmlJavaTypeAdapter(FormattedTextAdapter.class)
-       @Column(length=255, name="titleCache")
-       @Fields({@Field(index = org.hibernate.search.annotations.Index.TOKENIZED),
-                @Field(name = "titleCache_forSort", index = org.hibernate.search.annotations.Index.UN_TOKENIZED)
-       })
-       @FieldBridge(impl=StripHtmlBridge.class)
-       private String titleCache;
-       
-       //if true titleCache will not be automatically generated/updated
-       @XmlElement(name = "ProtectedTitleCache")
-       private boolean protectedTitleCache;
-       
-    @XmlElementWrapper(name = "Rights")
+public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrategy> extends AnnotatableEntity
+        implements IIdentifiableEntity /*, ISourceable<IdentifiableSource> */ {
+    private static final long serialVersionUID = -5610995424730659058L;
+    private static final Logger logger = Logger.getLogger(IdentifiableEntity.class);
+
+    @XmlTransient
+    public static final boolean PROTECTED = true;
+    @XmlTransient
+    public static final boolean NOT_PROTECTED = false;
+
+    @XmlElement(name = "LSID", type = String.class)
+    @XmlJavaTypeAdapter(LSIDAdapter.class)
+    @Embedded
+    private LSID lsid;
+
+    @XmlElement(name = "TitleCache", required = true)
+    @XmlJavaTypeAdapter(FormattedTextAdapter.class)
+    @Column(name="titleCache")
+    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.ALL)
+    @NotEmpty(groups = Level2.class) // implictly NotNull
+    @Size(max = 800)  //see #1592
+    @Fields({
+        @Field(store=Store.YES),
+        @Field(name = "titleCache__sort", analyze = Analyze.NO, store=Store.YES)
+    })
+    @FieldBridge(impl=StripHtmlBridge.class)
+    protected String titleCache;
+
+    //if true titleCache will not be automatically generated/updated
+    @XmlElement(name = "ProtectedTitleCache")
+    protected boolean protectedTitleCache;
+
+    @XmlElementWrapper(name = "Rights", nillable = true)
     @XmlElement(name = "Rights")
-    @OneToMany(fetch = FetchType.LAZY)
-       @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
-       //TODO
-       @Merge(MergeMode.ADD_CLONE)
-       private Set<Rights> rights = new HashSet<Rights>();
-    
-    @XmlElementWrapper(name = "Credits")
+    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
+    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
+    //TODO
+    @Merge(MergeMode.ADD_CLONE)
+    @NotNull
+    private Set<Rights> rights = new HashSet<Rights>();
+
+    @XmlElementWrapper(name = "Credits", nillable = true)
     @XmlElement(name = "Credit")
     @IndexColumn(name="sortIndex", base = 0)
-       @OneToMany(fetch = FetchType.LAZY)
-       @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
+    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
+    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
     //TODO
-       @Merge(MergeMode.ADD_CLONE)
-       private List<Credit> credits = new ArrayList<Credit>();
-       
-    @XmlElementWrapper(name = "Extensions")
+    @Merge(MergeMode.ADD_CLONE)
+    @NotNull
+    private List<Credit> credits = new ArrayList<Credit>();
+
+    @XmlElementWrapper(name = "Extensions", nillable = true)
     @XmlElement(name = "Extension")
-    @OneToMany(fetch = FetchType.LAZY)
-       @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
-       @Merge(MergeMode.ADD_CLONE)
-       private Set<Extension> extensions = new HashSet<Extension>();
-       
-    @XmlElementWrapper(name = "Sources")
-    @XmlElement(name = "OriginalSource")
-    @OneToMany(fetch = FetchType.LAZY)         
-       @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
-       @Merge(MergeMode.ADD_CLONE)
-       private Set<OriginalSource> sources = new HashSet<OriginalSource>();
-    
+    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
+    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
+    @Merge(MergeMode.ADD_CLONE)
+    @NotNull
+    private Set<Extension> extensions = new HashSet<Extension>();
+
+    @XmlElementWrapper(name = "Identifiers", nillable = true)
+    @XmlElement(name = "Identifier")
+    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
+    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
+    @Merge(MergeMode.ADD_CLONE)
+    @NotNull
+    private Set<Identifier> identifiers = new HashSet<Identifier>();
+
+    @XmlElementWrapper(name = "Sources", nillable = true)
+    @XmlElement(name = "IdentifiableSource")
+    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
+    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
+    @Merge(MergeMode.ADD_CLONE)
+    @NotNull
+    private Set<IdentifiableSource> sources = new HashSet<IdentifiableSource>();
+
     @XmlTransient
-       @Transient
-       protected S cacheStrategy;
-       
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getLsid()
-        */
-       public LSID getLsid(){
-               return this.lsid;
-       }
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setLsid(java.lang.String)
-        */
-       public void setLsid(LSID lsid){
-               this.lsid = lsid;
-       }
-
-       /**
-        * By default, we expect most cdm objects to be abstract things 
-        * i.e. unable to return a data representation.
-        * 
-        * Specific subclasses (e.g. Sequence) can override if necessary.
-        */
-       public byte[] getData() {
-               return null;
-       }
-
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getTitleCache()
-        */
-       //@Transient
-       public String getTitleCache(){
-               if (protectedTitleCache){
-                       return this.titleCache;                 
-               }
-               // is title dirty, i.e. equal NULL?
-               if (titleCache == null){
-                       this.setTitleCache(generateTitle(),protectedTitleCache) ; //for truncating
-               }
-               return titleCache;
-       }
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setTitleCache(java.lang.String)
-        */
-       public void setTitleCache(String titleCache){
-               setTitleCache(titleCache, PROTECTED);
-       }
-       
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setTitleCache(java.lang.String, boolean)
-        */
-       public void setTitleCache(String titleCache, boolean protectCache){
-               //TODO truncation of title cache
-               if (titleCache != null && titleCache.length() > 254){
-                       logger.warn("Truncation of title cache: " + this.toString() + "/" + titleCache);
-                       titleCache = titleCache.substring(0, 251) + "...";
-               }
-               this.titleCache = titleCache;
-               this.setProtectedTitleCache(protectCache);
-       }
-       
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getRights()
-        */
-       public Set<Rights> getRights() {
-               return this.rights;             
-       }
-       
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addRights(eu.etaxonomy.cdm.model.media.Rights)
-        */
-       public void addRights(Rights right){
-               this.rights.add(right);
-       }
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeRights(eu.etaxonomy.cdm.model.media.Rights)
-        */
-       public void removeRights(Rights right){
-               this.rights.remove(right);
-       }
-
-       
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getCredits()
-        */
-       public List<Credit> getCredits() {
-               return this.credits;            
-       }
-       
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getCredits(int)
-        */
-       public Credit getCredits(int index){
-               return this.credits.get(index);
-       }
-       
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addCredit(eu.etaxonomy.cdm.model.common.Credit)
-        */
-       public void addCredit(Credit credit){
-               this.credits.add(credit);
-       }
-       
-
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addCredit(eu.etaxonomy.cdm.model.common.Credit, int)
-        */
-       public void addCredit(Credit credit, int index){
-               this.credits.add(index, credit);
-       }
-       
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeCredit(eu.etaxonomy.cdm.model.common.Credit)
-        */
-       public void removeCredit(Credit credit){
-               this.credits.remove(credit);
-       }
-       
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeCredit(int)
-        */
-       public void removeCredit(int index){
-               this.credits.remove(index);
-       }
-       
-       
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getExtensions()
-        */
-       public Set<Extension> getExtensions(){
-               return this.extensions;
-       }
-
-       public void addExtension(String value, ExtensionType extensionType){
-               Extension.NewInstance(this, value, extensionType);
-       }
-       
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addExtension(eu.etaxonomy.cdm.model.common.Extension)
-        */
-       public void addExtension(Extension extension){
-               if (extension != null){
-                       extension.setExtendedObj(this);
-                       this.extensions.add(extension);
-               }
-       }
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeExtension(eu.etaxonomy.cdm.model.common.Extension)
-        */
-       public void removeExtension(Extension extension){
-               if (extension != null){
-                       extension.setExtendedObj(null);
-                       this.extensions.remove(extension);
-               }
-       }
-
-       
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#isProtectedTitleCache()
-        */
-       public boolean isProtectedTitleCache() {
-               return protectedTitleCache;
-       }
-
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setProtectedTitleCache(boolean)
-        */
-       public void setProtectedTitleCache(boolean protectedTitleCache) {
-               this.protectedTitleCache = protectedTitleCache;
-       }
-
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getSources()
-        */
-       public Set<OriginalSource> getSources() {
-               return this.sources;            
-       }
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addSource(eu.etaxonomy.cdm.model.common.OriginalSource)
-        */
-       public void addSource(OriginalSource source) {
-               if (source != null){
-                       IdentifiableEntity oldSourcedObj = source.getSourcedObj();
-                       if (oldSourcedObj != null && oldSourcedObj != this){
-                               oldSourcedObj.getSources().remove(source);
-                       }
-                       this.sources.add(source);
-                       source.setSourcedObj(this);
-               }
-       }
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeSource(eu.etaxonomy.cdm.model.common.OriginalSource)
-        */
-       public void removeSource(OriginalSource source) {
-               this.sources.remove(source);            
-       }
-       
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#toString()
-        */
-        @Override
-       public String toString() {
-               String result;
-               if (titleCache == null){
-                       result = super.toString();
-               }else{
-                       result = this.titleCache;
-               }
-               return result;  
-       }
-        
-        public int compareTo(IdentifiableEntity identifiableEntity) {
-
-                int result = 0;
-                
-                if (identifiableEntity == null) {
-                        throw new NullPointerException("Cannot compare to null.");
-                }
-
-                // First, compare the name cache.
-                // TODO: Avoid using instanceof operator
-                // Use Class.getDeclaredMethod() instead to find out whether class has getNameCache() method?
-
-                String specifiedNameCache = "";
-                String thisNameCache = "";
-                String specifiedTitleCache = "";
-                String thisTitleCache = "";
-                String specifiedReferenceTitleCache = "";
-                String thisReferenceTitleCache = "";   
-                
-                if(identifiableEntity instanceof NonViralName) {
-                        specifiedNameCache = HibernateProxyHelper.deproxy(identifiableEntity, NonViralName.class).getNameCache();
-                        specifiedTitleCache = identifiableEntity.getTitleCache();
-                        
-                } else if(identifiableEntity instanceof TaxonBase) {
-                        TaxonBase taxonBase = HibernateProxyHelper.deproxy(identifiableEntity, TaxonBase.class);
-                        
-                        TaxonNameBase<?,?> taxonNameBase = taxonBase.getName();
-                        specifiedNameCache = HibernateProxyHelper.deproxy(taxonNameBase, NonViralName.class).getNameCache();
-                        specifiedTitleCache = taxonNameBase.getTitleCache();
-                        
-                        //specifiedReferenceTitleCache = ((TaxonBase)identifiableEntity).getSec().getTitleCache();
-//                      ReferenceBase referenceBase = taxonBase.getSec();
-//                      if (referenceBase != null) {
-//           FIXME: HibernateProxyHelper.deproxy(referenceBase, ReferenceBase.class) throws exception
-//                              referenceBase = HibernateProxyHelper.deproxy(referenceBase, ReferenceBase.class);
-//                              specifiedReferenceTitleCache = referenceBase.getTitleCache();
-//                      }
-                }
-                
-                if(this instanceof NonViralName) {
-                        thisNameCache = HibernateProxyHelper.deproxy(this, NonViralName.class).getNameCache();
-                        thisTitleCache = getTitleCache();
-                } else if(this instanceof TaxonBase) {
-                        TaxonNameBase<?,?> taxonNameBase= HibernateProxyHelper.deproxy(this, TaxonBase.class).getName();
-                        thisNameCache = HibernateProxyHelper.deproxy(taxonNameBase, NonViralName.class).getNameCache();
-                        thisTitleCache = taxonNameBase.getTitleCache();
-                        thisReferenceTitleCache = getTitleCache();
-                }
-                
-                // Compare name cache of taxon names
-
-                if (!specifiedNameCache.equals("") && !thisNameCache.equals("")) {
-                        result = thisNameCache.compareTo(specifiedNameCache);
-                }
-                
-                // Compare title cache of taxon names
-                
-                if ((result == 0) && (!specifiedTitleCache.equals("") || !thisTitleCache.equals(""))) {
-                        result = thisTitleCache.compareTo(specifiedTitleCache);
-                }
-                
-                // Compare title cache of taxon references
-                
-                if ((result == 0) && (!specifiedReferenceTitleCache.equals("") || !thisReferenceTitleCache.equals(""))) {
-                        result = thisReferenceTitleCache.compareTo(specifiedReferenceTitleCache);
-                }
-                
-                return result;
-        }
-
-               /**
-                * Returns the {@link eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy cache strategy} used to generate
-                * several strings corresponding to <i>this</i> identifiable entity
-                * (in particular taxon name caches and author strings).
-                * 
-                * @return  the cache strategy used for <i>this</i> identifiable entity
-                * @see     eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
-                */
-               public S getCacheStrategy() {
-                       return this.cacheStrategy;
-               }
-               /** 
-                * @see         #getCacheStrategy()
-                */
-               
-               public void setCacheStrategy(S cacheStrategy) {
-                       this.cacheStrategy = cacheStrategy;
-               }
-               
-               public String generateTitle() {
-                       if (cacheStrategy == null){
-                               logger.warn("No CacheStrategy defined for "+ this.getClass() + ": " + this.getUuid());
-                               return null;
-                       }else{
-                               return cacheStrategy.getTitleCache(this);
-                       }
-               }
-        
+    @Transient
+    protected S cacheStrategy;
+
+    protected IdentifiableEntity(){
+        initListener();
+    }
+
+    protected void initListener(){
+        PropertyChangeListener listener = new PropertyChangeListener() {
+            @Override
+            public void propertyChange(PropertyChangeEvent ev) {
+                if (!ev.getPropertyName().equals("titleCache") && !ev.getPropertyName().equals("cacheStrategy") && ! isProtectedTitleCache()){
+                    titleCache = null;
+                }
+            }
+        };
+        addPropertyChangeListener(listener);
+    }
+
+    /**
+     * By default, we expect most cdm objects to be abstract things
+     * i.e. unable to return a data representation.
+     *
+     * Specific subclasses (e.g. Sequence) can override if necessary.
+     */
+    @Override
+    public byte[] getData() {
+        return null;
+    }
+
+//******************************** CACHE *****************************************************/
+
+    // @Transient  - must not be transient, since this property needs to to be included in all serializations produced by the remote layer
+    @Override
+    public String getTitleCache(){
+        if (protectedTitleCache){
+            return this.titleCache;
+        }
+        // is title dirty, i.e. equal NULL?
+        if (titleCache == null){
+            this.titleCache = generateTitle();
+            this.titleCache = getTruncatedCache(this.titleCache) ;
+        }
+        return titleCache;
+    }
+
+    /**
+     * The titleCache will be regenerated from scratch if not protected
+     * @return <code>true</code> if title cache was regenerated, <code>false</code> otherwise
+     */
+    protected boolean regenerateTitleCache() {
+        if (!protectedTitleCache) {
+            this.titleCache = null;
+            getTitleCache();
+        }
+        return protectedTitleCache;
+    }
+
+    @Deprecated
+    @Override
+    public void setTitleCache(String titleCache){
+       //TODO shouldn't we call setTitleCache(String, boolean),but is this conformant with Java Bean Specification?
+       this.titleCache = getTruncatedCache(titleCache);
+    }
+
+    @Override
+    public void setTitleCache(String titleCache, boolean protectCache){
+        titleCache = getTruncatedCache(titleCache);
+        this.titleCache = titleCache;
+        this.protectedTitleCache = protectCache;
+    }
+
+
+    /**
+     * @param cache
+     * @return
+     */
+    @Transient
+    protected String getTruncatedCache(String cache) {
+        int maxLength = 800;
+       if (cache != null && cache.length() > maxLength){
+            logger.warn("Truncation of cache: " + this.toString() + "/" + cache);
+            cache = cache.substring(0, maxLength - 4) + "...";   //TODO do we need -4 or is -3 enough
+        }
+        return cache;
+    }
+
+//**************************************************************************************
+
+    @Override
+    public LSID getLsid(){
+        return this.lsid;
+    }
+    @Override
+    public void setLsid(LSID lsid){
+        this.lsid = lsid;
+    }
+    @Override
+    public Set<Rights> getRights() {
+        if(rights == null) {
+            this.rights = new HashSet<Rights>();
+        }
+        return this.rights;
+    }
+
+    @Override
+    public void addRights(Rights right){
+        getRights().add(right);
+    }
+    @Override
+    public void removeRights(Rights right){
+        getRights().remove(right);
+    }
+
+
+    @Override
+    public List<Credit> getCredits() {
+        if(credits == null) {
+            this.credits = new ArrayList<Credit>();
+        }
+        return this.credits;
+    }
+
+    @Override
+    public Credit getCredits(Integer index){
+        return getCredits().get(index);
+    }
+
+    @Override
+    public void addCredit(Credit credit){
+        getCredits().add(credit);
+    }
+
+
+    @Override
+    public void addCredit(Credit credit, int index){
+        getCredits().add(index, credit);
+    }
+
+    @Override
+    public void removeCredit(Credit credit){
+        getCredits().remove(credit);
+    }
+
+    @Override
+    public void removeCredit(int index){
+        getCredits().remove(index);
+    }
+
+    @Override
+    public Set<Identifier> getIdentifiers(){
+        if(this.identifiers == null) {
+            this.identifiers = new HashSet<Identifier>();
+        }
+        return this.identifiers;
+    }
+    /**
+     * @param type
+     * @return a Set of extension value strings
+     */
+    public Set<String> getIdentifiers(DefinedTerm type){
+       return getIdentifiers(type.getUuid());
+    }
+    /**
+     * @param extensionTypeUuid
+     * @return a Set of extension value strings
+     */
+    public Set<String> getIdentifiers(UUID identifierTypeUuid){
+        Set<String> result = new HashSet<String>();
+        for (Identifier identifier : getIdentifiers()){
+            if (identifier.getType().getUuid().equals(identifierTypeUuid)){
+                result.add(identifier.getIdentifier());
+            }
+        }
+        return result;
+    }
+
+    public Identifier addIdentifier(String identifier, DefinedTerm identifierType){
+       Identifier result = Identifier.NewInstance(this, identifier, identifierType);
+       return result;
+    }
+
+    @Override
+    public void addIdentifier(Identifier identifier){
+        if (identifier != null){
+               identifier.setIdentifiedObj(this);
+            getIdentifiers().add(identifier);
+        }
+    }
+    @Override
+    public void removeIdentifier(Identifier identifier){
+        if (identifier != null){
+               logger.warn("TODO");
+//             identifier.setExtendedObj(null);
+            getIdentifiers().remove(identifier);
+        }
+    }
+
+    @Override
+    public Set<Extension> getExtensions(){
+        if(extensions == null) {
+            this.extensions = new HashSet<Extension>();
+        }
+        return this.extensions;
+    }
+    /**
+     * @param type
+     * @return a Set of extension value strings
+     */
+    public Set<String> getExtensions(ExtensionType type){
+       return getExtensions(type.getUuid());
+    }
+    /**
+     * @param extensionTypeUuid
+     * @return a Set of extension value strings
+     */
+    public Set<String> getExtensions(UUID extensionTypeUuid){
+        Set<String> result = new HashSet<String>();
+        for (Extension extension : getExtensions()){
+            if (extension.getType().getUuid().equals(extensionTypeUuid)){
+                result.add(extension.getValue());
+            }
+        }
+        return result;
+    }
+
+    public void addExtension(String value, ExtensionType extensionType){
+        Extension.NewInstance(this, value, extensionType);
+    }
+
+    @Override
+    public void addExtension(Extension extension){
+        if (extension != null){
+            extension.setExtendedObj(this);
+            getExtensions().add(extension);
+        }
+    }
+    @Override
+    public void removeExtension(Extension extension){
+        if (extension != null){
+            extension.setExtendedObj(null);
+            getExtensions().remove(extension);
+        }
+    }
+
+    @Override
+    public boolean isProtectedTitleCache() {
+        return protectedTitleCache;
+    }
+
+    @Override
+    public void setProtectedTitleCache(boolean protectedTitleCache) {
+        this.protectedTitleCache = protectedTitleCache;
+    }
+
+    @Override
+    public Set<IdentifiableSource> getSources() {
+        if(sources == null) {
+            this.sources = new HashSet<IdentifiableSource>();
+        }
+        return this.sources;
+    }
+
+    @Override
+    public void addSource(IdentifiableSource source) {
+        if (source != null){
+            IdentifiableEntity<?> oldSourcedObj = source.getSourcedObj();
+            if (oldSourcedObj != null && oldSourcedObj != this){
+                oldSourcedObj.getSources().remove(source);
+            }
+            getSources().add(source);
+            source.setSourcedObj(this);
+        }
+    }
+
+    @Override
+    public void addSources(Set<IdentifiableSource> sources) {
+        if (sources != null){
+               for (IdentifiableSource source: sources){
+                   IdentifiableEntity<?> oldSourcedObj = source.getSourcedObj();
+                   if (oldSourcedObj != null && oldSourcedObj != this){
+                       oldSourcedObj.getSources().remove(source);
+                   }
+                   getSources().add(source);
+                   source.setSourcedObj(this);
+               }
+        }
+    }
+    
+    @Override
+    public void removeSources() {
+       this.sources.clear();
+    }
+    
+    @Override
+    public IdentifiableSource addSource(OriginalSourceType type, String id, String idNamespace, Reference citation, String microCitation) {
+        if (id == null && idNamespace == null && citation == null && microCitation == null){
+            return null;
+        }
+        IdentifiableSource source = IdentifiableSource.NewInstance(type, id, idNamespace, citation, microCitation);
+        addSource(source);
+        return source;
+    }
+
+
+    @Override
+    public IdentifiableSource addImportSource(String id, String idNamespace, Reference<?> citation, String microCitation) {
+        if (id == null && idNamespace == null && citation == null && microCitation == null){
+            return null;
+        }
+        IdentifiableSource source = IdentifiableSource.NewInstance(OriginalSourceType.Import, id, idNamespace, citation, microCitation);
+        addSource(source);
+        return source;
+    }
+
+
+    @Override
+    public void removeSource(IdentifiableSource source) {
+        getSources().remove(source);
+    }
+
+//******************************** TO STRING *****************************************************/
+
+    /* (non-Javadoc)
+     * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#toString()
+     */
+     @Override
+    public String toString() {
+        String result;
+        if (titleCache == null){
+            result = super.toString();
+        }else{
+            result = this.titleCache;
+        }
+        return result;
+    }
+
+
+    public int compareTo(IdentifiableEntity identifiableEntity) {
+
+         int result = 0;
+
+         if (identifiableEntity == null) {
+             throw new NullPointerException("Cannot compare to null.");
+         }
+
+         // First, compare the name cache.
+         // TODO: Avoid using instanceof operator
+         // Use Class.getDeclaredMethod() instead to find out whether class has getNameCache() method?
+
+         String specifiedNameCache = "";
+         String thisNameCache = "";
+         String specifiedTitleCache = "";
+         String thisTitleCache = "";
+         String specifiedReferenceTitleCache = "";
+         String thisReferenceTitleCache = "";
+         String thisGenusString = "";
+         String specifiedGenusString = "";
+         int thisrank_order = 0;
+
+         //TODO we can remove all the deproxies here except for the first one
+         identifiableEntity = HibernateProxyHelper.deproxy(identifiableEntity, IdentifiableEntity.class);
+         if(identifiableEntity instanceof NonViralName) {
+             specifiedNameCache = HibernateProxyHelper.deproxy(identifiableEntity, NonViralName.class).getNameCache();
+             specifiedTitleCache = identifiableEntity.getTitleCache();
+            if (identifiableEntity instanceof BotanicalName){
+                if (((BotanicalName)identifiableEntity).isAutonym()){
+                        boolean isProtected = false;
+                        String oldNameCache = ((BotanicalName) identifiableEntity).getNameCache();
+                        if ( ((BotanicalName)identifiableEntity).isProtectedNameCache()){
+                                isProtected = true;
+                        }
+                        ((BotanicalName)identifiableEntity).setProtectedNameCache(false);
+                        ((BotanicalName)identifiableEntity).setNameCache(null, false);
+                        specifiedNameCache = ((BotanicalName) identifiableEntity).getNameCache();
+                        ((BotanicalName)identifiableEntity).setNameCache(oldNameCache, isProtected);
+
+                }
+             }
+
+         } else if(identifiableEntity instanceof TaxonBase) {
+             TaxonBase taxonBase = HibernateProxyHelper.deproxy(identifiableEntity, TaxonBase.class);
+
+             TaxonNameBase<?,?> taxonNameBase = taxonBase.getName();
+
+
+             NonViralName nonViralName = HibernateProxyHelper.deproxy(taxonNameBase, NonViralName.class);
+             specifiedNameCache = nonViralName.getNameCache();
+             specifiedTitleCache = taxonNameBase.getTitleCache();
+
+             specifiedReferenceTitleCache = ((TaxonBase)identifiableEntity).getSec().getTitleCache();
+             Reference reference = taxonBase.getSec();
+             if (reference != null) {
+                 reference = HibernateProxyHelper.deproxy(reference, Reference.class);
+                 specifiedReferenceTitleCache = reference.getTitleCache();
+             }
+         }
+
+         if(this.isInstanceOf(NonViralName.class)) {
+             thisNameCache = HibernateProxyHelper.deproxy(this, NonViralName.class).getNameCache();
+             thisTitleCache = getTitleCache();
+
+             if (this instanceof BotanicalName){
+                if (((BotanicalName)this).isAutonym()){
+                        boolean isProtected = false;
+                        String oldNameCache = ((BotanicalName) this).getNameCache();
+                        if ( ((BotanicalName)this).isProtectedNameCache()){
+                                isProtected = true;
+                        }
+                        ((BotanicalName)this).setProtectedNameCache(false);
+                        ((BotanicalName)this).setNameCache(null, false);
+                        thisNameCache = ((BotanicalName) this).getNameCache();
+                        ((BotanicalName)this).setNameCache(oldNameCache, isProtected);
+                }
+             }
+         } else if(this.isInstanceOf(TaxonBase.class)) {
+             TaxonNameBase<?,?> taxonNameBase= HibernateProxyHelper.deproxy(this, TaxonBase.class).getName();
+             NonViralName nonViralName = HibernateProxyHelper.deproxy(taxonNameBase, NonViralName.class);
+             thisNameCache = nonViralName.getNameCache();
+             thisTitleCache = taxonNameBase.getTitleCache();
+             thisReferenceTitleCache = ((TaxonBase)this).getSec().getTitleCache();
+             thisGenusString = nonViralName.getGenusOrUninomial();
+         }
+
+         // Compare name cache of taxon names
+
+
+
+         if (!specifiedNameCache.equals("") && !thisNameCache.equals("")) {
+             result = thisNameCache.compareTo(specifiedNameCache);
+         }
+
+         // Compare title cache of taxon names
+
+         if ((result == 0) && (!specifiedTitleCache.equals("") || !thisTitleCache.equals(""))) {
+             result = thisTitleCache.compareTo(specifiedTitleCache);
+         }
+
+         // Compare title cache of taxon references
+
+         if ((result == 0) && (!specifiedReferenceTitleCache.equals("") || !thisReferenceTitleCache.equals(""))) {
+             result = thisReferenceTitleCache.compareTo(specifiedReferenceTitleCache);
+         }
+
+         return result;
+     }
+
+        /**
+         * Returns the {@link eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy cache strategy} used to generate
+         * several strings corresponding to <i>this</i> identifiable entity
+         * (in particular taxon name caches and author strings).
+         *
+         * @return  the cache strategy used for <i>this</i> identifiable entity
+         * @see     eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
+         */
+        public S getCacheStrategy() {
+            return this.cacheStrategy;
+        }
+        /**
+         * @see        #getCacheStrategy()
+         */
+
+        public void setCacheStrategy(S cacheStrategy) {
+            this.cacheStrategy = cacheStrategy;
+        }
+
+        @Override
+        public String generateTitle() {
+            if (getCacheStrategy() == null){
+                //logger.warn("No CacheStrategy defined for "+ this.getClass() + ": " + this.getUuid());
+                return this.getClass() + ": " + this.getUuid();
+            }else{
+                return getCacheStrategy().getTitleCache(this);
+            }
+        }
+
 //****************** CLONE ************************************************/
-        
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.AnnotatableEntity#clone()
-        */
-       @Override
-       public Object clone() throws CloneNotSupportedException{
-               IdentifiableEntity result = (IdentifiableEntity)super.clone();
-               
-               //Extensions
-               result.extensions = new HashSet<Extension>();
-               for (Extension extension : this.extensions ){
-                       Extension newExtension = (Extension)extension.clone();
-                       result.addExtension(newExtension);
-               }
-               
-               //OriginalSources
-               result.sources = new HashSet<OriginalSource>();
-               for (OriginalSource originalSource : this.sources){
-                       OriginalSource newSource = (OriginalSource)originalSource.clone();
-                       result.addSource(newSource);
-               }
-               
-               //Rights
-               result.rights = new HashSet<Rights>();
-        for(Rights rights : this.rights) {
-               result.addRights(rights);
+
+    /* (non-Javadoc)
+     * @see eu.etaxonomy.cdm.model.common.AnnotatableEntity#clone()
+     */
+    @Override
+    public Object clone() throws CloneNotSupportedException{
+        IdentifiableEntity result = (IdentifiableEntity)super.clone();
+
+        //Extensions
+        result.extensions = new HashSet<Extension>();
+        for (Extension extension : getExtensions() ){
+            Extension newExtension = (Extension)extension.clone();
+            result.addExtension(newExtension);
+        }
+
+        //OriginalSources
+        result.sources = new HashSet<IdentifiableSource>();
+        for (IdentifiableSource source : getSources()){
+            IdentifiableSource newSource = (IdentifiableSource)source.clone();
+            result.addSource(newSource);
+        }
+
+        //Rights
+        result.rights = new HashSet<Rights>();
+        for(Rights rights : getRights()) {
+            result.addRights(rights);
         }
-               
-               //result.setLsid(lsid);
-               //result.setTitleCache(titleCache); 
-               //result.setProtectedTitleCache(protectedTitleCache);  //must be after setTitleCache
-               
-               //no changes to: lsid, titleCache, protectedTitleCache
-               
-               //empty titleCache
-               if (! protectedTitleCache){
-                       titleCache = null;
-               }
-               return result;
-       }
-       
+
+
+        //Credits
+        result.credits = new ArrayList<Credit>();
+        for(Credit credit : getCredits()) {
+            result.addCredit(credit);
+        }
+
+        //no changes to: lsid, titleCache, protectedTitleCache
+
+        //empty titleCache
+        if (! protectedTitleCache){
+            result.titleCache = null;
+        }
+
+        result.initListener();
+        return result;
+    }
+
 
 }
\ No newline at end of file