remove generics from Identifier class
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / common / IdentifiableEntity.java
index 38914a6abc8f3115bf83afec869e8b1ea03a14ea..05a247e7641735b8e818fcf0f84ab37af926e249 100644 (file)
@@ -13,6 +13,7 @@ package eu.etaxonomy.cdm.model.common;
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -21,12 +22,12 @@ import java.util.UUID;
 import javax.persistence.Column;
 import javax.persistence.Embedded;
 import javax.persistence.FetchType;
+import javax.persistence.ManyToMany;
 import javax.persistence.MappedSuperclass;
 import javax.persistence.OneToMany;
 import javax.persistence.OrderColumn;
 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;
@@ -48,16 +49,18 @@ import org.hibernate.search.annotations.SortableField;
 import org.hibernate.search.annotations.Store;
 import org.hibernate.validator.constraints.NotEmpty;
 
-import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
+import eu.etaxonomy.cdm.common.CdmUtils;
+import eu.etaxonomy.cdm.common.URI;
 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.ExternalLink;
 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.ICdmTarget;
+import eu.etaxonomy.cdm.model.reference.OriginalSourceBase;
+import eu.etaxonomy.cdm.model.reference.OriginalSourceType;
 import eu.etaxonomy.cdm.model.reference.Reference;
-import eu.etaxonomy.cdm.model.taxon.TaxonBase;
+import eu.etaxonomy.cdm.model.term.DefinedTerm;
 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
 import eu.etaxonomy.cdm.strategy.match.Match;
 import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
@@ -75,8 +78,7 @@ import eu.etaxonomy.cdm.validation.Level2;
  * 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
+ * @since 08-Nov-2007 13:06:27
  */
 @XmlAccessorType(XmlAccessType.FIELD)
 @XmlType(name = "IdentifiableEntity", propOrder = {
@@ -86,14 +88,16 @@ import eu.etaxonomy.cdm.validation.Level2;
     "credits",
     "extensions",
     "identifiers",
-    "rights",
-    "sources"
+    "links",
+    "rights"
 })
 @Audited
 @MappedSuperclass
-public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrategy> extends AnnotatableEntity
+public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrategy>
+        extends SourcedEntityBase<IdentifiableSource>
         implements IIdentifiableEntity /*, ISourceable<IdentifiableSource> */ {
-    private static final long serialVersionUID = -5610995424730659058L;
+
+    private static final long serialVersionUID = 7912083412108359559L;
     private static final Logger logger = Logger.getLogger(IdentifiableEntity.class);
 
     @XmlTransient
@@ -108,10 +112,9 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
 
     @XmlElement(name = "TitleCache", required = true)
     @XmlJavaTypeAdapter(FormattedTextAdapter.class)
-    @Column(name="titleCache")
+    @Column(name="titleCache", length=800) //see #1592
     @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.ALL)
