javadoc
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / occurrence / SpecimenOrObservationBase.java
index c665fef6dc5c0dfade861d7f8bd20cf5edeab047..a0a7dcb2fd39752d7f6bb6f79e1e6e745b08a71b 100644 (file)
@@ -1,32 +1,34 @@
 /**
 * 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.occurrence;
 
-import java.lang.reflect.Method;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
+import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.FetchType;
 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.Transient;
 import javax.validation.constraints.Min;
 import javax.validation.constraints.NotNull;
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
 import javax.xml.bind.annotation.XmlIDREF;
@@ -40,33 +42,42 @@ 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.IndexedEmbedded;
-import org.springframework.util.ReflectionUtils;
+import org.hibernate.search.annotations.NumericField;
 
 import eu.etaxonomy.cdm.jaxb.MultilanguageTextAdapter;
+import eu.etaxonomy.cdm.model.common.DefinedTerm;
+import eu.etaxonomy.cdm.model.common.IMultiLanguageTextHolder;
+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.Sex;
 import eu.etaxonomy.cdm.model.description.SpecimenDescription;
-import eu.etaxonomy.cdm.model.description.Stage;
-import eu.etaxonomy.cdm.model.media.IdentifiableMediaEntity;
+import eu.etaxonomy.cdm.model.description.TaxonDescription;
+import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
 
 /**
  * type figures are observations with at least a figure object in media
  * @author m.doering
- * @version 1.0
  * @created 08-Nov-2007 13:06:41
  */
 @XmlAccessorType(XmlAccessType.FIELD)
 @XmlType(name = "SpecimenOrObservationBase", propOrder = {
+       "recordBasis",
+       "publish",
        "sex",
-    "individualCount",
-    "lifeStage",
-    "description",
+       "lifeStage",
+    "kindOfUnit",
+       "individualCount",
+    "definition",
     "descriptions",
     "determinations",
     "derivationEvents"
@@ -76,68 +87,162 @@ import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
 @Audited
 @Inheritance(strategy=InheritanceType.SINGLE_TABLE)
 @Table(appliesTo="SpecimenOrObservationBase", indexes = { @Index(name = "specimenOrObservationBaseTitleCacheIndex", columnNames = { "titleCache" }) })
-public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCacheStrategy> extends IdentifiableMediaEntity<S> {
-       
+public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCacheStrategy> extends IdentifiableEntity<S> implements IMultiLanguageTextHolder{
+       private static final long serialVersionUID = 6932680139334408031L;
        private static final Logger logger = Logger.getLogger(SpecimenOrObservationBase.class);
+
+       /**
+        * An indication of what the unit record describes.
+        * 
+        * 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. 
+        * 
+        * @see ABCD: DataSets/DataSet/Units/Unit/RecordBasis
+        * @see Darwin Core: http://wiki.tdwg.org/twiki/bin/view/DarwinCore/BasisOfRecord 
+        */
+       @XmlAttribute(name ="RecordBasis")
+       @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")}
+    )
+       private SpecimenOrObservationType recordBasis;
+
        
        @XmlElementWrapper(name = "Descriptions")
        @XmlElement(name = "Description")
-       @ManyToMany(fetch = FetchType.LAZY,mappedBy="describedSpecimenOrObservations",targetEntity=DescriptionBase.class)
+       @OneToMany(mappedBy="describedSpecimenOrObservation", fetch = FetchType.LAZY)
        @Cascade(CascadeType.SAVE_UPDATE)
-       @NotNull
+    @ContainedIn
+    @NotNull
        private Set<DescriptionBase> descriptions = new HashSet<DescriptionBase>();
        
+       
        @XmlElementWrapper(name = "Determinations")
        @XmlElement(name = "Determination")
-       @OneToMany(mappedBy="identifiedUnit")
-       @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
+       @OneToMany(mappedBy="identifiedUnit", orphanRemoval=true)
+       @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
        @IndexedEmbedded(depth = 2)
        @NotNull
        private Set<DeterminationEvent> determinations = new HashSet<DeterminationEvent>();
-       
+
        @XmlElement(name = "Sex")
        @XmlIDREF
        @XmlSchemaType(name = "IDREF")
        @ManyToOne(fetch = FetchType.LAZY)
-       private Sex sex;
-       
+       private DefinedTerm sex;
+
        @XmlElement(name = "LifeStage")
        @XmlIDREF
        @XmlSchemaType(name = "IDREF")
        @ManyToOne(fetch = FetchType.LAZY)
-       private Stage lifeStage;
+       private DefinedTerm lifeStage;
        
+       /**
+        * Part(s) of organism or class of materials represented by this unit.
+        * Example: fruits, seeds, tissue, gDNA, leaves
+        * 
+        * @see ABCD: DataSets/DataSet/Units/Unit/KindOfUnit
+        * @see TermType#KindOfUnit
+        */
+       @XmlElement(name = "KindOfUnit")
+    @XmlIDREF
+    @XmlSchemaType(name = "IDREF")
+    @ManyToOne(fetch = FetchType.LAZY)
+//    @IndexedEmbedded(depth=1)
+       private DefinedTerm kindOfUnit;
+
        @XmlElement(name = "IndividualCount")
-       @Field(index=org.hibernate.search.annotations.Index.UN_TOKENIZED)
+       @Field(analyze = Analyze.NO)
+       @NumericField
        @Min(0)
        private Integer individualCount;
-       
+
        // 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
        @XmlElement(name = "Description")
        @XmlJavaTypeAdapter(MultilanguageTextAdapter.class)
-       @OneToMany(fetch = FetchType.LAZY)
-       @Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE, CascadeType.DELETE_ORPHAN})
+       @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
+       @MapKeyJoinColumn(name="definition_mapkey_id")
+    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE})
        @IndexedEmbedded
        @NotNull
