ref #9556 avoiding collection of full derivatives tree in DTOs
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / occurrence / SpecimenOrObservationBase.java
index cb683d5b4c0c6c575869fb3492cb7954f4fdafd0..da3d34d54246d5ff8549beb2601dac79c048215e 100644 (file)
@@ -6,9 +6,10 @@
 * 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.occurrence;
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -17,14 +18,15 @@ import java.util.Set;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.FetchType;
+import javax.persistence.Index;
 import javax.persistence.Inheritance;
 import javax.persistence.InheritanceType;
 import javax.persistence.ManyToMany;
 import javax.persistence.ManyToOne;
 import javax.persistence.MapKeyJoinColumn;
 import javax.persistence.OneToMany;
+import javax.persistence.Table;
 import javax.persistence.Transient;
-import javax.validation.constraints.Min;
 import javax.validation.constraints.NotNull;
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -40,41 +42,55 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.apache.log4j.Logger;
 import org.hibernate.annotations.Cascade;
 import org.hibernate.annotations.CascadeType;
-import org.hibernate.annotations.Index;
-import org.hibernate.annotations.Table;
 import org.hibernate.annotations.Type;
 import org.hibernate.envers.Audited;
 import org.hibernate.search.annotations.Analyze;
-import org.hibernate.search.annotations.ContainedIn;
 import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.FieldBridge;
+import org.hibernate.search.annotations.Fields;
 import org.hibernate.search.annotations.IndexedEmbedded;
-import org.hibernate.search.annotations.NumericField;
-
+import org.hibernate.search.annotations.SortableField;
+import org.hibernate.search.annotations.Store;
+import org.hibernate.search.bridge.builtin.BooleanBridge;
+
+import eu.etaxonomy.cdm.common.URI;
+import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
+import eu.etaxonomy.cdm.hibernate.search.StripHtmlBridge;
+import eu.etaxonomy.cdm.hibernate.search.UriBridge;
+import eu.etaxonomy.cdm.jaxb.FormattedTextAdapter;
 import eu.etaxonomy.cdm.jaxb.MultilanguageTextAdapter;
-import eu.etaxonomy.cdm.model.common.DefinedTerm;
+import eu.etaxonomy.cdm.model.common.IIntextReferenceTarget;
 import eu.etaxonomy.cdm.model.common.IMultiLanguageTextHolder;
 import eu.etaxonomy.cdm.model.common.IPublishable;
 import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
 import eu.etaxonomy.cdm.model.common.Language;
 import eu.etaxonomy.cdm.model.common.LanguageString;
 import eu.etaxonomy.cdm.model.common.MultilanguageText;
-import eu.etaxonomy.cdm.model.common.TermType;
 import eu.etaxonomy.cdm.model.description.DescriptionBase;
+import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
 import eu.etaxonomy.cdm.model.description.IDescribable;
 import eu.etaxonomy.cdm.model.description.SpecimenDescription;
 import eu.etaxonomy.cdm.model.description.TaxonDescription;
 import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
+import eu.etaxonomy.cdm.model.term.DefinedTerm;
+import eu.etaxonomy.cdm.model.term.TermType;
 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