-    @NotEmpty(groups = Level2.class) // implictly NotNull
-    @Size(max = 800)  //see #1592
+    @NotEmpty(groups = Level2.class) // implicitly NotNull
     @Fields({
         @Field(store=Store.YES),
         //  If the field is only needed for sorting and nothing else, you may configure it as
@@ -128,12 +131,12 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
 
     @XmlElementWrapper(name = "Rights", nillable = true)
     @XmlElement(name = "Rights")
-    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
-    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
+    @ManyToMany(fetch = FetchType.LAZY /*, orphanRemoval=false*/)  //#5762 M:N now
+    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
     //TODO
     @Merge(MergeMode.ADD_CLONE)
     @NotNull
-    private Set<Rights> rights = new HashSet<Rights>();
+    private Set<Rights> rights = new HashSet<>();
 
     @XmlElementWrapper(name = "Credits", nillable = true)
     @XmlElement(name = "Credit")
@@ -143,7 +146,7 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
     //TODO
     @Merge(MergeMode.ADD_CLONE)
     @NotNull
-    private List<Credit> credits = new ArrayList<Credit>();
+    private List<Credit> credits = new ArrayList<>();
 
     @XmlElementWrapper(name = "Extensions", nillable = true)
     @XmlElement(name = "Extension")
@@ -151,7 +154,7 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
     @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
     @Merge(MergeMode.ADD_CLONE)
     @NotNull
-    private Set<Extension> extensions = new HashSet<Extension>();
+    private Set<Extension> extensions = new HashSet<>();
 
     @XmlElementWrapper(name = "Identifiers", nillable = true)
     @XmlElement(name = "Identifier")
@@ -160,15 +163,14 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
     @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
     @Merge(MergeMode.ADD_CLONE)
     @NotNull
-    private List<Identifier> identifiers = new ArrayList<Identifier>();
+    private List<Identifier> identifiers = new ArrayList<>();
 
-    @XmlElementWrapper(name = "Sources", nillable = true)
-    @XmlElement(name = "IdentifiableSource")
-    @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
+    @XmlElementWrapper(name = "Links", nillable = true)
+    @XmlElement(name = "Link")
+    @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>();
+    private Set<ExternalLink> links = new HashSet<>();
 
     @XmlTransient
     @Transient
@@ -183,7 +185,9 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
         PropertyChangeListener listener = new PropertyChangeListener() {
             @Override
             public void propertyChange(PropertyChangeEvent ev) {
-                if (! "titleCache".equals(ev.getPropertyName()) && !"cacheStrategy".equals(ev.getPropertyName()) && ! isProtectedTitleCache()){
+                if (! "titleCache".equals(ev.getPropertyName())
+                        && !"cacheStrategy".equals(ev.getPropertyName())
+                        && ! isProtectedTitleCache()){
                     titleCache = null;
                 }
             }
@@ -215,6 +219,10 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
             this.titleCache = generateTitle();
             this.titleCache = getTruncatedCache(this.titleCache) ;
         }
+        //removed due to #5849
+//        if(StringUtils.isBlank(titleCache)){
+//            titleCache = this.toString();
+//        }
         return titleCache;
     }
 
@@ -232,6 +240,14 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
         this.protectedTitleCache = protectCache;
     }
 
+    @Override
+    public String resetTitleCache() {
+        if(!protectedTitleCache){
+            titleCache = null;
+        }
+        return getTitleCache();
+    }
+
     /**
      * @param cache
      * @return
@@ -246,6 +262,62 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
         return cache;
     }
 
+
+    @Override
+    public boolean isProtectedTitleCache() {
+        return protectedTitleCache;
+    }
+
+    @Override
+    public void setProtectedTitleCache(boolean protectedTitleCache) {
+        this.protectedTitleCache = protectedTitleCache;
+    }
+
+    /**
+     *
+     * @return true, if the current state of the titleCache (without generating it new)
+     * is <code>null</code> or the empty string. This is primarily meant for internal use.
+     */
+    public boolean hasEmptyTitleCache(){
+        return this.titleCache == null || "".equals(this.titleCache);
+    }
+
+    public boolean updateCaches(){
+        if (this.protectedTitleCache == false){
+            String oldTitleCache = this.titleCache;
+
+            @SuppressWarnings("unchecked")
+            String newTitleCache = getCacheStrategy().getTitleCache(this);
+
+            if ( oldTitleCache == null   || ! oldTitleCache.equals(newTitleCache) ){
+                this.setTitleCache(null, false);
+                String newCache = this.getTitleCache();
+
+                if (newCache == null){
+                    logger.warn("newCache should never be null");
+                }
+                if (oldTitleCache == null){
+                    logger.info("oldTitleCache was illegaly null and has been fixed");
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Updates the caches with the given cache strategy
+     * @param entityCacheStrategy
+     * @return <code>true</code> if some cache was updated, <code>false</code> otherwise
+     */
+    public boolean updateCaches(S entityCacheStrategy){
+        S oldCacheStrategy = this.getCacheStrategy();
+        this.cacheStrategy = entityCacheStrategy != null? entityCacheStrategy : this.getCacheStrategy();
+        boolean result = this.updateCaches();
+        this.cacheStrategy = oldCacheStrategy;
+        return result;
+    }
+
 //**************************************************************************************
 
     @Override
@@ -259,7 +331,7 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
     @Override
     public Set<Rights> getRights() {
         if(rights == null) {
-            this.rights = new HashSet<Rights>();
+            this.rights = new HashSet<>();
         }
         return this.rights;
     }
@@ -274,10 +346,40 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
     }
 
 
+//********************** External Links **********************************************
+
+
+    public Set<ExternalLink> getLinks(){
+        return this.links;
+    }
+    public void setLinks(Set<ExternalLink> links){
+        this.links = links;
+    }
+    public void addLink(ExternalLink link){
+        if (link != null){
+            links.add(link);
+        }
+    }
+    public ExternalLink addLinkWebsite(URI uri, String description, Language descriptionLanguage){
+        ExternalLink link = null;
+        if (uri != null || description != null || descriptionLanguage != null){
+            link = ExternalLink.NewWebSiteInstance(uri, description, descriptionLanguage);
+            links.add(link);
+        }
+        return link;
+    }
+    public void removeLink(ExternalLink link){
+        if(links.contains(link)) {
+            links.remove(link);
+        }
+    }
+
+//********************** CREDITS **********************************************
+
     @Override
     public List<Credit> getCredits() {
         if(credits == null) {
-            this.credits = new ArrayList<Credit>();
+            this.credits = new ArrayList<>();
         }
         return this.credits;
     }
@@ -308,13 +410,16 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
         getCredits().remove(index);
     }
 
-    /* (non-Javadoc)
-     * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getIdentifiers()
-     */
+    @Override
+    public boolean replaceCredit(Credit newObject, Credit oldObject){
+        return replaceInList(this.credits, newObject, oldObject);
+    }
+
+
     @Override
     public List<Identifier> getIdentifiers(){
         if(this.identifiers == null) {
-            this.identifiers = new ArrayList<Identifier>();
+            this.identifiers = new ArrayList<>();
         }
         return this.identifiers;
     }
@@ -323,56 +428,69 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
      * @return a set of identifier value strings
      */
     public Set<String> getIdentifiers(DefinedTerm type){
-       return getIdentifiers(type.getUuid());
+       return getIdentifiers(type == null? null :type.getUuid());
     }
     /**
      * @param identifierTypeUuid
      * @return a set of identifier value strings
      */
     public Set<String> getIdentifiers(UUID identifierTypeUuid){
-        Set<String> result = new HashSet<String>();
+        Set<String> result = new HashSet<>();
         for (Identifier identifier : getIdentifiers()){
-            if (identifier.getType().getUuid().equals(identifierTypeUuid)){
+            if ( (identifier.getType()== null && identifierTypeUuid == null)
+                || (identifier.getType().getUuid().equals(identifierTypeUuid))){
                 result.add(identifier.getIdentifier());
             }
         }
         return result;
     }
 
+    public Set<Identifier> getIdentifiers_(UUID identifierTypeUuid){
+        Set<Identifier> result = new HashSet<>();
+        for (Identifier identifier : getIdentifiers()){
+            if ( (identifier.getType()== null && identifierTypeUuid == null)
+                || (identifier.getType().getUuid().equals(identifierTypeUuid))){
+                result.add(identifier);
+            }
+        }
+        return result;
+    }
+
     @Override
     public Identifier addIdentifier(String identifier, DefinedTerm identifierType){
-       Identifier result = Identifier.NewInstance(this, identifier, identifierType);
+       Identifier result = Identifier.NewInstance(identifier, identifierType);
+       addIdentifier(result);
        return result;
     }
 
-     @Override
-    public void addIdentifier(int index, Identifier identifier){
+    @Override
+    public void addIdentifier(Integer index, Identifier identifier){
         if (identifier != null){
-               if (identifier.getIdentifiedObj() != null && ! identifier.getIdentifiedObj().equals(this)){
-                       identifier.getIdentifiedObj().removeIdentifier(identifier);
-               }
-               identifier.setIdentifiedObj(this);
                //deduplication
                int oldIndex = getIdentifiers().indexOf(identifier);
                if(oldIndex > -1){
                        getIdentifiers().remove(identifier);
-                       if (oldIndex < index){
+                       if (index != null && oldIndex < index){
                                index--;
                        }
                }
-               getIdentifiers().add(index, identifier);
+
+               if (index != null){
+                   getIdentifiers().add(index, identifier);
+               }else{
+                   getIdentifiers().add(identifier);
+               }
         }
     }
 
     @Override
     public void addIdentifier(Identifier identifier){
-        addIdentifier(getIdentifiers().size(), identifier);
+        addIdentifier(null, identifier);
     }
 
     @Override
     public void removeIdentifier(Identifier identifier){
         if (identifier != null){
-               identifier.setIdentifiedObj(null);
             getIdentifiers().remove(identifier);
         }
     }
@@ -381,13 +499,28 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
        getIdentifiers().remove(index);
     }
 
+    @Override
+    public boolean replaceIdentifier(Identifier newObject, Identifier oldObject){
+        return replaceInList(this.identifiers, newObject, oldObject);
+    }
+
+
     @Override
     public Set<Extension> getExtensions(){
         if(extensions == null) {
-            this.extensions = new HashSet<Extension>();
+            this.extensions = new HashSet<>();
         }
         return this.extensions;
     }
+    public Set<Extension> getFilteredExtensions(UUID extensionTypeUuid){
+        Set<Extension> result = new HashSet<>();
+        for (Extension extension : getExtensions()){
+            if (extension.getType() != null && extension.getType().getUuid().equals(extensionTypeUuid)){
+                result.add(extension);
+            }
+        }
+        return result;
+     }
     /**
      * @param type
      * @return a Set of extension value strings
@@ -398,17 +531,75 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
     /**
      * @param extensionTypeUuid
      * @return a Set of extension value strings
+     * @see #hasExtension(UUID, String)
      */
     public Set<String> getExtensions(UUID extensionTypeUuid){
-        Set<String> result = new HashSet<String>();
+        Set<String> result = new HashSet<>();
         for (Extension extension : getExtensions()){
-            if (extension.getType().getUuid().equals(extensionTypeUuid)){
+            if (extension.getType() != null && extension.getType().getUuid().equals(extensionTypeUuid)){
                 result.add(extension.getValue());
             }
         }
         return result;
     }
 
+    /**
+     * @see #getExtensionsConcat(Collection, String)
+     */
+    public String getExtensionsConcat(UUID extensionTypeUuid, String separator){
+        String result = null;
+        for (Extension extension : getExtensions()){
+            if (extension.getType() != null && extension.getType().getUuid().equals(extensionTypeUuid)){
+                result = CdmUtils.concat(separator, result, extension.getValue());
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Return all extensions matching the given extension type as
+     * concatenated string. If extensionTypeUuids is a sorted collection
+     * it is given in the correct order.
+     * @param extensionTypeUuids collection of the extension types to be considered
+     * @param separator the separator for concatenation
+     * @return the concatenated extension string
+     * @see #getExtensionsConcat(Collection, String)
+     */
+    public String getExtensionsConcat(Collection<UUID> extensionTypeUuids, String separator){
+        String result = null;
+        for (UUID uuid : extensionTypeUuids){
+            String extension = getExtensionsConcat(uuid, separator);
+            result = CdmUtils.concat(separator, result, extension);
+        }
+        return result;
+    }
+
+    /**
+     * Has this entity an extension of given type with value 'value'.
+     * If value is <code>null</code> <code>true</code> is returned if
+     * an Extension exists with given type and 'value' is <code>null</code>.
+     * @param extensionTypeUuid
+     * @param value
+     * @see #hasExtension(ExtensionType, String)
+     * @see #getExtensions(UUID)
+     */
+    public boolean hasExtension(UUID extensionTypeUuid, String value) {
+        for (String ext : this.getExtensions(extensionTypeUuid)){
+            if (CdmUtils.nullSafeEqual(ext, value)){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @see #hasExtension(UUID, String)
+     */
+    public boolean hasExtension(ExtensionType extensionType, String value) {
+        return hasExtension(extensionType.getUuid(), value);
+    }
+
+    @Override
     public void addExtension(String value, ExtensionType extensionType){
         Extension.NewInstance(this, value, extensionType);
     }
@@ -416,45 +607,20 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
     @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);
         }
     }
 
@@ -462,57 +628,23 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
     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);
+    protected IdentifiableSource createNewSource(OriginalSourceType type, String idInSource, String idNamespace,
+            Reference reference, String microReference, String originalInfo, ICdmTarget target) {
+        return IdentifiableSource.NewInstance(type, idInSource, idNamespace, reference, microReference, originalInfo, target);
     }
 
 //******************************** TO STRING *****************************************************/
 
-    /* (non-Javadoc)
-     * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#toString()
-     */
-     @Override
+    @Override
     public String toString() {
         String result;
-        if (titleCache == null){
+        if (isBlank(titleCache)){
             result = super.toString();
         }else{
             result = this.titleCache;
@@ -521,130 +653,6 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
     }
 
 
-    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;
-         final String HYBRID_SIGN = "\u00D7";
-         final String QUOT_SIGN = "[\\u02BA\\u0022\\u0022]";
-         //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("")) {
-
-                thisNameCache = thisNameCache.replaceAll(HYBRID_SIGN, "");
-                thisNameCache = thisNameCache.replaceAll(QUOT_SIGN, "");
-
-
-                specifiedNameCache = specifiedNameCache.replaceAll(HYBRID_SIGN, "");
-                specifiedNameCache = specifiedNameCache.replaceAll(QUOT_SIGN, "");
-
-
-             result = thisNameCache.compareTo(specifiedNameCache);
-         }
-
-         // Compare title cache of taxon names
-
-         if ((result == 0) && (!specifiedTitleCache.equals("") || !thisTitleCache.equals(""))) {
-                thisTitleCache = thisTitleCache.replaceAll(HYBRID_SIGN, "");
-                thisTitleCache = thisTitleCache.replaceAll(QUOT_SIGN, "");
-
-                specifiedTitleCache = specifiedTitleCache.replaceAll(HYBRID_SIGN, "");
-                specifiedTitleCache = specifiedTitleCache.replaceAll(QUOT_SIGN, "");
-             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
@@ -654,12 +662,11 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
      * @see     eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy
      */
     public S getCacheStrategy() {
+        if (this.cacheStrategy == null){
+            initDefaultCacheStrategy();
+        }
         return this.cacheStrategy;
     }
-    /**
-     * @see    #getCacheStrategy()
-     */
-
     public void setCacheStrategy(S cacheStrategy) {
         this.cacheStrategy = cacheStrategy;
     }
@@ -674,44 +681,51 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
         }
     }
 
+    /**
+     * Subclasses should implement setting the default cache strategy
+     */
+    protected abstract void initDefaultCacheStrategy();
+
 //****************** CLONE ************************************************/
 
     @Override
-    public Object clone() throws CloneNotSupportedException{
-        IdentifiableEntity<?> result = (IdentifiableEntity<?>)super.clone();
+    public IdentifiableEntity<S> clone() throws CloneNotSupportedException{
+
+        @SuppressWarnings("unchecked")
+        IdentifiableEntity<S> result = (IdentifiableEntity<S>)super.clone();
 
         //Extensions
-        result.extensions = new HashSet<Extension>();
+        result.extensions = new HashSet<>();
         for (Extension extension : getExtensions() ){
-            Extension newExtension = (Extension)extension.clone();
+            Extension newExtension = extension.clone();
             result.addExtension(newExtension);
         }
 
         //Identifier
-        result.identifiers = new ArrayList<Identifier>();
-        for (Identifier<?> identifier : getIdentifiers() ){
-               Identifier<?> newIdentifier = (Identifier<?>)identifier.clone();
+        result.identifiers = new ArrayList<>();
+        for (Identifier identifier : getIdentifiers() ){
+               Identifier newIdentifier = identifier.clone();
             result.addIdentifier(newIdentifier);
         }
 
-        //OriginalSources
-        result.sources = new HashSet<IdentifiableSource>();
-        for (IdentifiableSource source : getSources()){
-            IdentifiableSource newSource = (IdentifiableSource)source.clone();
-            result.addSource(newSource);
+        //Rights  - reusable since #5762
+        result.rights = new HashSet<>();
+        for(Rights right : getRights()) {
+            result.addRights(right);
         }
 
-        //Rights
-        result.rights = new HashSet<Rights>();
-        for(Rights rights : getRights()) {
-            result.addRights(rights);
-        }
-
-
         //Credits
-        result.credits = new ArrayList<Credit>();
+        result.credits = new ArrayList<>();
         for(Credit credit : getCredits()) {
-            result.addCredit(credit);
+            Credit newCredit = credit.clone();
+            result.addCredit(newCredit);
+        }
+
+        //Links
+        result.links = new HashSet<>();
+        for(ExternalLink link : getLinks()) {
+            ExternalLink newLink = link.clone();
+            result.addLink(newLink);
         }
 
         //no changes to: lsid, titleCache, protectedTitleCache
@@ -721,9 +735,7 @@ public abstract class IdentifiableEntity<S extends IIdentifiableEntityCacheStrat
             result.titleCache = null;
         }
 
-        result.initListener();
+        result.initListener();  //TODO why do we need this, isnt't the listener in constructor enough?
         return result;
     }
-
-
 }
\ No newline at end of file