move children up to parent in code
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / taxon / TaxonNode.java
index ebde4812e1753c5cd9b74c5e0cd9d9b15744ddaa..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;
@@ -22,7 +21,6 @@ import javax.persistence.FetchType;
 import javax.persistence.JoinTable;
 import javax.persistence.ManyToOne;
 import javax.persistence.OneToMany;
-import javax.persistence.OrderBy;
 import javax.persistence.OrderColumn;
 import javax.persistence.Table;
 import javax.persistence.Transient;
@@ -37,8 +35,8 @@ 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.Parameter;
@@ -52,7 +50,6 @@ import org.hibernate.search.annotations.IndexedEmbedded;
 import org.hibernate.search.annotations.Store;
 
 import eu.etaxonomy.cdm.common.CdmUtils;
-import eu.etaxonomy.cdm.hibernate.HHH_9751_Util;
 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
 import eu.etaxonomy.cdm.jaxb.MultilanguageTextAdapter;
 import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
@@ -82,8 +79,8 @@ import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustNotSkipRanks;
     "taxon",
     "parent",
     "treeIndex",
-    "sortIndex",
     "childNodes",
+    "sortIndex",
     "countChildren",
     "agentRelations",
     "synonymToBeUsed",
@@ -92,8 +89,6 @@ import eu.etaxonomy.cdm.validation.annotation.ChildTaxaMustNotSkipRanks;
 })
 @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(name="TaxonNode", indexes = { @javax.persistence.Index(name = "taxonNodeTreeIndex", columnList = "treeIndex") })
 @ChildTaxaMustBeLowerRankThanParent(groups = Level3.class)
@@ -104,7 +99,7 @@ public class TaxonNode
             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
@@ -121,6 +116,22 @@ public class TaxonNode
 //    @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)
@@ -135,32 +146,6 @@ public class TaxonNode
     @IndexedEmbedded(includeEmbeddedObjectId=true)
     private Classification classification;
 
-    @XmlElementWrapper(name = "childNodes")
-    @XmlElement(name = "childNode")
-    @XmlIDREF
-    @XmlSchemaType(name = "IDREF")
-    //see https://dev.e-taxonomy.eu/redmine/issues/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/redmine/issues/3722
-    //see https://dev.e-taxonomy.eu/redmine/issues/4200
-    private Integer sortIndex = -1;
-
-//    //the source for this placement
-//    @XmlElement(name = "source")
-//    @XmlIDREF
-//    @XmlSchemaType(name = "IDREF")
-//    @OneToOne(fetch = FetchType.LAZY, orphanRemoval=true)
-//    @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE, CascadeType.DELETE, CascadeType.PERSIST})
-//    private DescriptionElementSource source;
-
-    @XmlElement(name = "countChildren")
-    private int countChildren;
-
     @XmlElementWrapper(name = "agentRelations")
     @XmlElement(name = "agentRelation")
     @XmlIDREF
@@ -199,7 +184,9 @@ public class TaxonNode
 
 // ******************** 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)}
@@ -227,24 +214,6 @@ public class TaxonNode
 
 // ************************* 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;
     }
@@ -282,43 +251,39 @@ public class TaxonNode
     //#8281 indicates a preliminary placement
     public boolean isDoubtful() {return hasStatus(TaxonNodeStatus.DOUBTFUL);}
 
-    public boolean isExcluded() {return hasStatus(TaxonNodeStatus.EXCLUDED);}
-
-//************************* SOURCE *********************/
-
-//    @Override
-//    @Transient
-//    public String getMicroReference() {
-//        return source == null ? null : this.source.getCitationMicroReference();
-//    }
-//    public void setMicroReference(String microReference) {
-//        this.getSource(true).setCitationMicroReference(StringUtils.isBlank(microReference)? null : microReference);
-//        checkNullSource();
-//    }
-//
-//    @Override
-//    @Transient
-//    public Reference getReference() {
-//        return (this.source == null) ? null : source.getCitation();
-//    }
-//    public void setReference(Reference reference) {
-//        getSource(true).setCitation(reference);
-//        checkNullSource();
-//    }
-
-//    @Override
-//    public DescriptionElementSource getSource() {
-//        return source;
-//    }
-//    @Override
-//    public void setSource(DescriptionElementSource source) {
-//        this.source = source;
-//    }
+    /**
+     * <code>true</code> if status is {@link TaxonNodeStatus#EXCLUDED} or any
+     * of its child status
+     */
+    public boolean isExcluded() {return isOrIsKindOf(TaxonNodeStatus.EXCLUDED);}
 
+    /**
+     * <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
     public int getCountChildren() {
         return countChildren;
@@ -559,14 +524,13 @@ public class TaxonNode
         }
            // 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.setSource(source);
 
-
         return child;
     }
 
@@ -694,17 +658,12 @@ public class TaxonNode
             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
      *
@@ -746,8 +705,6 @@ public class TaxonNode
     /**
      * 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){
@@ -780,10 +737,7 @@ public class TaxonNode
         // 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();
         }
@@ -798,45 +752,17 @@ public class TaxonNode
             parentChildren.add(index, this);
         }
 
-
-        //sortIndex
-        //TODO workaround (see sortIndex doc)
-       // this.getParent().removeNullValueFromChildren();
-        this.getParent().updateSortIndex(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);
-        }
+//        //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.
      */
@@ -1053,19 +979,18 @@ public class TaxonNode
         return getTaxon() == null? null: getTaxon().getName();
     }
 
-    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.");
-        }
-    }
-
     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 ********************************************************/
     /**
      * Clones <i>this</i> taxon node. This is a shortcut that enables to create