move children up to parent in code
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / taxon / TaxonNode.java
index e5d112689a22773a2c4542ffb53b864991b9155e..8d8544aab996fbd3ed64673925bf30cae765f195 100644 (file)
@@ -6,7 +6,6 @@
 * 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.taxon;
 
 import java.util.ArrayList;
@@ -21,10 +20,9 @@ import javax.persistence.Entity;
 import javax.persistence.FetchType;
 import javax.persistence.JoinTable;
 import javax.persistence.ManyToOne;
-import javax.persistence.MapKeyJoinColumn;
 import javax.persistence.OneToMany;
-import javax.persistence.OrderBy;
 import javax.persistence.OrderColumn;
+import javax.persistence.Table;
 import javax.persistence.Transient;
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -37,30 +35,35 @@ import javax.xml.bind.annotation.XmlSchemaType;
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 
-import org.apache.log4j.Logger;
-import org.hibernate.LazyInitializationException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.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.Parameter;
+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.Index;
 import org.hibernate.search.annotations.IndexedEmbedded;
+import org.hibernate.search.annotations.Store;
 
-import eu.etaxonomy.cdm.hibernate.HHH_9751_Util;
+import eu.etaxonomy.cdm.common.CdmUtils;
 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
 import eu.etaxonomy.cdm.jaxb.MultilanguageTextAdapter;
 import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
-import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
 import eu.etaxonomy.cdm.model.common.CdmBase;
-import eu.etaxonomy.cdm.model.common.DefinedTerm;
 import eu.etaxonomy.cdm.model.common.ITreeNode;
 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.SingleSourcedEntityBase;
 import eu.etaxonomy.cdm.model.name.Rank;
 import eu.etaxonomy.cdm.model.name.TaxonName;
+import eu.etaxonomy.cdm.model.reference.NamedSource;
 import eu.etaxonomy.cdm.model.reference.Reference;
+import eu.etaxonomy.cdm.model.term.DefinedTerm;
 import eu.etaxonomy.cdm.validation.Level3;
 import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustBeLowerRankThanParent;
 import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustDeriveNameFromParent;
@@ -68,7 +71,7 @@ import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustNotSkipRanks;
 
 /**
  * @author a.mueller
- * @created 31.03.2009
+ * @since 31.03.2009
  */
 @XmlAccessorType(XmlAccessType.FIELD)
 @XmlType(name = "TaxonNode", propOrder = {
@@ -76,27 +79,27 @@ import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustNotSkipRanks;
     "taxon",
     "parent",
     "treeIndex",
-    "sortIndex",
     "childNodes",
-    "referenceForParentChildRelation",
-    "microReferenceForParentChildRelation",
+    "sortIndex",
     "countChildren",
     "agentRelations",
     "synonymToBeUsed",
-    "excludedNote"
+    "status",
+    "statusNote"
 })
 @XmlRootElement(name = "TaxonNode")
 @Entity
-//@Indexed disabled to reduce clutter in indexes, since this type is not used by any search
-//@Indexed(index = "eu.etaxonomy.cdm.model.taxon.TaxonNode")
 @Audited
-@Table(appliesTo="TaxonNode", indexes = { @Index(name = "taxonNodeTreeIndex", columnNames = { "treeIndex" }) })
+@Table(name="TaxonNode", indexes = { @javax.persistence.Index(name = "taxonNodeTreeIndex", columnList = "treeIndex") })
 @ChildTaxaMustBeLowerRankThanParent(groups = Level3.class)
 @ChildTaxaMustNotSkipRanks(groups = Level3.class)
 @ChildTaxaMustDeriveNameFromParent(groups = Level3.class)
