Extension bidirectional
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / common / IdentifiableEntity.java
index 5a64760d218ab17cd0a9dbd0ad54a0e5a58f905c..fdfa98b9247f190c2fb201fb4c04ad2cec833068 100644 (file)
 package eu.etaxonomy.cdm.model.common;
 
 
-import org.apache.log4j.Logger;
-import org.hibernate.annotations.Cascade;
-import org.hibernate.annotations.CascadeType;
-import org.hibernate.annotations.Index;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
+import javax.persistence.Column;
+import javax.persistence.Embedded;
+import javax.persistence.FetchType;
+import javax.persistence.MappedSuperclass;
+import javax.persistence.OneToMany;
+import javax.persistence.Transient;
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
 import javax.xml.bind.annotation.XmlTransient;
 import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 
-import eu.etaxonomy.cdm.model.media.IdentifyableMediaEntity;
-import eu.etaxonomy.cdm.model.media.Media;
-import eu.etaxonomy.cdm.model.media.Rights;
-
-
-import java.util.HashSet;
-import java.util.Set;
+import org.apache.log4j.Logger;
+import org.hibernate.annotations.Cascade;
+import org.hibernate.annotations.CascadeType;
+import org.hibernate.annotations.IndexColumn;
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.FieldBridge;
+import org.hibernate.search.annotations.Fields;
 
-import javax.persistence.*;
+import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
+import eu.etaxonomy.cdm.hibernate.StripHtmlBridge;
+import eu.etaxonomy.cdm.jaxb.FormattedTextAdapter;
+import eu.etaxonomy.cdm.jaxb.LSIDAdapter;
+import eu.etaxonomy.cdm.model.media.Rights;
+import eu.etaxonomy.cdm.model.name.NonViralName;
+import eu.etaxonomy.cdm.model.name.TaxonNameBase;
+import eu.etaxonomy.cdm.model.taxon.TaxonBase;
+import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
 
 /**
  * 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.
@@ -51,21 +66,32 @@ import javax.persistence.*;
     "protectedTitleCache",
     "rights",
     "extensions",
+    "credits",
     "sources"
 })
 @MappedSuperclass
-public abstract class IdentifiableEntity<T extends IdentifiableEntity> extends AnnotatableEntity<T> implements IOriginalSource, IIdentifiableEntitiy<T> {
-       static Logger logger = Logger.getLogger(IdentifiableEntity.class);
+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 final boolean PROTECTED = true;
+       public static final boolean PROTECTED = true;
        @XmlTransient
-       public final boolean NOT_PROTECTED = false;
+       public static final boolean NOT_PROTECTED = false;
        
-       @XmlElement(name = "LSID")
-       private String lsid;
+       @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
@@ -74,146 +100,208 @@ public abstract class IdentifiableEntity<T extends IdentifiableEntity> extends A
        
     @XmlElementWrapper(name = "Rights")
     @XmlElement(name = "Rights")
-       private Set<Rights> rights = getNewRightsSet();
+    @OneToMany(fetch = FetchType.LAZY)
+       @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
+       private Set<Rights> rights = new HashSet<Rights>();
+    
+    @XmlElementWrapper(name = "Credits")
+    @XmlElement(name = "Credit")
+    @IndexColumn(name="sortIndex", base = 0)
+       @OneToMany(fetch = FetchType.LAZY)
+       @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
+    private List<Credit> credits = new ArrayList<Credit>();
        
     @XmlElementWrapper(name = "Extensions")
     @XmlElement(name = "Extension")
-       private Set<Extension> extensions = getNewExtensionSet();
+    @OneToMany(fetch = FetchType.LAZY)
+       @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
+       private Set<Extension> extensions = new HashSet<Extension>();
        
     @XmlElementWrapper(name = "Sources")
     @XmlElement(name = "OriginalSource")
-       private Set<OriginalSource> sources = getNewOriginalSourcesSet();
-
+    @OneToMany(fetch = FetchType.LAZY)         
+       @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
+       private Set<OriginalSource> sources = new HashSet<OriginalSource>();
+    
+    @XmlTransient
+       @Transient
+       protected S cacheStrategy;
        
        /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntitiy#getLsid()
+        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getLsid()
         */
