ref #1445: fix helper methods for pesi merging
[cdmlib.git] / cdmlib-persistence / src / main / java / eu / etaxonomy / cdm / persistence / dao / hibernate / taxon / TaxonNodeDaoHibernateImpl.java
old mode 100644 (file)
new mode 100755 (executable)
index 2e80724..a996b22
@@ -1,4 +1,3 @@
-// $Id$\r
 /**\r
 * Copyright (C) 2007 EDIT\r
 * European Distributed Institute of Taxonomy\r
 package eu.etaxonomy.cdm.persistence.dao.hibernate.taxon;\r
 \r
 import java.math.BigInteger;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.HashMap;\r
+import java.util.HashSet;\r
+import java.util.Iterator;\r
 import java.util.List;\r
+import java.util.Map;\r
 import java.util.Set;\r
 import java.util.UUID;\r
 \r
 import org.apache.log4j.Logger;\r
 import org.hibernate.Criteria;\r
+import org.hibernate.Hibernate;\r
 import org.hibernate.Query;\r
+import org.hibernate.criterion.ProjectionList;\r
 import org.hibernate.criterion.Projections;\r
 import org.hibernate.criterion.Restrictions;\r
 import org.springframework.beans.factory.annotation.Autowired;\r
 import org.springframework.beans.factory.annotation.Qualifier;\r
 import org.springframework.stereotype.Repository;\r
 \r
+import eu.etaxonomy.cdm.common.CdmUtils;\r
+import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;\r
 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;\r
+import eu.etaxonomy.cdm.model.common.CdmBase;\r
+import eu.etaxonomy.cdm.model.common.TreeIndex;\r
+import eu.etaxonomy.cdm.model.name.Rank;\r
+import eu.etaxonomy.cdm.model.name.TaxonName;\r
+import eu.etaxonomy.cdm.model.reference.Reference;\r
 import eu.etaxonomy.cdm.model.taxon.Classification;\r
 import eu.etaxonomy.cdm.model.taxon.Taxon;\r
+import eu.etaxonomy.cdm.model.taxon.TaxonBase;\r
 import eu.etaxonomy.cdm.model.taxon.TaxonNode;\r
 import eu.etaxonomy.cdm.model.taxon.TaxonNodeAgentRelation;\r
+import eu.etaxonomy.cdm.model.taxon.UuidAndTitleCacheTaxonComparator;\r
+import eu.etaxonomy.cdm.persistence.dao.common.Restriction;\r
 import eu.etaxonomy.cdm.persistence.dao.hibernate.common.AnnotatableDaoImpl;\r
 import eu.etaxonomy.cdm.persistence.dao.taxon.IClassificationDao;\r
 import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao;\r
 import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonNodeDao;\r
+import eu.etaxonomy.cdm.persistence.dto.TaxonNodeDto;\r
+import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;\r
+import eu.etaxonomy.cdm.persistence.query.OrderHint;\r
 \r
 /**\r
  * @author a.mueller\r
- * @created 16.06.2009\r
+ * @since 16.06.2009\r
  */\r
 @Repository\r
 @Qualifier("taxonNodeDaoHibernateImpl")\r
@@ -56,32 +77,48 @@ public class TaxonNodeDaoHibernateImpl extends AnnotatableDaoImpl<TaxonNode>
        @Override\r
        public UUID delete(TaxonNode persistentObject, boolean deleteChildren){\r
                Taxon taxon = persistentObject.getTaxon();\r
-               taxon = HibernateProxyHelper.deproxy(taxon, Taxon.class);\r
+               taxon = HibernateProxyHelper.deproxy(taxon);\r
 \r
                /*Session session = this.getSession();\r
                Query query = session.createQuery("from TaxonNode t where t.taxon = :taxon");\r
                query.setParameter("taxon", taxon);\r
                List result = query.list();*/\r
                if (taxon != null){\r
+                   Hibernate.initialize(taxon);\r
+                   Hibernate.initialize(taxon.getTaxonNodes());\r
                        Set<TaxonNode> nodes = taxon.getTaxonNodes();\r
-\r
-                       if (nodes.size()==1){\r
-\r
-                               TaxonNode node = nodes.iterator().next();\r
-                               node = HibernateProxyHelper.deproxy(node, TaxonNode.class);\r
-\r
-                               taxon.removeTaxonNode(node, deleteChildren);\r
-                               taxonDao.delete(taxon);\r
+                       //Hibernate.initialize(taxon.getTaxonNodes());\r
+                       for (TaxonNode node:nodes) {\r
+                           node = HibernateProxyHelper.deproxy(node);\r
+\r
+                           if (node.equals(persistentObject)){\r
+                               if (node.hasChildNodes()){\r
+                                   Iterator<TaxonNode> childNodes = node.getChildNodes().iterator();\r
+                                   TaxonNode childNode;\r
+                                   List<TaxonNode> listForDeletion = new ArrayList<>();\r
+                           while (childNodes.hasNext()){\r
+                               childNode = childNodes.next();\r
+                               listForDeletion.add(childNode);\r
+                               childNodes.remove();\r
+\r
+                           }\r
+                           for (TaxonNode deleteNode:listForDeletion){\r
+                               delete(deleteNode, deleteChildren);\r
+                           }\r
+                       }\r
+\r
+                               taxon.removeTaxonNode(node, deleteChildren);\r
+                               taxonDao.saveOrUpdate(taxon);\r
+                               taxon = HibernateProxyHelper.deproxy(taxonDao.findByUuid(taxon.getUuid()), Taxon.class);\r
+                               taxonDao.delete(taxon);\r
+\r
+                           }\r
                        }\r
                }\r
-               //persistentObject.delete();\r
-\r
-               super.delete(persistentObject);\r
 \r
+               UUID result = super.delete(persistentObject);\r
 \r
-\r
-               //taxon = (Taxon)taxonDao.findByUuid(taxon.getUuid());\r
-               return persistentObject.getUuid();\r
+               return result;\r
        }\r
 \r
        @Override\r
@@ -92,30 +129,143 @@ public class TaxonNodeDaoHibernateImpl extends AnnotatableDaoImpl<TaxonNode>
                    limit = "LIMIT "+start+"," +end;\r
                }\r
                //FIXME write test\r