-public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITreeNode<TaxonNode>, Cloneable{
+public class TaxonNode
+            extends SingleSourcedEntityBase
+            implements ITaxonTreeNode, ITreeNode<TaxonNode>{
+
     private static final long serialVersionUID = -4743289894926587693L;
-    private static final Logger logger = Logger.getLogger(TaxonNode.class);
+    private static final Logger logger = LogManager.getLogger(TaxonNode.class);
 
     @XmlElement(name = "taxon")
     @XmlIDREF
@@ -106,20 +109,34 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
     @ContainedIn
     private Taxon taxon;
 
-
     @XmlElement(name = "parent")
     @XmlIDREF
     @XmlSchemaType(name = "IDREF")
     @ManyToOne(fetch = FetchType.LAZY)
-    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
+//    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
     private TaxonNode parent;
 
+    @XmlElementWrapper(name = "childNodes")
+    @XmlElement(name = "childNode")
+    @XmlIDREF
+    @XmlSchemaType(name = "IDREF")
+    //see https://dev.e-taxonomy.eu/redmine/issues/3722
+    //see https://dev.e-taxonomy.eu/redmine/issues/4200
+    //see https://dev.e-taxonomy.eu/redmine/issues/8127
+    //see https://dev.e-taxonomy.eu/redmine/issues/10067
+    @OrderColumn(name="sortIndex", nullable=true)
+    @OneToMany(mappedBy="parent", fetch=FetchType.LAZY)
+    //do not cascade
+    private List<TaxonNode> childNodes = new ArrayList<>();
+
+    @XmlElement(name = "countChildren")
+    private int countChildren;
 
     @XmlElement(name = "treeIndex")
     @Column(length=255)
+    @Field(store = Store.YES, index = Index.YES, analyze = Analyze.NO)
     private String treeIndex;
 
-
     @XmlElement(name = "classification")
     @XmlIDREF
     @XmlSchemaType(name = "IDREF")
@@ -129,34 +146,6 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
     @IndexedEmbedded(includeEmbeddedObjectId=true)
     private Classification classification;
 
-    @XmlElementWrapper(name = "childNodes")
-    @XmlElement(name = "childNode")
-    @XmlIDREF
-    @XmlSchemaType(name = "IDREF")
-    //see https://dev.e-taxonomy.eu/trac/ticket/3722
-    @OrderColumn(name="sortIndex")
-    @OrderBy("sortIndex")
-    @OneToMany(mappedBy="parent", fetch=FetchType.LAZY)
-    //@Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE})
-    private List<TaxonNode> childNodes = new ArrayList<>();
-
-    //see https://dev.e-taxonomy.eu/trac/ticket/3722
-    //see https://dev.e-taxonomy.eu/trac/ticket/4200
-    private Integer sortIndex = -1;
-
-       @XmlElement(name = "reference")
-    @XmlIDREF
-    @XmlSchemaType(name = "IDREF")
-    @ManyToOne(fetch = FetchType.LAZY)
-    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
-    private Reference referenceForParentChildRelation;
-
-    @XmlElement(name = "microReference")
-    private String microReferenceForParentChildRelation;
-
-    @XmlElement(name = "countChildren")
-    private int countChildren;
-
     @XmlElementWrapper(name = "agentRelations")
     @XmlElement(name = "agentRelation")
     @XmlIDREF
@@ -165,23 +154,24 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
     @Cascade({CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE})
     private Set<TaxonNodeAgentRelation> agentRelations = new HashSet<>();
 
-    @XmlAttribute(name= "unplaced")
-    private boolean unplaced = false;
-    public boolean isUnplaced() {return unplaced;}
-    public void setUnplaced(boolean unplaced) {this.unplaced = unplaced;}
-
-    @XmlAttribute(name= "excluded")
-    private boolean excluded = false;
-    public boolean isExcluded() {return excluded;}
-    public void setExcluded(boolean excluded) {this.excluded = excluded;}
-
-    @XmlElement(name = "excludedNote")
+    /**
+     * The {@link TaxonNodeStatus status} of this taxon node.
+     */
+    @XmlAttribute(name ="TaxonNodeStatus")
+    @Column(name="status", length=10)
+    @Type(type = "eu.etaxonomy.cdm.hibernate.EnumUserType",
+        parameters = {@Parameter(name  = "enumClass", value = "eu.etaxonomy.cdm.model.taxon.TaxonNodeStatus")}
+    )
+    @Audited
+    private TaxonNodeStatus status;
+
+    @XmlElement(name = "statusNote")
     @XmlJavaTypeAdapter(MultilanguageTextAdapter.class)
     @OneToMany(fetch = FetchType.LAZY, orphanRemoval=true)
-    @MapKeyJoinColumn(name="excludedNote_mapkey_id")
-    @JoinTable(name = "TaxonNode_ExcludedNote")  //to make possible to add also unplacedNote
+//    @MapKeyJoinColumn(name="statusNote_mapkey_id")
+    @JoinTable(name = "TaxonNode_StatusNote")  //to make possible to add also unplacedNote
     @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE, CascadeType.DELETE})
-    private Map<Language,LanguageString> excludedNote = new HashMap<>();
+    private Map<Language,LanguageString> statusNote = new HashMap<>();
 
 //     private Taxon originalConcept;
 //     //or