-       public String getLsid(){
+       public LSID getLsid(){
                return this.lsid;
        }
        /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntitiy#setLsid(java.lang.String)
+        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setLsid(java.lang.String)
         */
-       public void setLsid(String lsid){
+       public void setLsid(LSID lsid){
                this.lsid = lsid;
        }
 
-       /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntitiy#generateTitle()
+       /**
+        * 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 abstract String generateTitle();
+       public byte[] getData() {
+               return null;
+       }
 
-       //@Index(name="titleCacheIndex")
        /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntitiy#getTitleCache()
+        * @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.titleCache = generateTitle();
+                       this.setTitleCache(generateTitle(),protectedTitleCache) ; //for truncating
                }
                return titleCache;
        }
        /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntitiy#setTitleCache(java.lang.String)
+        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#setTitleCache(java.lang.String)
         */
        public void setTitleCache(String titleCache){
-               //TODO truncation of title cach
-               if (titleCache != null && titleCache.length() > 254){
-                       logger.warn("Truncation of title cache: " + this.toString());
-                       titleCache = titleCache.substring(0, 252) + "...";
-               }
-               this.titleCache = titleCache;
-               this.setProtectedTitleCache(PROTECTED);
+               setTitleCache(titleCache, PROTECTED);
        }
+       
        /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntitiy#setTitleCache(java.lang.String, boolean)
+        * @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.IIdentifiableEntitiy#getRights()
+        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getRights()
         */
-       @ManyToMany
-       @Cascade({CascadeType.SAVE_UPDATE})
-       public Set<Rights> getRights(){
-               return this.rights;
-       }
-
-       protected void setRights(Set<Rights> rights) {
-               this.rights = rights;
+       public Set<Rights> getRights() {
+               return this.rights;             
        }
+       
        /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntitiy#addRights(eu.etaxonomy.cdm.model.media.Rights)
+        * @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.IIdentifiableEntitiy#removeRights(eu.etaxonomy.cdm.model.media.Rights)
+        * @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.IIdentifiableEntitiy#getExtensions()
+        * @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()
         */
-       @OneToMany//(mappedBy="extendedObj")
-       @Cascade({CascadeType.SAVE_UPDATE})
        public Set<Extension> getExtensions(){
                return this.extensions;
        }
-       protected void setExtensions(Set<Extension> extensions) {
-               this.extensions = extensions;
+
+       public void addExtension(String value, ExtensionType extensionType){
+               Extension.NewInstance(this, value, extensionType);
        }
+       
        /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntitiy#addExtension(eu.etaxonomy.cdm.model.common.Extension)
+        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addExtension(eu.etaxonomy.cdm.model.common.Extension)
         */
        public void addExtension(Extension extension){
-               this.extensions.add(extension);
+               if (extension != null){
+                       extension.setExtendedObj(this);
+                       this.extensions.add(extension);
+               }
        }
        /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntitiy#removeExtension(eu.etaxonomy.cdm.model.common.Extension)
+        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#removeExtension(eu.etaxonomy.cdm.model.common.Extension)
         */
        public void removeExtension(Extension extension){
-               this.extensions.remove(extension);
+               if (extension != null){
+                       extension.setExtendedObj(null);
+                       this.extensions.remove(extension);
+               }
        }
 
        
        /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntitiy#isProtectedTitleCache()
+        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#isProtectedTitleCache()
         */
        public boolean isProtectedTitleCache() {
                return protectedTitleCache;
        }
 
        /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntitiy#setProtectedTitleCache(boolean)
+        * @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.IIdentifiableEntitiy#getSources()
+        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#getSources()
         */
-       @OneToMany //(mappedBy="sourcedObj")            
-       @Cascade({CascadeType.SAVE_UPDATE})
        public Set<OriginalSource> getSources() {
                return this.sources;            
        }
-       protected void setSources(Set<OriginalSource> sources) {
-               this.sources = sources;         
-       }
        /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntitiy#addSource(eu.etaxonomy.cdm.model.common.OriginalSource)
+        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#addSource(eu.etaxonomy.cdm.model.common.OriginalSource)
         */
        public void addSource(OriginalSource source) {
                if (source != null){
@@ -226,14 +314,14 @@ public abstract class IdentifiableEntity<T extends IdentifiableEntity> extends A
                }
        }
        /* (non-Javadoc)
-        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntitiy#removeSource(eu.etaxonomy.cdm.model.common.OriginalSource)
+        * @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.IIdentifiableEntitiy#toString()
+        * @see eu.etaxonomy.cdm.model.common.IIdentifiableEntity#toString()
         */
         @Override
        public String toString() {
@@ -246,56 +334,144 @@ public abstract class IdentifiableEntity<T extends IdentifiableEntity> extends A
                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;
+        }
+        
+        
 //****************** CLONE ************************************************/
         
        /* (non-Javadoc)
         * @see eu.etaxonomy.cdm.model.common.AnnotatableEntity#clone()
         */
+       @Override
        public Object clone() throws CloneNotSupportedException{
                IdentifiableEntity result = (IdentifiableEntity)super.clone();
                
                //Extensions
-               Set<Extension> newExtensions = getNewExtensionSet();
+               result.extensions = new HashSet<Extension>();
                for (Extension extension : this.extensions ){
-                       Extension newExtension = (Extension)extension.clone(this);
-                       newExtensions.add(newExtension);
+                       Extension newExtension = (Extension)extension.clone();
+                       result.addExtension(newExtension);
                }
-               result.setExtensions(newExtensions);
                
                //OriginalSources
-               Set<OriginalSource> newOriginalSources = getNewOriginalSourcesSet();
+               result.sources = new HashSet<OriginalSource>();
                for (OriginalSource originalSource : this.sources){
-                       OriginalSource newSource = (OriginalSource)originalSource.clone(this);
-                       newOriginalSources.add(newSource);      
+                       OriginalSource newSource = (OriginalSource)originalSource.clone();
+                       result.addSource(newSource);
                }
-               result.setSources(newOriginalSources);
                
                //Rights
-               Set<Rights> rights = getNewRightsSet();
-               rights.addAll(this.rights);
-               result.setRights(rights);
+               result.rights = new HashSet<Rights>();
+        for(Rights rights : this.rights) {
+               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;
        }
        
-       @Transient
-       private Set<Extension> getNewExtensionSet(){
-               return new HashSet<Extension>();
+       /**
+        * 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()
+        */
        
-       @Transient
-       private Set<OriginalSource> getNewOriginalSourcesSet(){
-               return new HashSet<OriginalSource>();
+       public void setCacheStrategy(S cacheStrategy) {
+               this.cacheStrategy = cacheStrategy;
        }
        
-       @Transient
-       private Set<Rights> getNewRightsSet(){
-               return new HashSet<Rights>();
+       public String generateTitle() {
+               if (cacheStrategy == null){
+                       logger.warn("No CacheStrategy defined for "+ this.getClass() + ": " + this.getUuid());
+                       return null;
+               }else{
+                       return cacheStrategy.getTitleCache(this);
+               }
        }
-
 }
\ No newline at end of file