-       protected Map<Language,LanguageString> description = new HashMap<Language,LanguageString>();
-       
+       protected Map<Language,LanguageString> definition = new HashMap<Language,LanguageString>();
+
        // events that created derivedUnits from this unit
        @XmlElementWrapper(name = "DerivationEvents")
        @XmlElement(name = "DerivationEvent")
     @XmlIDREF
     @XmlSchemaType(name = "IDREF")
     @ManyToMany(fetch=FetchType.LAZY)
+    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE})
     @NotNull
        protected Set<DerivationEvent> derivationEvents = new HashSet<DerivationEvent>();
+    
+    @XmlAttribute(name = "publish")
+    private boolean publish = true;
+
+
+//********************************** CONSTRUCTOR *********************************/    
+
+       //for hibernate use only
+       @Deprecated
+       protected SpecimenOrObservationBase(){super();}
+
+       protected SpecimenOrObservationBase(SpecimenOrObservationType recordBasis) {
+               super();
+               if (recordBasis == null){ throw new IllegalArgumentException("RecordBasis must not be null");}
+               this.recordBasis = recordBasis;
+       }
+       
+//************************* GETTER / SETTER ***********************/   
 
        /**
-        * Constructor
+        * @see #recordBasis
+        * @return
         */
-       protected SpecimenOrObservationBase(){
-               super();
+       public SpecimenOrObservationType getRecordBasis() {
+               return recordBasis;
+       }
+
+       /**
+     * @see #recordBasis
+        * @param recordBasis
+        */
+       public void setRecordBasis(SpecimenOrObservationType recordBasis) {
+               this.recordBasis = recordBasis;
        }
        
+
+       /**
+        * Returns the boolean value indicating if this specimen or observation should be withheld 
+        * (<code>publish=false</code>) or not (<code>publish=true</code>) during any publication
+        * process to the general public.
+        * This publish flag implementation is preliminary and may be replaced by a more general 
+        * implementation of READ rights in future.<BR>
+        * The default value is <code>true</code>.
+        */
+       public boolean isPublish() {
+               return publish;
+       }
+
+       /**
+        * @see #isPublish()
+        * @param publish
+        */
+       public void setPublish(boolean publish) {
+               this.publish = publish;
+       }
+       
+       /**
+        * The descriptions this specimen or observation is part of.<BR>
+        * A specimen can not only have it's own {@link SpecimenDescription specimen description }
+        * but can also be part of a {@link TaxonDescription taxon description} or a
+        * {@link TaxonNameDescription taxon name description}.<BR>
+        * @see #getSpecimenDescriptions()
+        * @return
+        */
        public Set<DescriptionBase> getDescriptions() {
                if(descriptions == null) {
                        this.descriptions = new HashSet<DescriptionBase>();
@@ -145,52 +250,95 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
                return this.descriptions;
        }
 
+       /**
+        * Returns the {@link SpecimenDescription specimen descriptions} this specimen is part of.
+        * @see #getDescriptions()
+        * @return
+        */
+       @Transient
+       public Set<SpecimenDescription> getSpecimenDescriptions() {
+               return getSpecimenDescriptions(true);
+       }
+
+       /**
+        * Returns the {@link SpecimenDescription specimen descriptions} this specimen is part of.
+        * @see #getDescriptions()
+        * @return
+        */
+       @Transient
+       public Set<SpecimenDescription> getSpecimenDescriptions(boolean includeImageGallery) {
+               Set<SpecimenDescription> specimenDescriptions = new HashSet<SpecimenDescription>();
+               for (DescriptionBase descriptionBase : getDescriptions()){
+                       if (descriptionBase.isInstanceOf(SpecimenDescription.class)){
+                               if (includeImageGallery || descriptionBase.isImageGallery() == false){
+                                       specimenDescriptions.add(descriptionBase.deproxy(descriptionBase, SpecimenDescription.class));
+                               }
+
+                       }
+               }
+               return specimenDescriptions;
+       }
+       /**
+        * Returns the {@link SpecimenDescription specimen descriptions} which act as an image gallery
+        * and which this specimen is part of.
+        * @see #getDescriptions()
+        * @return
+        */
+       @Transient
+       public Set<SpecimenDescription> getSpecimenDescriptionImageGallery() {
+               Set<SpecimenDescription> specimenDescriptions = new HashSet<SpecimenDescription>();
+               for (DescriptionBase descriptionBase : getDescriptions()){
+                       if (descriptionBase.isInstanceOf(SpecimenDescription.class)){
+                               if (descriptionBase.isImageGallery() == true){
+                                       specimenDescriptions.add(descriptionBase.deproxy(descriptionBase, SpecimenDescription.class));
+                               }
+                       }
+               }
+               return specimenDescriptions;
+       }
+
+       
        /**
         * Adds a new description to this specimen or observation
         * @param description
         */
        public void addDescription(DescriptionBase description) {
-               this.descriptions.add(description);
-               if (! description.getDescribedSpecimenOrObservations().contains(this)){
-                       description.addDescribedSpecimenOrObservation(this);
+               if (description.getDescribedSpecimenOrObservation() != null){
+                       description.getDescribedSpecimenOrObservation().removeDescription(description);
                }
-//             Method method = ReflectionUtils.findMethod(SpecimenDescription.class, "addDescribedSpecimenOrObservation", new Class[] {SpecimenOrObservationBase.class});
-//             ReflectionUtils.makeAccessible(method);
-//             ReflectionUtils.invokeMethod(method, description, new Object[] {this});
+               descriptions.add(description);
+               description.setDescribedSpecimenOrObservation(this);
        }
-       
+
        /**
         * Removes a specimen from a description (removes a description from this specimen)
         * @param description
         */
        public void removeDescription(DescriptionBase description) {
-               this.descriptions.remove(description);
-               if (description.getDescribedSpecimenOrObservations().contains(this)){
-                       description.removeDescribedSpecimenOrObservation(this);
-               }
-//             Method method = ReflectionUtils.findMethod(SpecimenDescription.class, "removeDescribedSpecimenOrObservations", new Class[] {SpecimenOrObservationBase.class});
-//             ReflectionUtils.makeAccessible(method);
-//             ReflectionUtils.invokeMethod(method, description, new Object[] {this});
+        boolean existed = descriptions.remove(description);
+        if (existed){
+               description.setDescribedSpecimenOrObservation(null);
+        }
        }
-       
+
        public Set<DerivationEvent> getDerivationEvents() {
                if(derivationEvents == null) {
                        this.derivationEvents = new HashSet<DerivationEvent>();
                }
                return this.derivationEvents;
        }
-       
+
        public void addDerivationEvent(DerivationEvent derivationEvent) {
                if (! this.derivationEvents.contains(derivationEvent)){
                        this.derivationEvents.add(derivationEvent);
                        derivationEvent.addOriginal(this);
                }
        }
-       
+
        public void removeDerivationEvent(DerivationEvent derivationEvent) {
                this.derivationEvents.remove(derivationEvent);
        }
-       
+
        public Set<DeterminationEvent> getDeterminations() {
                if(determinations == null) {
                        this.determinations = new HashSet<DeterminationEvent>();
@@ -202,34 +350,50 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
                // FIXME bidirectional integrity. Use protected Determination setter
                this.determinations.add(determination);
        }
-       
+
        public void removeDetermination(DeterminationEvent determination) {
                // FIXME bidirectional integrity. Use protected Determination setter
                this.determinations.remove(determination);
        }
-       
-       public Sex getSex() {
+
+       public DefinedTerm getSex() {
                return sex;
        }
 
-       public void setSex(Sex sex) {
+       public void setSex(DefinedTerm sex) {
                this.sex = sex;
        }
 
-       public Stage getLifeStage() {
+       public DefinedTerm getLifeStage() {
                return lifeStage;
        }
 
-       public void setLifeStage(Stage lifeStage) {
+       public void setLifeStage(DefinedTerm lifeStage) {
                this.lifeStage = lifeStage;
        }
        
+
+       /**
+        * @see #kindOfUnit
+        * @return
+        */
+       public DefinedTerm getKindOfUnit() {
+               return kindOfUnit;
+       }
+
+       /**
+        * @see #kindOfUnit
+        * @param kindOfUnit
+        */
+       public void setKindOfUnit(DefinedTerm kindOfUnit) {
+               this.kindOfUnit = kindOfUnit;
+       }
+
        @Override
        public String generateTitle(){
-               logger.warn("Generate title for specimen not yet implemented");
-               return "";
+               return getCacheStrategy().getTitleCache(this);
        }
-       
+
        public Integer getIndividualCount() {
                return individualCount;
        }
@@ -239,20 +403,42 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
        }
 
        public Map<Language,LanguageString> getDefinition(){
-               return this.description;
+               return this.definition;
        }
-       
-       public void addDefinition(LanguageString description){
-               this.description.put(description.getLanguage(),description);
+
+       /**
+        * adds the {@link LanguageString description} to the {@link MultilanguageText multilanguage text}
+        * used to define <i>this</i> specimen or observation.
+        *
+        * @param description   the languageString in with the title string and the given language
+        *
+        * @see                         #getDefinition()
+        * @see                         #putDefinition(Language, String)
+        */
+       public void putDefinition(LanguageString description){
+               this.definition.put(description.getLanguage(),description);
        }
-       
-       public void addDefinition(String text, Language language){
-               this.description.put(language, LanguageString.NewInstance(text, language));
+
+       /**
+        * Creates a {@link LanguageString language string} based on the given text string
+        * and the given {@link Language language} and adds it to the {@link MultilanguageText multilanguage text}
+        * used to define <i>this</i> specimen or observation.
+        *
+        * @param language      the language in which the title string is formulated
+        * @param text          the definition in a particular language
+        *
+        * @see                         #getDefinition()
+        * @see                         #putDefinition(LanguageString)
+        */
+       public void putDefinition(Language language, String text){
+               this.definition.put(language, LanguageString.NewInstance(text, language));
        }
+
+
        public void removeDefinition(Language lang){
-               this.description.remove(lang);
+               this.definition.remove(lang);
        }
-       
+
        /**
         * for derived units get the single next higher parental/original unit.
         * If multiple original units exist throw error
@@ -260,15 +446,13 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
         */
        @Transient
        public SpecimenOrObservationBase getOriginalUnit(){
+               logger.warn("GetOriginalUnit not yet implemented");
                return null;
        }
 
-       @Transient
-       public abstract GatheringEvent getGatheringEvent();
-       
-       
+
 //******************** CLONE **********************************************/
-       
+
        /* (non-Javadoc)
         * @see eu.etaxonomy.cdm.model.media.IdentifiableMediaEntity#clone()
         * @see eu.etaxonomy.cdm.model.common.IdentifiableEntity#clone()
@@ -278,36 +462,38 @@ public abstract class SpecimenOrObservationBase<S extends IIdentifiableEntityCac
        public Object clone() throws CloneNotSupportedException {
                SpecimenOrObservationBase result = null;
                result = (SpecimenOrObservationBase)super.clone();
-               
+
                //defininion (description, languageString)
-               result.description = new HashMap<Language,LanguageString>();
-               for(LanguageString languageString : this.description.values()) {
+               result.definition = new HashMap<Language,LanguageString>();
+               for(LanguageString languageString : this.definition.values()) {
                        LanguageString newLanguageString = (LanguageString)languageString.clone();
-                       result.addDefinition(newLanguageString);
-               } 
+                       result.putDefinition(newLanguageString);
+               }
 
                //sex
                result.setSex(this.sex);
                //life stage
                result.setLifeStage(this.lifeStage);
-               
+
                //Descriptions
                for(DescriptionBase description : this.descriptions) {
-                       result.addDescription((SpecimenDescription)description);
+                       result.addDescription(description);
                }
-               
+
                //DeterminationEvent FIXME should clone() the determination
                // as the relationship is OneToMany
                for(DeterminationEvent determination : this.determinations) {
                        result.addDetermination(determination);
                }
-               
+
                //DerivationEvent
                for(DerivationEvent derivationEvent : this.derivationEvents) {
                        result.addDerivationEvent(derivationEvent);
                }
-               
+
                //no changes to: individualCount
                return result;
        }
+
+
 }
\ No newline at end of file