+import eu.etaxonomy.cdm.strategy.match.Match;
+import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
+import eu.etaxonomy.cdm.strategy.match.MatchMode;
 
 /**
  * type figures are observations with at least a figure object in media
  * @author m.doering
- * @created 08-Nov-2007 13:06:41
+ * @since 08-Nov-2007 13:06:41
  */
 @XmlAccessorType(XmlAccessType.FIELD)
 @XmlType(name = "SpecimenOrObservationBase", propOrder = {
     "recordBasis",
+    "identityCache",
+    "protectedIdentityCache",
     "publish",
+    "preferredStableUri",
     "sex",
     "lifeStage",
     "kindOfUnit",
@@ -88,9 +104,12 @@ import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
 @Entity
 @Audited
 @Inheritance(strategy=InheritanceType.SINGLE_TABLE)
-@Table(appliesTo="SpecimenOrObservationBase", indexes = { @Index(name = "specimenOrObservationBaseTitleCacheIndex", columnNames = { "titleCache" }) })
-public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCacheStrategy> extends IdentifiableEntity<S>
-                implements IMultiLanguageTextHolder, IDescribable<DescriptionBase>, IPublishable  {
+@Table(name="SpecimenOrObservationBase", indexes = { @Index(name = "specimenOrObservationBaseTitleCacheIndex", columnList = "titleCache"),
+        @Index(name = "specimenOrObservationBaseIdentityCacheIndex", columnList = "identityCache") })
+public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCacheStrategy<?>>
+                extends IdentifiableEntity<S>
+                implements IMultiLanguageTextHolder, IIntextReferenceTarget, IDescribable<DescriptionBase<S>>, IPublishable  {
+
     private static final long serialVersionUID = 6932680139334408031L;
     private static final Logger logger = Logger.getLogger(SpecimenOrObservationBase.class);
 
@@ -99,7 +118,7 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
      *
      * NOTE: The name of the attribute was chosen against the common naming conventions of the CDM
      * as it is well known in common standards like ABCD and DarwinCore. According to CDM naming
-     * conventions it would specimenOrObservationType.
+     * conventions it would be specimenOrObservationType.
      *
      * @see ABCD: DataSets/DataSet/Units/Unit/RecordBasis
      * @see Darwin Core: http://wiki.tdwg.org/twiki/bin/view/DarwinCore/BasisOfRecord
@@ -108,8 +127,9 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
     @Column(name="recordBasis")
     @NotNull
     @Type(type = "eu.etaxonomy.cdm.hibernate.EnumUserType",
-        parameters = {@org.hibernate.annotations.Parameter(name  = "enumClass", value = "eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType")}
+        parameters = {@org.hibernate.annotations.Parameter(name = "enumClass", value = "eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType")}
     )
+    @Audited
     private SpecimenOrObservationType recordBasis;
 
 
@@ -117,9 +137,8 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
     @XmlElement(name = "Description")
     @OneToMany(mappedBy="describedSpecimenOrObservation", fetch = FetchType.LAZY)
     @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
-    @ContainedIn
     @NotNull
-    private Set<DescriptionBase> descriptions = new HashSet<DescriptionBase>();
+    private Set<DescriptionBase<S>> descriptions = new HashSet<>();
 
 
     @XmlElementWrapper(name = "Determinations")
@@ -128,7 +147,7 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
     @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
     @IndexedEmbedded(depth = 2)
     @NotNull
-    private Set<DeterminationEvent> determinations = new HashSet<DeterminationEvent>();
+    private Set<DeterminationEvent> determinations = new HashSet<>();
 
     @XmlElement(name = "Sex")
     @XmlIDREF
@@ -158,9 +177,17 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
 
     @XmlElement(name = "IndividualCount")
     @Field(analyze = Analyze.NO)
-    @NumericField
-    @Min(0)
-    private Integer individualCount;
+    private String individualCount;
+
+    /**
+     * The preferred stable identifier (URI) as discussed in
+     * {@link  https://dev.e-taxonomy.eu/redmine/issues/5606}
+     */
+    @XmlElement(name = "PreferredStableUri")
+    @Field(analyze = Analyze.NO)
+    @FieldBridge(impl = UriBridge.class)
+    @Type(type="uriUserType")
+    private URI preferredStableUri;
 
     // the verbatim description of this occurrence. Free text usable when no atomised data is available.
     // in conjunction with titleCache which serves as the "citation" string for this object
@@ -171,7 +198,7 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
     @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE, CascadeType.DELETE})
     @IndexedEmbedded
     @NotNull
-    protected Map<Language,LanguageString> definition = new HashMap<Language,LanguageString>();
+    protected Map<Language,LanguageString> definition = new HashMap<>();
 
     // events that created derivedUnits from this unit
     @XmlElementWrapper(name = "DerivationEvents")
@@ -181,17 +208,40 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
     @ManyToMany(fetch=FetchType.LAZY)
     @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE, CascadeType.DELETE})
     @NotNull
-    protected Set<DerivationEvent> derivationEvents = new HashSet<DerivationEvent>();
+    protected Set<DerivationEvent> derivationEvents = new HashSet<>();
 
     @XmlAttribute(name = "publish")
+    @Field(analyze = Analyze.NO)
+    @FieldBridge(impl=BooleanBridge.class)
     private boolean publish = true;
 
+    @XmlElement(name = "IdentityCache", required = false)
+    @XmlJavaTypeAdapter(FormattedTextAdapter.class)
+    @Match(value=MatchMode.CACHE, cacheReplaceMode=ReplaceMode.ALL)
+//    @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
+        //  un-indexed and un-stored, thus avoid unnecessary index growth.
+        @Field(name = "identityCache__sort", analyze = Analyze.NO, store=Store.NO, index = org.hibernate.search.annotations.Index.NO)
+    })
+    @SortableField(forField = "identityCache__sort")
+    @FieldBridge(impl=StripHtmlBridge.class)
+    private String identityCache;
+
+
+    //if true identityCache will not be automatically generated/updated
+    @XmlElement(name = "ProtectedIdentityCache")
+    private boolean protectedIdentityCache;
+
 
 //********************************** CONSTRUCTOR *********************************/
 