@@ -194,7 +184,9 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
 
 // ******************** CONSTRUCTOR **********************************************/
 
-    protected TaxonNode(){super();}
+    //for hibernate use only, *packet* private required by bytebuddy
+    @Deprecated
+    TaxonNode(){}
 
     /**
      * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
@@ -211,7 +203,7 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
     }
 
     /**
-     * to create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
+     * To create nodes either use {@link Classification#addChildTaxon(Taxon, Reference, String, Synonym)}
      * or {@link TaxonNode#addChildTaxon(Taxon, Reference, String, Synonym)}
      *
      * @param taxon
@@ -222,36 +214,16 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
 
 // ************************* GETTER / SETTER *******************************/
 
-    @Transient
-    public Integer getSortIndex() {
-        return sortIndex;
-       }
-    /**
-     * SortIndex shall be handled only internally, therefore not public.
-     * However, as javaassist only supports protected methods it needs to be protected, not private.
-     * Alternatively we could use deproxy on every call of this method (see commented code)
-     * @param i
-     * @return
-     * @deprecated for internal use only
-     */
-     @Deprecated
-    protected void setSortIndex(Integer i) {
-//      CdmBase.deproxy(this, TaxonNode.class).sortIndex = i;  //alternative solution for private, DON'T remove
-        sortIndex = i;
-       }
-
-
     public Taxon getTaxon() {
         return taxon;
     }
-    protected void setTaxon(Taxon taxon) {
+    public void setTaxon(Taxon taxon) {
         this.taxon = taxon;
         if (taxon != null){
             taxon.addTaxonNode(this);
         }
     }
 
-
     @Override
     public List<TaxonNode> getChildNodes() {
         return childNodes;
@@ -260,13 +232,12 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
                this.childNodes = childNodes;
        }
 
-
     public Classification getClassification() {
         return classification;
     }
     /**
      * THIS METHOD SHOULD NOT BE CALLED!
-     * invisible part of the bidirectional relationship, for public use TaxonomicView.addRoot() or TaxonNode.addChild()
+     * invisible part of the bidirectional relationship, for public use Classification.addRoot() or TaxonNode.addChild()
      * @param classification
      * @deprecated for internal use only
      */
@@ -275,22 +246,42 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
         this.classification = classification;
     }
 
+    public boolean isUnplaced() {return hasStatus(TaxonNodeStatus.UNPLACED);}
 
-    @Override
-    public String getMicroReference() {
-        return microReferenceForParentChildRelation;
-    }
-    public void setMicroReference(String microReference) {
-        this.microReferenceForParentChildRelation = microReference;
-    }
+    //#8281 indicates a preliminary placement
+    public boolean isDoubtful() {return hasStatus(TaxonNodeStatus.DOUBTFUL);}
 
+    /**
+     * <code>true</code> if status is {@link TaxonNodeStatus#EXCLUDED} or any
+     * of its child status
+     */
+    public boolean isExcluded() {return isOrIsKindOf(TaxonNodeStatus.EXCLUDED);}
 
-    @Override
-    public Reference getReference() {
-        return referenceForParentChildRelation;
-    }
-    public void setReference(Reference reference) {
-        this.referenceForParentChildRelation = reference;
+    /**
+     * <code>true</code> if status is {@link TaxonNodeStatus#EXCLUDED} but not
+     * a sub-status (more specific excluded status)
+     */
+    public boolean isExcludedExact() {return hasStatus(TaxonNodeStatus.EXCLUDED);}
+
+    public boolean isGeographicallyExcluded() {return hasStatus(TaxonNodeStatus.EXCLUDED_GEO);}
+
+    public boolean isTaxonomicallyExcluded() {return hasStatus(TaxonNodeStatus.EXCLUDED_TAX);}
+
+    public boolean isNomenclaturallyExcluded() {return hasStatus(TaxonNodeStatus.EXCLUDED_NOM);}
+
+    public boolean isUncertainApplication() {return hasStatus(TaxonNodeStatus.UNCERTAIN_APPLICATION);}
+
+    public boolean isUnresolved() {return hasStatus(TaxonNodeStatus.UNRESOLVED);}
+
+//************************************************************/
+
+    /**
+     * The computed order index of this node being a child in the parents
+     * childnode list.
+     */
+    @Transient
+    public Integer getSortIndex() {
+        return getParent() == null ? null : getParent().getChildNodes().indexOf(CdmBase.deproxy(this));
     }
 
     //countChildren
@@ -306,7 +297,6 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
         this.countChildren = countChildren;
     }
 