-        String queryString = "SELECT DISTINCT nodes.*,taxa.titleCache FROM TaxonNode AS nodes LEFT JOIN TaxonBase AS taxa ON nodes.taxon_id = taxa.id WHERE taxa.DTYPE = 'Taxon' AND nodes.classification_id = " + classificationId + " ORDER BY taxa.titleCache " + limit;\r
+        String queryString = "SELECT DISTINCT nodes.*,taxa.titleCache "\r
+                + " FROM TaxonNode AS nodes "\r
+                + "    LEFT JOIN TaxonBase AS taxa ON nodes.taxon_id = taxa.id "\r
+                + " WHERE taxa.DTYPE = 'Taxon' "\r
+                + "    AND nodes.classification_id = " + classificationId +\r
+                  " ORDER BY taxa.titleCache " + limit;\r
+        @SuppressWarnings("unchecked")\r
         List<TaxonNode> result  = getSession().createSQLQuery(queryString).addEntity(TaxonNode.class).list();\r
 \r
        return result;\r
-\r
-\r
        }\r
-    /* (non-Javadoc)\r
-     * @see eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonNodeDao#countTaxonOfAcceptedTaxaByClassification(eu.etaxonomy.cdm.model.taxon.Classification)\r
-     */\r
+\r
     @Override\r
     public int countTaxonOfAcceptedTaxaByClassification(Classification classification){\r
         int classificationId = classification.getId();\r
         //FIXME write test\r
-        String queryString = "SELECT DISTINCT COUNT('nodes.*') FROM TaxonNode AS nodes LEFT JOIN TaxonBase AS taxa ON nodes.taxon_id = taxa.id WHERE taxa.DTYPE = 'Taxon' AND nodes.classification_id = " + classificationId;\r
-         List<BigInteger> result = getSession().createSQLQuery(queryString).list();\r
+        String queryString = ""\r
+                + " SELECT DISTINCT COUNT('nodes.*') "\r
+                + " FROM TaxonNode AS nodes "\r
+                + "   LEFT JOIN TaxonBase AS taxa ON nodes.taxon_id = taxa.id "\r
+                + " WHERE taxa.DTYPE = 'Taxon' AND nodes.classification_id = " + classificationId;\r
+         @SuppressWarnings("unchecked")\r
+        List<BigInteger> result = getSession().createSQLQuery(queryString).list();\r
          return result.get(0).intValue ();\r
     }\r
 \r
     @Override\r