-      //for hibernate use only
-      @Deprecated
-      protected SpecimenOrObservationBase(){super();}
+    //for hibernate use only
+    @Deprecated
+    protected SpecimenOrObservationBase(){
+        super();
+    }
 
     protected SpecimenOrObservationBase(SpecimenOrObservationType recordBasis) {
         super();
@@ -201,20 +251,56 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
 
 //************************* GETTER / SETTER ***********************/
 
-    /**
-     * @see #recordBasis
-     * @return
-     */
+    /**@see #recordBasis */
     public SpecimenOrObservationType getRecordBasis() {
         return recordBasis;
     }
+    /**@see #recordBasis */
+    public void setRecordBasis(SpecimenOrObservationType recordBasis) {
+        this.recordBasis = recordBasis;
+    }
+
 
     /**
-     * @see #recordBasis
-     * @param recordBasis
+     * @return the identityCache
      */
-    public void setRecordBasis(SpecimenOrObservationType recordBasis) {
-        this.recordBasis = recordBasis;
+    public String getIdentityCache() {
+        return identityCache;
+    }
+    /**
+     * @Deprecated For special use only.
+     * Use {@link #setIdentityCache(String, boolean)} instead
+     */
+    @Deprecated
+    public void setIdentityCache(String identityCache) {
+        this.identityCache = identityCache;
+    }
+
+    public void setIdentityCache(String identityCache, boolean isProtected) {
+        this.protectedIdentityCache = isProtected;
+        setIdentityCache(identityCache);
+    }
+
+    /**
+     * @return the protectedIdentityCache
+     */
+    public boolean isProtectedIdentityCache() {
+        return protectedIdentityCache;
+    }
+    /**
+     * @param protectedIdentityCache the protectedIdentityCache to set
+     */
+    public void setProtectedIdentityCache(boolean protectedIdentityCache) {
+        this.protectedIdentityCache = protectedIdentityCache;
+    }
+
+    /**@see #preferredStableUri */
+    public URI getPreferredStableUri() {
+        return preferredStableUri;
+    }
+    /**@see #preferredStableUri */
+    public void setPreferredStableUri(URI preferredStableUri) {
+        this.preferredStableUri = preferredStableUri;
     }
 
 
@@ -249,9 +335,9 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
      * @return
      */
     @Override
-    public Set<DescriptionBase> getDescriptions() {
+    public Set<DescriptionBase<S>> getDescriptions() {
         if(descriptions == null) {
-            this.descriptions = new HashSet<DescriptionBase>();
+            this.descriptions = new HashSet<>();
         }
         return this.descriptions;
     }
@@ -273,8 +359,8 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
      */
     @Transient
     public Set<SpecimenDescription> getSpecimenDescriptions(boolean includeImageGallery) {
-        Set<SpecimenDescription> specimenDescriptions = new HashSet<SpecimenDescription>();
-        for (DescriptionBase descriptionBase : getDescriptions()){
+        Set<SpecimenDescription> specimenDescriptions = new HashSet<>();
+        for (DescriptionBase<?> descriptionBase : getDescriptions()){
             if (descriptionBase.isInstanceOf(SpecimenDescription.class)){
                 if (includeImageGallery || descriptionBase.isImageGallery() == false){
                     specimenDescriptions.add(descriptionBase.deproxy(descriptionBase, SpecimenDescription.class));
@@ -292,8 +378,8 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
      */
     @Transient
     public Set<SpecimenDescription> getSpecimenDescriptionImageGallery() {
-        Set<SpecimenDescription> specimenDescriptions = new HashSet<SpecimenDescription>();
-        for (DescriptionBase descriptionBase : getDescriptions()){
+        Set<SpecimenDescription> specimenDescriptions = new HashSet<>();
+        for (DescriptionBase<?> descriptionBase : getDescriptions()){
             if (descriptionBase.isInstanceOf(SpecimenDescription.class)){
                 if (descriptionBase.isImageGallery() == true){
                     specimenDescriptions.add(descriptionBase.deproxy(descriptionBase, SpecimenDescription.class));
@@ -331,7 +417,7 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
 
     public Set<DerivationEvent> getDerivationEvents() {
         if(derivationEvents == null) {
-            this.derivationEvents = new HashSet<DerivationEvent>();
+            this.derivationEvents = new HashSet<>();
         }
         return this.derivationEvents;
     }
@@ -352,7 +438,7 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
 
     public Set<DeterminationEvent> getDeterminations() {
         if(determinations == null) {
-            this.determinations = new HashSet<DeterminationEvent>();
+            this.determinations = new HashSet<>();
         }
         return this.determinations;
     }
@@ -400,11 +486,11 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
         this.kindOfUnit = kindOfUnit;
     }
 
-    public Integer getIndividualCount() {
+    public String getIndividualCount() {
         return individualCount;
     }
 
-    public void setIndividualCount(Integer individualCount) {
+    public void setIndividualCount(String individualCount) {
         this.individualCount = individualCount;
     }
 
@@ -457,49 +543,90 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
     }
 
 
-//******************** CLONE **********************************************/
+    public boolean hasCharacterData() {
+        Set<DescriptionBase<S>> descriptions = this.getDescriptions();
+        for (DescriptionBase<?> descriptionBase : descriptions) {
+            if (descriptionBase.isInstanceOf(SpecimenDescription.class)) {
+                SpecimenDescription specimenDescription = HibernateProxyHelper.deproxy(descriptionBase, SpecimenDescription.class);
+                Set<DescriptionElementBase> elements = specimenDescription.getElements();
+                for (DescriptionElementBase descriptionElementBase : elements) {
+                    if (descriptionElementBase.isCharacterData()){
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
 
-    /* (non-Javadoc)
-     * @see eu.etaxonomy.cdm.model.media.IdentifiableMediaEntity#clone()
-     * @see eu.etaxonomy.cdm.model.common.IdentifiableEntity#clone()
-     * @see java.lang.Object#clone()
+    /**
+     * Returns a list of all description items which
+     * @return
      */
+    @Transient
+    public Collection<DescriptionElementBase> characterData() {
+        Collection<DescriptionElementBase> states = new ArrayList<>();
+        Set<DescriptionBase<S>> descriptions = this.getDescriptions();
+        for (DescriptionBase<?> descriptionBase : descriptions) {
+            if (descriptionBase.isInstanceOf(SpecimenDescription.class)) {
+                SpecimenDescription specimenDescription = HibernateProxyHelper.deproxy(descriptionBase, SpecimenDescription.class);
+                Set<DescriptionElementBase> elements = specimenDescription.getElements();
+                for (DescriptionElementBase descriptionElementBase : elements) {
+                    if(descriptionElementBase.isCharacterData()){
+                        states.add(descriptionElementBase);
+                    }
+                }
+            }
+        }
+        return states;
+    }
+
+    public Collection<DerivedUnit> collectDerivedUnits(boolean addFullSubTree) {
+        Collection<DerivedUnit> derivedUnits = new ArrayList<>();
+        for (DerivationEvent derivationEvent : getDerivationEvents()) {
+            for (DerivedUnit derivative : derivationEvent.getDerivatives()) {
+                derivedUnits.add(derivative);
+                if(addFullSubTree) {
+                    derivedUnits.addAll(derivative.collectDerivedUnits(false));
+                }
+            }
+        }
+        return derivedUnits;
+    }
+
+//******************** CLONE **********************************************/
+
     @Override
-    public Object clone() throws CloneNotSupportedException {
-        SpecimenOrObservationBase result = null;
-        result = (SpecimenOrObservationBase)super.clone();
+    public SpecimenOrObservationBase<S> clone() throws CloneNotSupportedException {
+        SpecimenOrObservationBase<S> result = (SpecimenOrObservationBase<S>)super.clone();
 
         //defininion (description, languageString)
-        result.definition = new HashMap<Language,LanguageString>();
-        for(LanguageString languageString : this.definition.values()) {
-            LanguageString newLanguageString = (LanguageString)languageString.clone();
-            result.putDefinition(newLanguageString);
-        }
+        result.definition = cloneLanguageString(this.definition);
 
         //sex
         result.setSex(this.sex);
         //life stage
         result.setLifeStage(this.lifeStage);
 
+        result.descriptions = new HashSet<>();
         //Descriptions
-        for(DescriptionBase description : this.descriptions) {
-            result.addDescription(description);
+        for(DescriptionBase<S> description : this.descriptions) {
+            result.addDescription(description.clone());
         }
 
-        //DeterminationEvent FIXME should clone() the determination
-        // as the relationship is OneToMany
+        result.determinations = new HashSet<>();
         for(DeterminationEvent determination : this.determinations) {
-            result.addDetermination(determination);
+            result.addDetermination(determination.clone());
         }
 
+        result.derivationEvents = new HashSet<>();
         //DerivationEvent
         for(DerivationEvent derivationEvent : this.derivationEvents) {
+            //TODO should we clone this?
             result.addDerivationEvent(derivationEvent);
         }
 
         //no changes to: individualCount
         return result;
     }
-
-
-}
\ No newline at end of file
+}