-
     //parent
     @Override
     public TaxonNode getParent(){
@@ -324,30 +314,38 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
      */
     protected void setParent(TaxonNode parent) {
         this.parent = parent;
+//        this.treeIndex = parent.treeIndex() +
+    }
+
+    public TaxonNodeStatus getStatus() {
+        return status;
+    }
+    public void setStatus(TaxonNodeStatus status) {
+        this.status = status;
     }
 
-    // *************** Excluded Note ***************
+// *************** Status Note ***************
 
     /**
      * Returns the {@link MultilanguageText multi-language text} to add a note to the
-     * excluded flag. The different {@link LanguageString language strings}
+     * status. The different {@link LanguageString language strings}
      * contained in the multi-language text should all have the same meaning.
-     * @see #getExcludedNote()
-     * @see #putExcludedNote(Language, String)
+     * @see #getStatusNote(Language)
+     * @see #putStatusNote(Language, String)
      */
-    public Map<Language,LanguageString> getExcludedNote(){
-        return this.excludedNote;
+    public Map<Language,LanguageString> getStatusNote(){
+        return this.statusNote;
     }
 
     /**
-     * Returns the excluded note string in the given {@link Language language}
+     * Returns the status note string in the given {@link Language language}
      *
      * @param language  the language in which the description string looked for is formulated
-     * @see             #getExcludedNote()
-     * @see             #putExcludedNote(Language, String)
+     * @see             #getStatusNote()
+     * @see             #putStatusNote(Language, String)
      */
-    public String getExcludedNote(Language language){
-        LanguageString languageString = excludedNote.get(language);
+    public String getStatusNote(Language language){
+        LanguageString languageString = statusNote.get(language);
         if (languageString == null){
             return null;
         }else{
@@ -358,71 +356,59 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
     /**
      * Adds a translated {@link LanguageString text in a particular language}
      * to the {@link MultilanguageText multilanguage text} used to add a note to
-     * the {@link #isExcluded() excluded} flag.
+     * the {@link #getStatus() status}.
      *
-     * @param excludedNote   the language string adding a note to the excluded flag
+     * @param statusNote   the language string adding a note to the status
      *                      in a particular language
-     * @see                 #getExcludedNote()
-     * @see                 #putExcludedNote(String, Language)
+     * @see                 #getStatusNote()
+     * @see                 #putStatusNote(String, Language)
      */
-    public void putExcludedNote(LanguageString excludedNote){
-        this.excludedNote.put(excludedNote.getLanguage(), excludedNote);
+    public void putStatusNote(LanguageString statusNote){
+        this.statusNote.put(statusNote.getLanguage(), statusNote);
     }
     /**
      * 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
-     * multi-language text} used to annotate the excluded flag.
+     * multi-language text} used to annotate the status.
      *
-     * @param text      the string annotating the excluded flag
+     * @param text      the string annotating the status
      *                  in a particular language
      * @param language  the language in which the text string is formulated
-     * @see             #getExcludedNote()
-     * @see             #putExcludedNote(LanguageString)
-     * @see             #removeExcludedNote(Language)
+     * @see             #getStatusNote()
+     * @see             #putStatusNote(LanguageString)
+     * @see             #removeStatusNote(Language)
      */
-    public void putExcludedNote(Language language, String text){
-        this.excludedNote.put(language, LanguageString.NewInstance(text, language));
+    public void putStatusNote(Language language, String text){
+        this.statusNote.put(language, LanguageString.NewInstance(text, language));
     }
 
     /**
      * Removes from the {@link MultilanguageText multilanguage text} used to annotate
-     * the excluded flag the one {@link LanguageString language string}
+     * the status the one {@link LanguageString language string}
      * with the given {@link Language language}.
      *
      * @param  lang the language in which the language string to be removed
      *       has been formulated
-     * @see         #getExcludedNote()
+     * @see         #getStatusNote()
      */
-    public void removeExcludedNote(Language lang){
-        this.excludedNote.remove(lang);
+    public void removeStatusNote(Language lang){
+        this.statusNote.remove(lang);
     }
 
 // ****************** Agent Relations ****************************/
 
-
-    /**
-     * @return
-     */
     public Set<TaxonNodeAgentRelation> getAgentRelations() {
         return this.agentRelations;
     }
-
     public TaxonNodeAgentRelation addAgentRelation(DefinedTerm type, TeamOrPersonBase<?> agent){
         TaxonNodeAgentRelation result = TaxonNodeAgentRelation.NewInstance(this, agent, type);
         return result;
     }
-    /**
-     * @param nodeAgentRelation
-     */
-    protected void addAgentRelation(TaxonNodeAgentRelation agentRelation) {
+    public void addAgentRelation(TaxonNodeAgentRelation agentRelation) {
         agentRelation.setTaxonNode(this);
         this.agentRelations.add(agentRelation);
     }
-
-    /**
-     * @param nodeAgentRelation
-     */
-    public void removeNodeAgent(TaxonNodeAgentRelation agentRelation) {
+    public void removeAgentRelation(TaxonNodeAgentRelation agentRelation) {
         agentRelation.setTaxonNode(this);
         agentRelations.remove(agentRelation);
     }
@@ -437,7 +423,6 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
         this.synonymToBeUsed = synonymToBeUsed;
     }
 
-
     //treeindex
     @Override
     public String treeIndex() {
@@ -448,8 +433,14 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
     public void setTreeIndex(String treeIndex) {
         this.treeIndex = treeIndex;
     }
-
-
+    @Override
+    public String treeIndexLike() {
+        return treeIndex + "%";
+    }
+    @Override
+    public String treeIndexWc() {
+        return treeIndex + "*";
+    }
 
 //************************ METHODS **************************/
 
@@ -458,15 +449,24 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
         return addChildTaxon(taxon, this.childNodes.size(), citation, microCitation);
     }
 
+   @Override
+   public TaxonNode addChildTaxon(Taxon taxon, NamedSource source) {
+       return addChildTaxon(taxon, this.childNodes.size(), source);
+   }
 
     @Override
     public TaxonNode addChildTaxon(Taxon taxon, int index, Reference citation, String microCitation) {
+        return addChildTaxon(taxon, index, NamedSource.NewPrimarySourceInstance(citation, microCitation));
+    }
+
+    @Override
+    public TaxonNode addChildTaxon(Taxon taxon, int index, NamedSource source) {
         Classification classification = CdmBase.deproxy(this.getClassification());
         taxon = HibernateProxyHelper.deproxy(taxon, Taxon.class);
         if (classification.isTaxonInTree(taxon)){
             throw new IllegalArgumentException(String.format("Taxon may not be in a classification twice: %s", taxon.getTitleCache()));
        }
-       return addChildNode(new TaxonNode(taxon), index, citation, microCitation);
+       return addChildNode(new TaxonNode(taxon), index, source);
     }
 
     /**
@@ -498,18 +498,38 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
      */
     @Override
     public TaxonNode addChildNode(TaxonNode child, int index, Reference reference, String microReference){
+        return addChildNode(child, index, NamedSource.NewPrimarySourceInstance(reference, microReference));
+    }
+
+
+    /**
+     * Inserts the given taxon node in the list of children of <i>this</i> taxon node
+     * at the given (index + 1) position. If the given index is out of bounds
+     * an exception will arise.<BR>
+     * Due to bidirectionality this method must also assign <i>this</i> taxon node
+     * as the parent of the given child.
+     *
+     * @param   child   the taxon node to be added
+     * @param   index   the integer indicating the position at which the child
+     *                  should be added
+     * @see             #getChildNodes()
+     * @see             #addChildNode(TaxonNode, Reference, String, Synonym)
+     * @see             #deleteChildNode(TaxonNode)
+     * @see             #deleteChildNode(int)
+     */
+    @Override
+    public TaxonNode addChildNode(TaxonNode child, int index, NamedSource source){
         if (index < 0 || index > childNodes.size() + 1){
             throw new IndexOutOfBoundsException("Wrong index: " + index);
         }
            // check if this node is a descendant of the childNode
         if(child.getParent() != this && child.isAncestor(this)){
-            throw new IllegalAncestryException("New parent node is a descendant of the node to be moved.");
+            throw new IllegalStateException("New parent node is a descendant of the node to be moved.");
         }
 
         child.setParentTreeNode(this, index);
 
-        child.setReference(reference);
-        child.setMicroReference(microReference);
+        child.setSource(source);
 
         return child;
     }
@@ -536,15 +556,17 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
         }
     }
 
+
+
+
     @Override
     public boolean deleteChildNode(TaxonNode node) {
         boolean result = removeChildNode(node);
-        Taxon taxon = HibernateProxyHelper.deproxy(node.getTaxon(), Taxon.class);
-        node = HibernateProxyHelper.deproxy(node, TaxonNode.class);
+        Taxon taxon = deproxy(node.getTaxon());
+        node = deproxy(node);
         node.setTaxon(null);
 
-
-        ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
+        ArrayList<TaxonNode> childNodes = new ArrayList<>(node.getChildNodes());
         for(TaxonNode childNode : childNodes){
             HibernateProxyHelper.deproxy(childNode, TaxonNode.class);
             node.deleteChildNode(childNode);
@@ -566,12 +588,12 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
         node.setTaxon(null);
         taxon.removeTaxonNode(node);
         if (deleteChildren){
-            ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
+            ArrayList<TaxonNode> childNodes = new ArrayList<>(node.getChildNodes());
             for(TaxonNode childNode : childNodes){
                 node.deleteChildNode(childNode, deleteChildren);
             }
         } else{
-               ArrayList<TaxonNode> childNodes = new ArrayList<TaxonNode>(node.getChildNodes());
+               ArrayList<TaxonNode> childNodes = new ArrayList<>(node.getChildNodes());
             for(TaxonNode childNode : childNodes){
              this.addChildNode(childNode, null, null);
             }
@@ -607,7 +629,9 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
      * from the list of {@link #getChildNodes() children} of <i>this</i> taxon node.
      * Sets the parent and the classification of the child
      * node to null.
-     * If the given index is out of bounds no child will be removed.
+     * If the given index is out of bounds no child will be removed.<BR>
+     * NOTE: this is more for inner use. It does not remove the node from the taxon!!
+     * Use deleteChildNode(TaxonNode) instead
      *
      * @param  index   the integer indicating the position of the taxon node to
      *                                         be removed
@@ -620,12 +644,12 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
         //TODO: Only as a workaround. We have to find out why merge creates null entries.
 
         TaxonNode child = childNodes.get(index);
-        child = HibernateProxyHelper.deproxy(child, TaxonNode.class); //strange that this is required, but otherwise child.getParent() returns null for some lazy-loaded items.
+        child = HibernateProxyHelper.deproxy(child); //strange that this is required, but otherwise child.getParent() returns null for some lazy-loaded items.
 
         if (child != null){
 
-            TaxonNode parent = HibernateProxyHelper.deproxy(child.getParent(), TaxonNode.class);
-            TaxonNode thisNode = HibernateProxyHelper.deproxy(this, TaxonNode.class);
+            TaxonNode parent = HibernateProxyHelper.deproxy(child.getParent());
+            TaxonNode thisNode = HibernateProxyHelper.deproxy(this);
             if(parent != null && parent != thisNode){
                 throw new IllegalArgumentException("Child TaxonNode (id:" + child.getId() +") must be a child of this (id:" + thisNode.getId() + " node. Sortindex is: " + index + ", parent-id:" + parent.getId());
             }else if (parent == null){
@@ -634,17 +658,12 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
             childNodes.remove(index);
             child.setClassification(null);
 
-            //update sortindex
-            //TODO workaround (see sortIndex doc)
             this.countChildren = childNodes.size();
             child.setParent(null);
             child.setTreeIndex(null);
-            updateSortIndex(index);
-            child.setSortIndex(null);
         }
     }
 
-
     /**
      * Remove this taxonNode From its taxonomic parent
      *
@@ -686,8 +705,6 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
     /**
      * Sets the parent of this taxon node to the given parent. Cleans up references to
      * old parents and sets the classification to the new parents classification
-     *
-     * @param parent
      */
     @Transient
     protected void setParentTreeNode(TaxonNode parent, int index){
@@ -720,10 +737,7 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
         // add this node to the parent's child nodes
         parent = CdmBase.deproxy(parent);
         List<TaxonNode> parentChildren = parent.getChildNodes();
-       //TODO: Only as a workaround. We have to find out why merge creates null entries.
 
-//        HHH_9751_Util.removeAllNull(parentChildren);
-//        parent.updateSortIndex(0);
         if (index > parent.getChildNodes().size()){
             index = parent.getChildNodes().size();
         }
@@ -738,52 +752,23 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
             parentChildren.add(index, this);
         }
 
-
-        //sortIndex
-        //TODO workaround (see sortIndex doc)
-       // this.getParent().removeNullValueFromChildren();
-        this.getParent().updateSortIndex(index);
-        //only for debugging
-        if (! this.getSortIndex().equals(index)){
-               logger.warn("index and sortindex are not equal: " +  this.getSortIndex() + ";" + index);
-        }
+//        //only for debugging
+//        if (this.getSortIndex() == null){
+//            logger.warn("sortindex is null. This should not happen.");
+//        }else if (! this.getSortIndex().equals(index)){
+//             logger.warn("index and sortindex are not equal: " +  this.getSortIndex() + ";" + index);
+//        }
 
         // update the children count
         parent.setCountChildren(parent.getChildNodes().size());
     }
 
-       /**
-        * As long as the sort index is not correctly handled through hibernate this is a workaround method
-        * to update the sort index manually
-        * @param parentChildren
-        * @param index
-        */
-       private void updateSortIndex(int index) {
-           if (this.hasChildNodes()){
-           List<TaxonNode> children = this.getChildNodes();
-           HHH_9751_Util.removeAllNull(children);
-           for(int i = index; i < children.size(); i++){
-               TaxonNode child = children.get(i);
-               if (child != null){
-    //                 child = CdmBase.deproxy(child, TaxonNode.class);  //deproxy not needed as long as setSortIndex is protected or public #4200
-                       child.setSortIndex(i);
-               }else{
-                       String message = "A node in a taxon tree must never be null but is (ParentId: %d; sort index: %d; index: %d; i: %d)";
-                       throw new IllegalStateException(String.format(message, getId(), sortIndex, index, i));
-               }
-            }
-           }
-       }
-
-
     /**
-     * Returns a set containing this node and all nodes that are descendants of this node
-     *
-     * @return
+     * Returns a set containing this node and all nodes that are descendants of this node.
      */
-
+       @Transient
     protected Set<TaxonNode> getDescendants(){
-        Set<TaxonNode> nodeSet = new HashSet<TaxonNode>();
+        Set<TaxonNode> nodeSet = new HashSet<>();
 
         nodeSet.add(this);
 
@@ -801,11 +786,11 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
      */
     protected TaxonNode cloneDescendants(){
 
-        TaxonNode clone = (TaxonNode)this.clone();
+        TaxonNode clone = this.clone();
         TaxonNode childClone;
 
         for(TaxonNode childNode : getChildNodes()){
-            childClone = (TaxonNode) childNode.clone();
+            childClone = childNode.clone();
             for (TaxonNode childChild:childNode.getChildNodes()){
                 childClone.addChildNode(childChild.cloneDescendants(), childChild.getReference(), childChild.getMicroReference());
             }
@@ -820,6 +805,7 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
      *
      * @return a set of all parent nodes
      */
+    @Transient
     protected Set<TaxonNode> getAncestors(){
         Set<TaxonNode> nodeSet = new HashSet<>();
         if(this.getParent() != null){
@@ -837,6 +823,7 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
      * @param rank the rank the ancestor should have
      * @return the first found instance of a parent taxon node with the given rank
      */
+    @Transient
     public TaxonNode getAncestorOfRank(Rank rank){
         Taxon taxon = CdmBase.deproxy(this.getTaxon());
         if (taxon == null){
@@ -863,6 +850,7 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
      * Returns the ancestor taxa, starting with the highest (e.g. kingdom)
      * @return
      */
+    @Transient
     public List<Taxon> getAncestorTaxaList(){
         List<Taxon> result = new ArrayList<>();
         TaxonNode current = this;
@@ -878,9 +866,8 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
     /**
      * Returns the ancestor taxon nodes, that do have a taxon attached
      * (excludes the root node) starting with the highest
-     *
-     * @return
      */
+    @Transient
     public List<TaxonNode> getAncestorList(){
         List<TaxonNode> result = new ArrayList<>();
         TaxonNode current = this.getParent();
@@ -896,7 +883,6 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
 
     /**
      * Whether this TaxonNode is a direct child of the classification TreeNode
-     * @return
      */
     @Transient
     public boolean isTopmostNode(){
@@ -939,40 +925,33 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
     }
 
     /**
-     * Whether this TaxonNode is a descendant of the given TaxonNode
-     *
-     * Caution: use this method with care on big branches. -> performance and memory hungry
-     *
-     * Protip: Try solving your problem with the isAscendant method which traverses the tree in the
-     * other direction (up). It will always result in a rather small set of consecutive parents beeing
-     * generated.
-     *
-     * TODO implement more efficiently without generating the set of descendants first
+     * Whether this TaxonNode is a descendant of (or equal to) the given TaxonNode
      *
      * @param possibleParent
-     * @return true if this is a descendant
+     * @return <code>true</code> if <b>this</b> is a descendant
      */
     @Transient
     public boolean isDescendant(TaxonNode possibleParent){
-       if (this.treeIndex() == null || possibleParent.treeIndex() == null) {
+       if (possibleParent == null || this.treeIndex() == null
+               || possibleParent.treeIndex() == null) {
                return false;
        }
-       return possibleParent == null ? false : this.treeIndex().startsWith(possibleParent.treeIndex() );
+       return this.treeIndex().startsWith(possibleParent.treeIndex() );
     }
 
     /**
-     * Whether this TaxonNode is an ascendant of the given TaxonNode
+     * Whether this TaxonNode is an ascendant of (or equal to) the given TaxonNode.
      *
      * @param possibleChild
-     * @return true if there are ascendants
+     * @return <code>true</code> if <b>this</b> is a ancestor of the given child parameter
      */
     @Transient
     public boolean isAncestor(TaxonNode possibleChild){
-       if (this.treeIndex() == null || possibleChild.treeIndex() == null) {
+       if (possibleChild == null || this.treeIndex() == null || possibleChild.treeIndex() == null) {
                return false;
        }
        // return possibleChild == null ? false : possibleChild.getAncestors().contains(this);
-        return possibleChild == null ? false : possibleChild.treeIndex().startsWith(this.treeIndex());
+        return  possibleChild.treeIndex().startsWith(this.treeIndex());
     }
 
     /**
@@ -986,29 +965,31 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
         return childNodes.size() > 0;
     }
 
-
     public boolean hasTaxon() {
         return (taxon!= null);
     }
 
-    /**
-     * @return
-     */
     @Transient
     public Rank getNullSafeRank() {
         return hasTaxon() ? getTaxon().getNullSafeRank() : null;
     }
 
-    public void removeNullValueFromChildren(){
-        try {
-            //HHH_9751_Util.removeAllNull(childNodes);
-            this.updateSortIndex(0);
-        } catch (LazyInitializationException e) {
-            logger.info("Cannot clean up uninitialized children without a session, skipping.");
-        }
+    @Transient
+    public TaxonName getNullSafeName() {
+        return getTaxon() == null? null: getTaxon().getName();
     }
 
+    private boolean hasStatus(TaxonNodeStatus status) {
+        return CdmUtils.nullSafeEqual(this.status, status);
+    }
 
+    private boolean isOrIsKindOf(TaxonNodeStatus status) {
+        if (this.status == null) {
+            return status == null;
+        }else {
+            return hasStatus(status) || this.status.isKindOf(status);
+        }
+    }
 
 //*********************** CLONE ********************************************************/
     /**
@@ -1017,12 +998,17 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
      * modifying only some of the attributes.<BR><BR>
      * The child nodes are not copied.<BR>
      * The taxon and parent are the same as for the original taxon node. <BR>
+     * <BR>
+     * Note: Cloning taxon nodes with cloning taxa (and children) is a complex
+     * issue which is better be handled in service layer logic. See according
+     * clone method in classification service
+     * or taxon node service there.
      *
-     * @see eu.etaxonomy.cdm.model.media.IdentifiableEntity#clone()
      * @see java.lang.Object#clone()
      */
     @Override
-    public Object clone()  {
+    public TaxonNode clone()  {
+
         try{
             TaxonNode result = (TaxonNode)super.clone();
             result.getTaxon().addTaxonNode(result);
@@ -1034,16 +1020,15 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
             //agents
             result.agentRelations = new HashSet<>();
             for (TaxonNodeAgentRelation rel : this.agentRelations){
-                result.addAgentRelation((TaxonNodeAgentRelation)rel.clone());
+                result.addAgentRelation(rel.clone());
             }
 
-            //excludedNote
-            result.excludedNote = new HashMap<>();
-            for(Language lang : this.excludedNote.keySet()){
-                result.excludedNote.put(lang, this.excludedNote.get(lang));
+            //statusNote
+            result.statusNote = new HashMap<>();
+            for(Language lang : this.statusNote.keySet()){
+                result.statusNote.put(lang, this.statusNote.get(lang));
             }
 
-
             return result;
         }catch (CloneNotSupportedException e) {
             logger.warn("Object does not implement cloneable");
@@ -1052,4 +1037,18 @@ public class TaxonNode extends AnnotatableEntity implements ITaxonTreeNode, ITre
         }
     }
 
-}
+//To be removed once SingleSourcedEntity attributes have been renamed to reference and microReference
+
+    @Override
+    @Transient
+    public Reference getReference() {
+        return getCitation();
+    }
+
+    @Override
+    @Transient
+    public String getMicroReference() {
+        return getCitationMicroReference();
+    }
+
+}
\ No newline at end of file