-    public List<TaxonNode> listChildrenOf(TaxonNode node, Integer pageSize, Integer pageIndex, List<String> propertyPaths, boolean recursive){\r
-       if (recursive == true){\r
-               Criteria crit = getSession().createCriteria(TaxonNode.class);\r
-               crit.add( Restrictions.like("treeIndex", node.treeIndex()+ "%") );\r
+    public List<UuidAndTitleCache<TaxonNode>> listChildNodesAsUuidAndTitleCache(UuidAndTitleCache<TaxonNode> parent) {\r
+        String queryString =\r
+                  " SELECT tn.uuid, tn.id, t.titleCache "\r
+                + " FROM TaxonNode tn "\r
+                + "    INNER JOIN tn.taxon AS t "\r
+                + " WHERE tn.parent.uuid = :parentId";\r
+\r
+        Query query =  getSession().createQuery(queryString);\r
+        query.setParameter("parentId", parent.getUuid());\r
+\r
+        @SuppressWarnings("unchecked")\r
+        List<Object[]> result = query.list();\r
+\r
+        List<UuidAndTitleCache<TaxonNode>> list = new ArrayList<>();\r
+        for(Object[] object : result){\r
+            list.add(new UuidAndTitleCache<>((UUID) object[0],(Integer) object[1], (String) object[2]));\r
+        }\r
+        return list;\r
+    }\r
+\r
+    @Override\r
+    public List<TaxonNodeDto> listChildNodesAsTaxonNodeDto(UuidAndTitleCache<TaxonNode> parent) {\r
+        String queryString =\r
+                 " SELECT tn "\r
+               + " FROM TaxonNode tn "\r
+               + "    INNER JOIN tn.taxon AS t "\r
+               + " WHERE tn.parent.uuid = :parentId";\r
+        Query query =  getSession().createQuery(queryString);\r
+        query.setParameter("parentId", parent.getUuid());\r
+\r
+        @SuppressWarnings("unchecked")\r
+        List<TaxonNode> result = query.list();\r
+\r
+        List<TaxonNodeDto> list = new ArrayList<>();\r
+        for(TaxonNode object : result){\r
+            list.add(new TaxonNodeDto(object));\r
+        }\r
+        return list;\r
+    }\r
+\r
+    @Override\r
+    public List<UuidAndTitleCache<TaxonNode>> getUuidAndTitleCache(Integer limit, String pattern, UUID classificationUuid) {\r
+        String queryString = "SELECT tn.uuid, tn.id, t.titleCache, t.name.rank "\r
+                + " FROM TaxonNode tn "\r
+                       + "   INNER JOIN tn.taxon AS t "\r
+                       + "   INNER JOIN tn.classification AS cls "\r
+                       + "WHERE t.titleCache LIKE :pattern ";\r
+        if(classificationUuid != null){\r
+               queryString += "AND cls.uuid = :classificationUuid";\r
+        }\r
+        Query query =  getSession().createQuery(queryString);\r
+\r
+        query.setParameter("pattern", pattern.toLowerCase()+"%");\r
+        query.setParameter("classificationUuid", classificationUuid);\r
+\r
+\r
+        @SuppressWarnings("unchecked")\r
+        List<Object[]> result = query.list();\r
+        Collections.sort(result, new UuidAndTitleCacheTaxonComparator());\r
+\r
+        List<UuidAndTitleCache<TaxonNode>> list = new ArrayList<>();\r
+        for(Object[] object : result){\r
+            list.add(new UuidAndTitleCache<>((UUID) object[0],(Integer) object[1], (String) object[2]));\r
+        }\r
+        return list;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     */\r
+    @Override\r
+    public UuidAndTitleCache<TaxonNode> getParentUuidAndTitleCache(UuidAndTitleCache<TaxonNode> child) {\r
+        String queryString = ""\r
+                + " SELECT tn.parent.uuid, tn.parent.id, tn.parent.taxon.titleCache, "\r
+                + "                  tn.parent.classification.titleCache "\r
+                + " FROM TaxonNode tn"\r
+                + "    LEFT OUTER JOIN tn.parent.taxon"\r
+                + " WHERE tn.id = :childId";\r
+        Query query =  getSession().createQuery(queryString);\r
+        query.setParameter("childId", child.getId());\r
+        List<UuidAndTitleCache<TaxonNode>> list = new ArrayList<>();\r
+\r
+        @SuppressWarnings("unchecked")\r
+        List<Object[]> result = query.list();\r
+\r
+        for(Object[] object : result){\r
+            UUID uuid = (UUID) object[0];\r
+            Integer id = (Integer) object[1];\r
+            String taxonTitleCache = (String) object[2];\r
+            String classificationTitleCache = (String) object[3];\r
+            if(taxonTitleCache!=null){\r
+                list.add(new UuidAndTitleCache<>(uuid,id, taxonTitleCache));\r
+            }\r
+            else{\r
+                list.add(new UuidAndTitleCache<>(uuid,id, classificationTitleCache));\r
+            }\r
+        }\r
+        if(list.size()==1){\r
+            return list.iterator().next();\r
+        }\r
+        return null;\r
+    }\r
+\r
+    @Override\r
+    public List<TaxonNode> listChildrenOf(TaxonNode node, Integer pageSize, Integer pageIndex,\r
+            boolean recursive, boolean includeUnpublished, List<String> propertyPaths){\r
+\r
+        if (recursive == true){\r
+               Criteria crit = childrenOfCriteria(node, includeUnpublished);\r
+\r
                if(pageSize != null) {\r
                 crit.setMaxResults(pageSize);\r
                 if(pageIndex != null) {\r
@@ -124,44 +274,56 @@ public class TaxonNodeDaoHibernateImpl extends AnnotatableDaoImpl<TaxonNode>
                     crit.setFirstResult(0);\r
                 }\r
             }\r
-               List<TaxonNode> results = crit.list();\r
+               @SuppressWarnings("unchecked")\r
+            List<TaxonNode> results = crit.list();\r
                results.remove(node);\r
                defaultBeanInitializer.initializeAll(results, propertyPaths);\r
                return results;\r
        }else{\r
-               return classificationDao.listChildrenOf(node.getTaxon(), node.getClassification(), pageSize, pageIndex, propertyPaths);\r
+               return classificationDao.listChildrenOf(node.getTaxon(), node.getClassification(), null,\r
+                      includeUnpublished, pageSize, pageIndex, propertyPaths);\r
        }\r
 \r
     }\r
 \r
     @Override\r
        public Long countChildrenOf(TaxonNode node, Classification classification,\r
-                       boolean recursive) {\r
+                       boolean recursive, boolean includeUnpublished) {\r
 \r
                if (recursive == true){\r
-                       Criteria crit = getSession().createCriteria(TaxonNode.class);\r
-               crit.add( Restrictions.like("treeIndex", node.treeIndex()+ "%") );\r
+                       Criteria crit = childrenOfCriteria(node, includeUnpublished);\r
                crit.setProjection(Projections.rowCount());\r
                return ((Integer)crit.uniqueResult().hashCode()).longValue();\r
                }else{\r
-                       return classificationDao.countChildrenOf(node.getTaxon(), classification);\r
+                       return classificationDao.countChildrenOf(\r
+                               node.getTaxon(), classification, null, includeUnpublished);\r
                }\r
        }\r
+    /**\r
+     * @param node\r
+     * @param includeUnpublished\r
+     * @return\r
+     */\r
+    private Criteria childrenOfCriteria(TaxonNode node, boolean includeUnpublished) {\r
+        Criteria crit = getSession().createCriteria(TaxonNode.class);\r
+        crit.add( Restrictions.like("treeIndex", node.treeIndex()+ "%") );\r
+        if (!includeUnpublished){\r
+            crit.createCriteria("taxon").add( Restrictions.eq("publish", Boolean.TRUE));\r
+        }\r
+        return crit;\r
+    }\r
     /**\r
      * {@inheritDoc}\r
      */\r
     @Override\r
     public List<TaxonNodeAgentRelation> listTaxonNodeAgentRelations(UUID taxonUuid, UUID classificationUuid,\r
-            Integer start, Integer limit, List<String> propertyPaths) {\r
-\r
-        String hql = "select tnar from TaxonNodeAgentRelation as tnar "\r
-                + "join tnar.taxonNode as tn "\r
-                + "join tn.taxon as t "\r
-                + "join tn.classification as c "\r
-                + "join fetch tnar.agent as a "\r
-                + "where t.uuid = :taxonUuid and c.uuid = :classificationUuid "\r
-                + "order by a.titleCache";\r
-        Query query =  getSession().createQuery(hql);\r
+            UUID agentUuid, UUID rankUuid, UUID relTypeUuid, Integer start, Integer limit,\r
+            List<String> propertyPaths) {\r
+\r
+        StringBuilder hql = prepareListTaxonNodeAgentRelations(taxonUuid, classificationUuid,\r
+                agentUuid, rankUuid, relTypeUuid, false);\r
+\r
+        Query query =  getSession().createQuery(hql.toString());\r
 \r
         if(limit != null) {\r
             query.setMaxResults(limit);\r
@@ -170,9 +332,9 @@ public class TaxonNodeDaoHibernateImpl extends AnnotatableDaoImpl<TaxonNode>
             }\r
         }\r
 \r
-        query.setParameter("taxonUuid", taxonUuid);\r
-        query.setParameter("classificationUuid", classificationUuid);\r
+        setParamsForListTaxonNodeAgentRelations(taxonUuid, classificationUuid, agentUuid, rankUuid, relTypeUuid, query);\r
 \r
+        @SuppressWarnings("unchecked")\r
         List<TaxonNodeAgentRelation> records = query.list();\r
 \r
         if(propertyPaths != null) {\r
@@ -180,24 +342,662 @@ public class TaxonNodeDaoHibernateImpl extends AnnotatableDaoImpl<TaxonNode>
         }\r
         return records;\r
     }\r
+\r
+    @Override\r
+    public <S extends TaxonNode> List<S> list(Class<S> type, List<Restriction<?>> restrictions, Integer limit,\r
+            Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {\r
+        // TODO Auto-generated method stub\r
+        return list(type, restrictions, limit, start, orderHints, propertyPaths, INCLUDE_UNPUBLISHED);\r
+    }\r
+\r
+    @Override\r
+    public <S extends TaxonNode> List<S> list(Class<S> type, List<Restriction<?>> restrictions, Integer limit,\r
+            Integer start, List<OrderHint> orderHints, List<String> propertyPaths, boolean includePublished) {\r
+\r
+        Criteria criteria = createCriteria(type, restrictions, false);\r
+\r
+        if(!includePublished){\r
+            criteria.add(Restrictions.eq("taxon.publish", true));\r
+        }\r
+\r
+        addLimitAndStart(criteria, limit, start);\r
+        addOrder(criteria, orderHints);\r
+\r
+        @SuppressWarnings("unchecked")\r
+        List<S> result = criteria.list();\r
+        defaultBeanInitializer.initializeAll(result, propertyPaths);\r
+        return result;\r
+    }\r
+\r
+\r
+    @Override\r
+    public long count(Class<? extends TaxonNode> type, List<Restriction<?>> restrictions) {\r
+        return count(type, restrictions, INCLUDE_UNPUBLISHED);\r
+    }\r
+\r
+\r
+    @Override\r
+    public long count(Class<? extends TaxonNode> type, List<Restriction<?>> restrictions, boolean includePublished) {\r
+\r
+        Criteria criteria = createCriteria(type, restrictions, false);\r
+        if(!includePublished){\r
+            criteria.add(Restrictions.eq("taxon.publish", true));\r
+        }\r
+        criteria.setProjection(Projections.projectionList().add(Projections.rowCount()));\r
+        return (Long) criteria.uniqueResult();\r
+    }\r
+\r
+\r
     /**\r
      * {@inheritDoc}\r
      */\r
     @Override\r
-    public long countTaxonNodeAgentRelations(UUID taxonUuid, UUID classificationUuid) {\r
-        String hql = "select count(tnar) from TaxonNodeAgentRelation as tnar "\r
-                + "join tnar.taxonNode as tn "\r
-                + "join tn.taxon as t "\r
-                + "join tn.classification as c "\r
-                + "where t.uuid = :taxonUuid and c.uuid = :classificationUuid";\r
-        Query query =  getSession().createQuery(hql);\r
+    public long countTaxonNodeAgentRelations(UUID taxonUuid, UUID classificationUuid, UUID agentUuid, UUID rankUuid, UUID relTypeUuid) {\r
 \r
-        query.setParameter("taxonUuid", taxonUuid);\r
-        query.setParameter("classificationUuid", classificationUuid);\r
+        StringBuilder hql = prepareListTaxonNodeAgentRelations(taxonUuid, classificationUuid, agentUuid, rankUuid, relTypeUuid, true);\r
+        Query query =  getSession().createQuery(hql.toString());\r
+\r
+        setParamsForListTaxonNodeAgentRelations(taxonUuid, classificationUuid, agentUuid, rankUuid, relTypeUuid, query);\r
 \r
         Long count = Long.parseLong(query.uniqueResult().toString());\r
 \r
         return count;\r
     }\r
+    /**\r
+     * @param taxonUuid\r
+     * @param classificationUuid\r
+     * @param agentUuid\r
+     * @param relTypeUuid TODO\r
+     * @param doCount TODO\r
+     * @param rankId\r
+     *     limit to taxa having this rank, only applies if <code>taxonUuid = null</code>\r
+     * @return\r
+     */\r
+    private StringBuilder prepareListTaxonNodeAgentRelations(UUID taxonUuid, UUID classificationUuid, UUID agentUuid, UUID rankUuid, UUID relTypeUuid, boolean doCount) {\r
+\r
+        StringBuilder hql = new StringBuilder();\r
+\r
+        String join_fetch_mode = doCount ? "JOIN" : "JOIN FETCH";\r
+\r
+        if(doCount) {\r
+            hql.append("SELECT COUNT(tnar)");\r
+        } else {\r
+            hql.append("SELECT tnar");\r
+        }\r
+\r
+        hql.append(" FROM TaxonNodeAgentRelation AS tnar ");\r
+        if(taxonUuid != null) {\r
+            // taxonUuid is search filter, do not fetch it\r
+            hql.append(" JOIN tnar.taxonNode AS tn "\r
+                    + "  JOIN tn.taxon AS t ");\r
+        } else {\r
+            hql.append(join_fetch_mode)\r
+                .append(" tnar.taxonNode AS tn ")\r
+                .append(join_fetch_mode).append(" tn.taxon AS t ");\r
+            if(rankUuid != null) {\r
+                hql.append(" join t.name as n ");\r
+            }\r
+        }\r
+        hql.append(" JOIN tn.classification AS c ");\r
+        if(agentUuid != null) {\r
+            // agentUuid is search filter, do not fetch it\r
+//            hql.append(" join tnar.agent as a ");\r
+            hql.append(join_fetch_mode).append(" tnar.agent AS a ");\r
+        } else {\r
+            hql.append(join_fetch_mode).append(" tnar.agent AS a ");\r
+        }\r
+\r
+        hql.append(" WHERE (1 = 1) ");\r
+\r
+        if(relTypeUuid != null) {\r
+            hql.append(" AND tnar.type.uuid = :relTypeUuid ");\r
+        }\r
+\r
+        if(taxonUuid != null) {\r
+            hql.append(" AND t.uuid = :taxonUuid ");\r
+        } else {\r
+            if(rankUuid != null) {\r
+                hql.append(" AND n.rank.uuid = :rankUuid ");\r
+            }\r
+        }\r
+        if(classificationUuid != null) {\r
+            hql.append(" AND c.uuid = :classificationUuid ");\r
+        }\r
+        if(agentUuid != null) {\r
+            hql.append(" AND a.uuid = :agentUuid ");\r
+        }\r
+\r
+        hql.append(" ORDER BY a.titleCache");\r
+        return hql;\r
+    }\r
+    /**\r
+     * @param taxonUuid\r
+     * @param classificationUuid\r
+     * @param agentUuid\r
+     * @param relTypeUuid TODO\r
+     * @param query\r
+     * @param rankId TODO\r
+     */\r
+    private void setParamsForListTaxonNodeAgentRelations(UUID taxonUuid, UUID classificationUuid, UUID agentUuid,\r
+            UUID rankUuid, UUID relTypeUuid, Query query) {\r
+\r
+        if(taxonUuid != null) {\r
+            query.setParameter("taxonUuid", taxonUuid);\r
+        } else {\r
+            if(rankUuid != null) {\r
+                query.setParameter("rankUuid", rankUuid);\r
+            }\r
+        }\r
+        if(classificationUuid != null) {\r
+            query.setParameter("classificationUuid", classificationUuid);\r
+        }\r
+        if(agentUuid != null) {\r
+            query.setParameter("agentUuid", agentUuid);\r
+        }\r
+        if(relTypeUuid != null) {\r
+            query.setParameter("relTypeUuid", relTypeUuid);\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public Map<TreeIndex, Integer> rankOrderIndexForTreeIndex(List<TreeIndex> treeIndexes,\r
+            Integer minRankOrderIndex,\r
+            Integer maxRankOrderIndex) {\r
+\r
+        Map<TreeIndex, Integer> result = new HashMap<>();\r
+        if (treeIndexes == null || treeIndexes.isEmpty()){\r
+            return result;\r
+        }\r
+\r
+        String hql = " SELECT tn.treeIndex, r.orderIndex "\r
+                + " FROM TaxonNode tn "\r
+                + "     JOIN tn.taxon t "\r
+                + "     JOIN t.name n "\r
+                + "      JOIN n.rank r "\r
+                + " WHERE tn.treeIndex IN (:treeIndexes) ";\r
+        if (minRankOrderIndex != null){\r
+            hql += " AND r.orderIndex <= :minOrderIndex";\r
+        }\r
+        if (maxRankOrderIndex != null){\r
+            hql += " AND r.orderIndex >= :maxOrderIndex";\r
+        }\r
+\r
+        Query query =  getSession().createQuery(hql);\r
+        query.setParameterList("treeIndexes", TreeIndex.toString(treeIndexes));\r
+        if (minRankOrderIndex != null){\r
+            query.setParameter("minOrderIndex", minRankOrderIndex);\r
+        }\r
+        if (maxRankOrderIndex != null){\r
+            query.setParameter("maxOrderIndex", maxRankOrderIndex);\r
+        }\r
+\r
+        @SuppressWarnings("unchecked")\r
+        List<Object[]> list = query.list();\r
+        for (Object[] o : list){\r
+            result.put(TreeIndex.NewInstance((String)o[0]), (Integer)o[1]);\r
+        }\r
+        return result;\r
+    }\r
+\r
+    @Override\r
+    public Map<TreeIndex, UuidAndTitleCache<?>> taxonUuidsForTreeIndexes(Collection<TreeIndex> treeIndexes) {\r
+        Map<TreeIndex, UuidAndTitleCache<?>> result = new HashMap<>();\r
+        if (treeIndexes == null || treeIndexes.isEmpty()){\r
+            return result;\r
+        }\r
+\r
+        String hql =\r
+                  " SELECT tn.treeIndex, t.uuid, tnb.titleCache "\r
+                + " FROM TaxonNode tn JOIN tn.taxon t Join t.name tnb "\r
+                + " WHERE tn.treeIndex IN (:treeIndexes) ";\r
+        Query query =  getSession().createQuery(hql);\r
+        query.setParameterList("treeIndexes", TreeIndex.toString(treeIndexes));\r
+\r
+        @SuppressWarnings("unchecked")\r
+        List<Object[]> list = query.list();\r
+        for (Object[] o : list){\r
+            result.put(TreeIndex.NewInstance((String)o[0]), new UuidAndTitleCache<>((UUID)o[1], null, (String)o[2]));\r
+        }\r
+        return result;\r
+    }\r
+    \r
+    @Override\r
+    public TaxonNodeDto taxonNodeDtoParentRank(Classification classification, Rank rank, TaxonName name) {\r
+       \r
+       Set<Taxon> taxa = name.getTaxa();\r
+       TaxonNode node = null;\r
+       String treeIndex = null;\r
+       for (Taxon taxon:taxa) {\r
+               node = taxon.getTaxonNode(classification);\r
+               if (node != null) {\r
+                       break;\r
+               }\r
+       }\r
+       if (node != null) {\r
+               treeIndex = node.treeIndex();\r
+       }\r
+       Criteria nodeCrit = getSession().createCriteria(TaxonNode.class);\r
+       Criteria taxonCrit = nodeCrit.createCriteria("taxon");\r
+       Criteria nameCrit = taxonCrit.createCriteria("name");\r
+       nodeCrit.add(Restrictions.eq("classification", classification));\r
+       nameCrit.add(Restrictions.eq("rank", rank));\r
+       \r
+        ProjectionList projList = Projections.projectionList();\r
+         projList.add(Projections.property("treeIndex"));\r
+         projList.add(Projections.property("uuid"));\r
+\r
+        @SuppressWarnings("unchecked")\r
+        List<Object[]> list = nodeCrit.list();\r
+        UUID uuid = null;\r
+        if (list.size() > 0) {\r
+               for (Object o: list) {\r
+                        Object[] objectArray = (Object[]) o;\r
+                 uuid = (UUID)objectArray[1];\r
+                 String treeIndexParent = (String) objectArray[0];\r
+                 if (treeIndex.startsWith(treeIndexParent)) {\r
+                        node = load(uuid);\r
+                        break;\r
+                 }\r
+               }\r
+               TaxonNodeDto dto = new TaxonNodeDto(node);\r
+               return dto;\r
+        }\r
+        \r
+        return null;\r
+    }\r
+    \r
+    \r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     */\r
+    @Override\r
+    public int countSecundumForSubtreeAcceptedTaxa(TreeIndex subTreeIndex, Reference newSec,\r
+            boolean overwriteExisting, boolean includeSharedTaxa, boolean emptySecundumDetail) {\r
+        String queryStr = forSubtreeAcceptedQueryStr(includeSharedTaxa, subTreeIndex, false, SelectMode.COUNT);\r
+        if (!overwriteExisting){\r
+            queryStr += " AND t.sec IS NULL ";\r
+        }\r
+        return countResult(queryStr);\r
+    }\r
+    /**\r
+     * @param queryStr\r
+     * @return\r
+     */\r
+    private int countResult(String queryStr) {\r
+        Query query = getSession().createQuery(queryStr);\r
+        return ((Long)query.uniqueResult()).intValue();\r
+    }\r
+    /**\r
+     * {@inheritDoc}\r
+     */\r
+    @Override\r
+    public int countSecundumForSubtreeSynonyms(TreeIndex subTreeIndex, Reference newSec,\r
+            boolean overwriteExisting, boolean includeSharedTaxa, boolean emptySecundumDetail) {\r
+        String queryStr = forSubtreeSynonymQueryStr(includeSharedTaxa, subTreeIndex, false, SelectMode.COUNT);\r
+        if (!overwriteExisting){\r
+            queryStr += " AND syn.sec IS NULL ";\r
+        }\r
+        return countResult(queryStr);\r
+    }\r
+\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     */\r
+    //#3465\r
+    @Override\r
+    public Set<TaxonBase> setSecundumForSubtreeAcceptedTaxa(TreeIndex subTreeIndex, Reference newSec,\r
+            boolean overwriteExisting, boolean includeSharedTaxa, boolean emptyDetail, IProgressMonitor monitor) {\r
+        //for some reason this does not work, maybe because the listeners are not activated,\r
+        //but also the first taxon for some reason does not get updated in terms of secundum, but only by the update listener\r
+//        String where = "SELECT t.id FROM TaxonNode tn JOIN tn.taxon t " +\r
+//                " WHERE tn.treeIndex like '%s%%' ORDER BY t.id";\r
+//        where = String.format(where, subTreeIndex.toString());\r
+//        Query query1 = getSession().createQuery(where);\r
+//        List l = query1.list();\r
+//\r
+//        String hql = "UPDATE Taxon SET sec = :newSec, publish=false WHERE id IN (" + where + ")";\r
+//        Query query = getSession().createQuery(hql);\r
+//        query.setParameter("newSec", newSec);\r
+//        int n = query.executeUpdate();\r
+\r
+        String queryStr = forSubtreeAcceptedQueryStr(includeSharedTaxa, subTreeIndex, false, SelectMode.ID);\r
+        if (!overwriteExisting){\r
+            queryStr += " AND t.sec IS NULL ";\r
+        }\r
+        return setSecundum(newSec, emptyDetail, queryStr, monitor);\r
+\r
+    }\r
+\r
+    @Override\r
+    public Set<TaxonBase> setSecundumForSubtreeSynonyms(TreeIndex subTreeIndex, Reference newSec,\r
+            boolean overwriteExisting, boolean includeSharedTaxa, boolean emptyDetail, IProgressMonitor monitor) {\r
+\r
+        String queryStr = forSubtreeSynonymQueryStr(includeSharedTaxa, subTreeIndex, false, SelectMode.ID);\r
+        if (!overwriteExisting){\r
+            queryStr += " AND syn.sec IS NULL ";\r
+        }\r
+        return setSecundum(newSec, emptyDetail, queryStr, monitor);\r
+    }\r
+    /**\r
+     * @param newSec\r
+     * @param emptyDetail\r
+     * @param queryStr\r
+     * @param monitor\r
+     * @return\r
+     */\r
+    @SuppressWarnings("unchecked")\r
+    private <T extends TaxonBase<?>> Set<T> setSecundum(Reference newSec, boolean emptyDetail, String queryStr, IProgressMonitor monitor) {\r
+        Set<T> result = new HashSet<>();\r
+        Query query = getSession().createQuery(queryStr);\r
+        List<List<Integer>> partitionList = splitIdList(query.list(), DEFAULT_PARTITION_SIZE);\r
+        for (List<Integer> taxonIdList : partitionList){\r
+            List<TaxonBase> taxonList = taxonDao.loadList(taxonIdList, null, null);\r
+            for (TaxonBase taxonBase : taxonList){\r
+                if (taxonBase != null){\r
+                    taxonBase = taxonDao.load(taxonBase.getUuid());\r
+                    taxonBase.setSec(newSec);\r
+                    if (emptyDetail){\r
+                        taxonBase.setSecMicroReference(null);\r
+                    }\r
+                    result.add((T)CdmBase.deproxy(taxonBase));\r
+                    monitor.worked(1);\r
+                    if (monitor.isCanceled()){\r
+                        return result;\r
+                    }\r
+                }\r
+            }\r
+        }\r
+        return result;\r
+    }\r
+\r
+    private List<List<Integer>> splitIdList(List<Integer> idList, Integer size){\r
+        List<List<Integer>> result = new ArrayList<>();\r
+        for (int i = 0; (i*size)<idList.size(); i++) {\r
+            int upper = Math.min((i+1)*size, idList.size());\r
+            result.add(idList.subList(i*size, upper));\r
+        }\r
+        return result;\r
+    }\r
+\r
+    @Override\r
+    public int countPublishForSubtreeAcceptedTaxa(TreeIndex subTreeIndex, boolean publish, boolean includeSharedTaxa, boolean includeHybrids) {\r
+        String queryStr = forSubtreeAcceptedQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.COUNT);\r
+        queryStr += " AND t.publish != :publish ";\r
+        System.out.println(queryStr);\r
+        Query query = getSession().createQuery(queryStr);\r
+        query.setBoolean("publish", publish);\r
+        return ((Long)query.uniqueResult()).intValue();\r
+    }\r
+\r
+    @Override\r
+    public int countPublishForSubtreeSynonyms(TreeIndex subTreeIndex, boolean publish, boolean includeSharedTaxa, boolean includeHybrids) {\r
+        String queryStr = forSubtreeSynonymQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.COUNT);\r
+        queryStr += " AND syn.publish != :publish ";\r
+        Query query = getSession().createQuery(queryStr);\r
+        query.setBoolean("publish", publish);\r
+        return ((Long)query.uniqueResult()).intValue();\r
+    }\r
+\r
+    @Override\r
+    public Set<TaxonBase> setPublishForSubtreeAcceptedTaxa(TreeIndex subTreeIndex, boolean publish,\r
+            boolean includeSharedTaxa, boolean includeHybrids, IProgressMonitor monitor) {\r
+        String queryStr = forSubtreeAcceptedQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.ID);\r
+        queryStr += " AND t.publish != :publish ";\r
+        return setPublish(publish, queryStr, null, monitor);\r
+    }\r
+\r
+    @Override\r
+    public Set<TaxonBase> setPublishForSubtreeSynonyms(TreeIndex subTreeIndex, boolean publish,\r
+            boolean includeSharedTaxa, boolean includeHybrids, IProgressMonitor monitor) {\r
+        String queryStr = forSubtreeSynonymQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.ID);\r
+        queryStr += " AND syn.publish != :publish ";\r
+        return setPublish(publish, queryStr, null, monitor);\r
+    }\r
+    @Override\r
+    public int countPublishForSubtreeRelatedTaxa(TreeIndex subTreeIndex, boolean publish, boolean includeSharedTaxa, boolean includeHybrids) {\r
+        String queryStr = forSubtreeRelatedTaxaQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.COUNT);\r
+        queryStr += " AND relTax.publish != :publish ";\r
+        Query query = getSession().createQuery(queryStr);\r
+        query.setBoolean("publish", publish);\r
+        return ((Long)query.uniqueResult()).intValue();\r
+    }\r
+\r
+    @Override\r
+    public Set<TaxonBase> setPublishForSubtreeRelatedTaxa(TreeIndex subTreeIndex, boolean publish,\r
+            Set<UUID> relationTypes, boolean includeSharedTaxa, boolean includeHybrids,\r
+            IProgressMonitor monitor) {\r
+        String queryStr = forSubtreeRelatedTaxaQueryStr(includeSharedTaxa, subTreeIndex, !includeHybrids, SelectMode.ID);\r
+        queryStr += " AND relTax.publish != :publish ";\r
+        queryStr += " AND rel.type.uuid IN (:relTypeUuid)";\r
+        return setPublish(publish, queryStr, relationTypes, monitor);\r
+    }\r
+\r
+    private static final int DEFAULT_PARTITION_SIZE = 100;\r
+    /**\r
+     * @param publish\r
+     * @param queryStr\r
+     * @return\r
+     */\r
+    private <T extends TaxonBase<?>> Set<T> setPublish(boolean publish, String queryStr, Set<UUID> relTypeUuids, IProgressMonitor monitor) {\r
+        Set<T> result = new HashSet<>();\r
+        Query query = getSession().createQuery(queryStr);\r
+        query.setBoolean("publish", publish);\r
+        if (relTypeUuids != null && !relTypeUuids.isEmpty()){\r
+            query.setParameterList("relTypeUuid", relTypeUuids);\r
+        }\r
+        @SuppressWarnings("unchecked")\r
+        List<List<Integer>> partitionList = splitIdList(query.list(), DEFAULT_PARTITION_SIZE);\r
+        for (List<Integer> taxonIdList : partitionList){\r
+            List<TaxonBase> taxonList = taxonDao.loadList(taxonIdList, null, null);\r
+            for (TaxonBase taxonBase : taxonList){\r
+                if (taxonBase != null){\r
+                    taxonBase.setPublish(publish);\r
+                    result.add((T)CdmBase.deproxy(taxonBase));\r
+                    monitor.worked(1);\r
+                    if (monitor.isCanceled()){\r
+                        return result;\r
+                    }\r
+                }\r
+            }\r
+        }\r
+        return result;\r
+    }\r
+\r
+\r
+    /**\r
+     * @param includeSharedTaxa\r
+     * @param subTreeIndex\r
+     * @param includeHybrids\r
+     * @param includeSharedTaxa2\r
+     * @return\r
+     */\r
+    private String forSubtreeSynonymQueryStr(boolean includeSharedTaxa, TreeIndex subTreeIndex, boolean excludeHybrids, SelectMode mode) {\r
+        String queryStr = "SELECT " + mode.hql("syn")\r
+                + " FROM TaxonNode tn "\r
+                + "   JOIN tn.taxon t "\r
+                + "   JOIN t.synonyms syn  LEFT JOIN syn.name n ";\r
+        String whereStr = " tn.treeIndex LIKE '%1$s%%' ";\r
+        if (!includeSharedTaxa){\r
+            whereStr += " AND NOT EXISTS ("\r
+                    + "FROM TaxonNode tn2 WHERE tn2.taxon = t AND tn2.treeIndex not like '%1$s%%')  ";\r
+        }\r
+        whereStr = handleExcludeHybrids(whereStr, excludeHybrids, "syn");\r
+        queryStr += " WHERE " + String.format(whereStr, subTreeIndex.toString());\r
+\r
+        return queryStr;\r
+    }\r
+\r
+    /**\r
+     * @param queryStr\r
+     * @param excludeHybrids\r
+     * @return\r
+     */\r
+    private String handleExcludeHybrids(String whereStr, boolean excludeHybrids, String t) {\r
+        if(excludeHybrids){\r
+\r
+            String hybridWhere =  " AND (n is NULL OR "\r
+                    + " (n.monomHybrid=0 AND n.binomHybrid=0 "\r
+                    + "   AND n.trinomHybrid=0 AND n.hybridFormula=0 )) ";\r
+\r
+            whereStr += hybridWhere; //String.format(hybridWhere, t);\r
+        }\r
+        return whereStr;\r
+    }\r
+\r
+    private String forSubtreeRelatedTaxaQueryStr(boolean includeSharedTaxa, TreeIndex subTreeIndex,\r
+            boolean excludeHybrids, SelectMode mode) {\r
+        String queryStr = "SELECT " + mode.hql("relTax")\r
+                + " FROM TaxonNode tn "\r
+                + "   JOIN tn.taxon t "\r
+                + "   JOIN t.relationsToThisTaxon rel"\r
+                + "   JOIN rel.relatedFrom relTax  LEFT JOIN relTax.name n ";\r
+        String whereStr =" tn.treeIndex LIKE '%1$s%%' ";\r
+        if (!includeSharedTaxa){\r
+            //toTaxon should only be used in the given subtree\r
+            whereStr += " AND NOT EXISTS ("\r
+                    + "FROM TaxonNode tn2 WHERE tn2.taxon = t AND tn2.treeIndex not like '%1$s%%')  ";\r
+            //from taxon should not be used in another classification\r
+            whereStr += " AND NOT EXISTS ("\r
+                    + "FROM TaxonNode tn3 WHERE tn3.taxon = relTax AND tn3.treeIndex not like '%1$s%%')  ";\r
+            //fromTaxon should not be related as e.g. pro parte synonym or misapplication to\r
+            //another taxon which is not part of the subtree\r
+            //TODO and has not the publish state\r
+            whereStr += " AND NOT EXISTS ("\r
+                    + "FROM TaxonNode tn4 JOIN tn4.taxon t2 JOIN t2.relationsToThisTaxon rel2  "\r
+                    + "   WHERE rel2.relatedFrom = relTax AND tn4.treeIndex not like '%1$s%%' "\r
+                    + "         AND tn4.taxon.publish != :publish ) ";\r
+        }\r
+        whereStr = handleExcludeHybrids(whereStr, excludeHybrids, "relTax");\r
+        queryStr += " WHERE " + String.format(whereStr, subTreeIndex.toString());\r
+\r
+        return queryStr;\r
+    }\r
+\r
+    private enum SelectMode{\r
+        COUNT(" count(*) "),\r
+        ID ("id "),\r
+        UUID("uuid "),\r
+        FULL("");\r
+        private String hql;\r
+        SelectMode(String hql){\r
+            this.hql = hql;\r
+        }\r
+        public String hql(String prefix){\r
+            switch (this){\r
+            case ID:\r
+            case UUID:\r
+                return CdmUtils.Nz(prefix)+"." + hql;\r
+            case FULL:\r
+                return CdmUtils.Nz(prefix) + hql;\r
+            case COUNT:\r
+            default: return hql;\r
+            }\r
+\r
+        }\r
+    }\r
+\r
+    private String forSubtreeAcceptedQueryStr(boolean includeSharedTaxa, TreeIndex subTreeIndex, boolean excludeHybrids, SelectMode mode) {\r
+        String queryStr = "SELECT " + mode.hql("t")\r
+                + " FROM TaxonNode tn JOIN tn.taxon t LEFT JOIN t.name n ";\r
+        String whereStr = " tn.treeIndex like '%1$s%%' ";\r
+        if (!includeSharedTaxa){\r
+            whereStr += " AND NOT EXISTS ("\r
+                    + "FROM TaxonNode tn2 WHERE tn2.taxon = t AND tn2.treeIndex not like '%1$s%%')  ";\r
+        }\r
+        whereStr = handleExcludeHybrids(whereStr, excludeHybrids, "t");\r
+        queryStr += " WHERE " + String.format(whereStr, subTreeIndex.toString());\r
+\r
+        return queryStr;\r
+    }\r
+\r
+    @Override\r
+    public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(Classification classification, Integer limit, String pattern, boolean searchForClassifications) {\r
+        int classificationId = classification.getId();\r
+\r
+         String queryString =\r
+                   " SELECT nodes.uuid, nodes.id,  taxon.titleCache, taxon.name.rank " +\r
+                   " FROM TaxonNode AS nodes "\r
+                   + "    JOIN nodes.taxon as taxon " +\r
+                   " WHERE nodes.classification.id = " + classificationId ;\r
+         if (pattern != null){\r
+             if (pattern.equals("?")){\r
+                 limit = null;\r
+             } else{\r
+                 if (!pattern.endsWith("*")){\r
+                     pattern += "%";\r
+                 }\r
+                 pattern = pattern.replace("*", "%");\r
+                 pattern = pattern.replace("?", "%");\r
+                 queryString = queryString + " AND taxon.titleCache LIKE (:pattern) " ;\r
+             }\r
+         }\r
+\r
+         Query query = getSession().createQuery(queryString);\r
+\r
+         if (limit != null){\r
+             query.setMaxResults(limit);\r
+         }\r
+\r
+         if (pattern != null && !pattern.equals("?")){\r
+             query.setParameter("pattern", pattern);\r
+         }\r
+         @SuppressWarnings("unchecked")\r
+         List<Object[]> result = query.list();\r
+\r
+         if (searchForClassifications){\r
+             queryString = ""\r
+                     + " SELECT nodes.uuid, nodes.id,  nodes.classification.titleCache, NULLIF(1,1) "\r
+                     + " FROM TaxonNode AS nodes "\r
+                     + " WHERE nodes.classification.id = " + classificationId +\r
+                          " AND nodes.taxon IS NULL";\r
+             if (pattern != null){\r
+                 if (pattern.equals("?")){\r
+                     limit = null;\r
+                 } else{\r
+                     if (!pattern.endsWith("*")){\r
+                         pattern += "%";\r
+                     }\r
+                     pattern = pattern.replace("*", "%");\r
+                     pattern = pattern.replace("?", "%");\r
+                     queryString = queryString + " AND nodes.classification.titleCache LIKE (:pattern) " ;\r
+                 }\r
+             }\r
+             query = getSession().createQuery(queryString);\r
+\r
+             if (limit != null){\r
+                 query.setMaxResults(limit);\r
+             }\r
+\r
+             if (pattern != null && !pattern.equals("?")){\r
+                 query.setParameter("pattern", pattern);\r
+             }\r
+             @SuppressWarnings("unchecked")\r
+             List<Object[]> resultClassifications = query.list();\r
+\r
+             result.addAll(resultClassifications);\r
+         }\r
+\r
+         if(result.size() == 0){\r
+             return null;\r
+         }else{\r
+             List<UuidAndTitleCache<TaxonNode>> list = new ArrayList<>(result.size());\r
+             if (result.iterator().next().length == 4){\r
+                 Collections.sort(result, new UuidAndTitleCacheTaxonComparator());\r
+             }\r
+             for (Object object : result){\r
+                 Object[] objectArray = (Object[]) object;\r
+                 UUID uuid = (UUID)objectArray[0];\r
+                 Integer id = (Integer) objectArray[1];\r
+                 String titleCache = (String) objectArray[2];\r
+\r
+                 list.add(new UuidAndTitleCache<>(TaxonNode.class, uuid, id, titleCache));\r
+             }\r
+\r
+             return list;\r
+         }\r
+    }\r
+\r
 \r
 }\r