ref #9481: move the descriptions to the new taxon to avoid DDS when deleting the...
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / TaxonServiceImpl.java
index c3027d2409e3bc60b5292208b7534f9c09d2e6d8..8162c1fbf33f0b85c83f8e24c2e34ba7f774edca 100644 (file)
@@ -11,6 +11,7 @@ package eu.etaxonomy.cdm.api.service;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -22,7 +23,7 @@ import java.util.UUID;
 
 import javax.persistence.EntityNotFoundException;
 
-import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 import org.apache.lucene.queryparser.classic.ParseException;
 import org.apache.lucene.search.BooleanClause.Occur;
@@ -33,6 +34,7 @@ import org.apache.lucene.search.SortField;
 import org.apache.lucene.search.grouping.TopGroups;
 import org.apache.lucene.search.join.ScoreMode;
 import org.apache.lucene.util.BytesRef;
+import org.hibernate.criterion.Criterion;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -47,10 +49,12 @@ import eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator;
 import eu.etaxonomy.cdm.api.service.dto.IdentifiedEntityDTO;
 import eu.etaxonomy.cdm.api.service.dto.IncludedTaxaDTO;
 import eu.etaxonomy.cdm.api.service.dto.MarkedEntityDTO;
+import eu.etaxonomy.cdm.api.service.dto.TaxonRelationshipsDTO;
 import eu.etaxonomy.cdm.api.service.exception.DataChangeNoRollbackException;
 import eu.etaxonomy.cdm.api.service.exception.HomotypicalGroupChangeException;
 import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;
 import eu.etaxonomy.cdm.api.service.pager.Pager;
+import eu.etaxonomy.cdm.api.service.pager.impl.AbstractPagerImpl;
 import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
 import eu.etaxonomy.cdm.api.service.search.ILuceneIndexToolProvider;
 import eu.etaxonomy.cdm.api.service.search.ISearchResultBuilder;
@@ -63,25 +67,25 @@ import eu.etaxonomy.cdm.api.service.search.SearchResult;
 import eu.etaxonomy.cdm.api.service.search.SearchResultBuilder;
 import eu.etaxonomy.cdm.api.service.util.TaxonRelationshipEdge;
 import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
+import eu.etaxonomy.cdm.compare.taxon.HomotypicGroupTaxonComparator;
+import eu.etaxonomy.cdm.compare.taxon.TaxonComparator;
+import eu.etaxonomy.cdm.exception.UnpublishedException;
 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
 import eu.etaxonomy.cdm.hibernate.search.AcceptedTaxonBridge;
-import eu.etaxonomy.cdm.hibernate.search.DefinedTermBaseClassBridge;
 import eu.etaxonomy.cdm.hibernate.search.GroupByTaxonClassBridge;
-import eu.etaxonomy.cdm.hibernate.search.MultilanguageTextFieldBridge;
 import eu.etaxonomy.cdm.model.CdmBaseType;
 import eu.etaxonomy.cdm.model.common.Annotation;
 import eu.etaxonomy.cdm.model.common.AnnotationType;
 import eu.etaxonomy.cdm.model.common.CdmBase;
-import eu.etaxonomy.cdm.model.common.DefinedTerm;
 import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
 import eu.etaxonomy.cdm.model.common.IdentifiableSource;
 import eu.etaxonomy.cdm.model.common.Language;
 import eu.etaxonomy.cdm.model.common.MarkerType;
-import eu.etaxonomy.cdm.model.common.OriginalSourceType;
 import eu.etaxonomy.cdm.model.common.RelationshipBase.Direction;
 import eu.etaxonomy.cdm.model.description.CommonTaxonName;
 import eu.etaxonomy.cdm.model.description.DescriptionBase;
 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
+import eu.etaxonomy.cdm.model.description.DescriptionElementSource;
 import eu.etaxonomy.cdm.model.description.Distribution;
 import eu.etaxonomy.cdm.model.description.Feature;
 import eu.etaxonomy.cdm.model.description.IIdentificationKey;
@@ -93,8 +97,7 @@ import eu.etaxonomy.cdm.model.description.TaxonInteraction;
 import eu.etaxonomy.cdm.model.description.TaxonNameDescription;
 import eu.etaxonomy.cdm.model.location.NamedArea;
 import eu.etaxonomy.cdm.model.media.Media;
-import eu.etaxonomy.cdm.model.media.MediaRepresentation;
-import eu.etaxonomy.cdm.model.media.MediaUtils;
+import eu.etaxonomy.cdm.model.metadata.SecReferenceHandlingEnum;
 import eu.etaxonomy.cdm.model.name.HomotypicalGroup;
 import eu.etaxonomy.cdm.model.name.IZoologicalName;
 import eu.etaxonomy.cdm.model.name.Rank;
@@ -103,9 +106,9 @@ import eu.etaxonomy.cdm.model.name.TaxonNameFactory;
 import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
 import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
+import eu.etaxonomy.cdm.model.reference.OriginalSourceType;
 import eu.etaxonomy.cdm.model.reference.Reference;
 import eu.etaxonomy.cdm.model.taxon.Classification;
-import eu.etaxonomy.cdm.model.taxon.HomotypicGroupTaxonComparator;
 import eu.etaxonomy.cdm.model.taxon.ITaxonTreeNode;
 import eu.etaxonomy.cdm.model.taxon.Synonym;
 import eu.etaxonomy.cdm.model.taxon.SynonymType;
@@ -114,7 +117,8 @@ import eu.etaxonomy.cdm.model.taxon.TaxonBase;
 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
 import eu.etaxonomy.cdm.model.taxon.TaxonRelationship;
 import eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType;
-import eu.etaxonomy.cdm.persistence.dao.common.ICdmGenericDao;
+import eu.etaxonomy.cdm.model.term.DefinedTerm;
+import eu.etaxonomy.cdm.persistence.dao.common.Restriction;
 import eu.etaxonomy.cdm.persistence.dao.initializer.AbstractBeanInitializer;
 import eu.etaxonomy.cdm.persistence.dao.name.ITaxonNameDao;
 import eu.etaxonomy.cdm.persistence.dao.occurrence.IOccurrenceDao;
@@ -163,10 +167,10 @@ public class TaxonServiceImpl
     private ITaxonNodeService nodeService;
 
     @Autowired
-    private ICdmGenericDao genericDao;
+    private IDescriptionService descriptionService;
 
     @Autowired
-    private IDescriptionService descriptionService;
+    private IReferenceService referenceService;
 //
 //    @Autowired
 //    private IOrderedTermVocabularyDao orderedVocabularyDao;
@@ -190,10 +194,6 @@ public class TaxonServiceImpl
 
 // ****************************** METHODS ********************************/
 
-
-    /**
-     * {@inheritDoc}
-     */
     @Override
     public TaxonBase load(UUID uuid, boolean includeUnpublished, List<String> propertyPaths) {
         return dao.load(uuid, includeUnpublished, propertyPaths);
@@ -206,34 +206,131 @@ public class TaxonServiceImpl
 
     @Override
     @Transactional(readOnly = false)
-    public UpdateResult swapSynonymAndAcceptedTaxon(Synonym synonym, Taxon acceptedTaxon){
-       UpdateResult result = new UpdateResult();
-        TaxonName synonymName = synonym.getName();
+    public UpdateResult swapSynonymAndAcceptedTaxon(Synonym synonym, Taxon acceptedTaxon, boolean setNameInSource){
+        UpdateResult result = new UpdateResult();
+       acceptedTaxon.removeSynonym(synonym);
+       TaxonName synonymName = synonym.getName();
+       TaxonName taxonName = HibernateProxyHelper.deproxy(acceptedTaxon.getName());
+
+       boolean sameHomotypicGroup = synonymName.getHomotypicalGroup().equals(taxonName.getHomotypicalGroup());
+
         synonymName.removeTaxonBase(synonym);
-        TaxonName taxonName = acceptedTaxon.getName();
-        taxonName.removeTaxonBase(acceptedTaxon);
-
-        synonym.setName(taxonName);
-        synonym.setTitleCache(null, false);
-        synonym.getTitleCache();
-        acceptedTaxon.setName(synonymName);
-        acceptedTaxon.setTitleCache(null, false);
-        acceptedTaxon.getTitleCache();
-        saveOrUpdate(synonym);
-        saveOrUpdate(acceptedTaxon);
-        result.addUpdatedObject(acceptedTaxon);
-        result.addUpdatedObject(synonym);
-               return result;
 
-        // the accepted taxon needs a new uuid because the concept has changed
-        // FIXME this leads to an error "HibernateException: immutable natural identifier of an instance of eu.etaxonomy.cdm.model.taxon.Taxon was altered"
-        //acceptedTaxon.setUuid(UUID.randomUUID());
-    }
+        //taxonName.removeTaxonBase(acceptedTaxon);
+
+        List<Synonym> synonyms = new ArrayList<>();
+        for (Synonym syn: acceptedTaxon.getSynonyms()){
+            syn = HibernateProxyHelper.deproxy(syn, Synonym.class);
+            synonyms.add(syn);
+        }
+        for (Synonym syn: synonyms){
+            acceptedTaxon.removeSynonym(syn);
+        }
+        Taxon newTaxon = acceptedTaxon.clone();
+        newTaxon.getDescriptions().clear();
+        Set<TaxonDescription> descriptionsToCopy = new HashSet<>();
+        for (TaxonDescription desc: acceptedTaxon.getDescriptions()){
+            descriptionsToCopy.add(desc);
+        }
+        for (TaxonDescription description: descriptionsToCopy){
+            newTaxon.addDescription(description);
+        }
+        newTaxon.setName(synonymName);
+        newTaxon.setSec(synonym.getSec());
+        newTaxon.setPublish(synonym.isPublish());
+        for (Synonym syn: synonyms){
+            if (!syn.getName().equals(newTaxon.getName())){
+                newTaxon.addSynonym(syn, syn.getType());
+            }
+        }
 
+        //move all data to new taxon
+        //Move Taxon RelationShips to new Taxon
+        for(TaxonRelationship taxonRelationship : newTaxon.getTaxonRelations()){
+            newTaxon.removeTaxonRelation(taxonRelationship);
+        }
+
+        for(TaxonRelationship taxonRelationship : acceptedTaxon.getTaxonRelations()){
+            Taxon fromTaxon = HibernateProxyHelper.deproxy(taxonRelationship.getFromTaxon());
+            Taxon toTaxon = HibernateProxyHelper.deproxy(taxonRelationship.getToTaxon());
+            if (fromTaxon == acceptedTaxon){
+                newTaxon.addTaxonRelation(taxonRelationship.getToTaxon(), taxonRelationship.getType(),
+                        taxonRelationship.getCitation(), taxonRelationship.getCitationMicroReference());
+
+            }else if(toTaxon == acceptedTaxon){
+               fromTaxon.addTaxonRelation(newTaxon, taxonRelationship.getType(),
+                        taxonRelationship.getCitation(), taxonRelationship.getCitationMicroReference());
+               saveOrUpdate(fromTaxon);
+
+            }else{
+                logger.warn("Taxon is not part of its own Taxonrelationship");
+            }
+            // Remove old relationships
+
+            fromTaxon.removeTaxonRelation(taxonRelationship);
+            toTaxon.removeTaxonRelation(taxonRelationship);
+            taxonRelationship.setToTaxon(null);
+            taxonRelationship.setFromTaxon(null);
+        }
+
+        //Move descriptions to new taxon
+        List<TaxonDescription> descriptions = new ArrayList<TaxonDescription>( newTaxon.getDescriptions()); //to avoid concurrent modification errors (newAcceptedTaxon.addDescription() modifies also oldtaxon.descritpions())
+        for(TaxonDescription description : descriptions){
+            String message = "Description copied from former accepted taxon: %s (Old title: %s)";
+            message = String.format(message, acceptedTaxon.getTitleCache(), description.getTitleCache());
+            description.setTitleCache(message, true);
+            if(setNameInSource){
+                for (DescriptionElementBase element: description.getElements()){
+                    for (DescriptionElementSource source: element.getSources()){
+                        if (source.getNameUsedInSource() == null){
+                            source.setNameUsedInSource(taxonName);
+                        }
+                    }
+                }
+            }
+//            //oldTaxon.removeDescription(description, false);
+ //           newTaxon.addDescription(description);
+        }
+        List<TaxonNode> nodes = new ArrayList<>(acceptedTaxon.getTaxonNodes());
+        for (TaxonNode node: nodes){
+            node = HibernateProxyHelper.deproxy(node, TaxonNode.class);
+            TaxonNode parent = node.getParent();
+            acceptedTaxon.removeTaxonNode(node);
+            node.setTaxon(newTaxon);
+            if (parent != null){
+                parent.addChildNode(node, null, null);
+            }
+
+        }
+        Synonym newSynonym = synonym.clone();
+        newSynonym.setName(taxonName);
+        newSynonym.setSec(acceptedTaxon.getSec());
+        newSynonym.setPublish(acceptedTaxon.isPublish());
+        if (sameHomotypicGroup){
+            newTaxon.addSynonym(newSynonym, SynonymType.HOMOTYPIC_SYNONYM_OF());
+        }else{
+            newTaxon.addSynonym(newSynonym, SynonymType.HETEROTYPIC_SYNONYM_OF());
+        }
+
+        saveOrUpdate(newSynonym);
+        saveOrUpdate(newTaxon);
+        TaxonDeletionConfigurator conf = new TaxonDeletionConfigurator();
+        conf.setDeleteNameIfPossible(false);
+        SynonymDeletionConfigurator confSyn = new SynonymDeletionConfigurator();
+        confSyn.setDeleteNameIfPossible(false);
+        result.setCdmEntity(newTaxon);
+
+        DeleteResult deleteResult = deleteTaxon(acceptedTaxon.getUuid(), conf, null);
+        if (synonym.isPersited()){
+            deleteResult.includeResult(deleteSynonym(synonym, confSyn));
+        }
+        result.includeResult(deleteResult);
+               return result;
+    }
 
     @Override
     @Transactional(readOnly = false)
-    public UpdateResult changeSynonymToAcceptedTaxon(Synonym synonym, Taxon acceptedTaxon, boolean deleteSynonym) {
+    public UpdateResult changeSynonymToAcceptedTaxon(Synonym synonym, Taxon acceptedTaxon, Reference newSecRef, String microRef, boolean deleteSynonym) {
         UpdateResult result = new UpdateResult();
         TaxonName acceptedName = acceptedTaxon.getName();
         TaxonName synonymName = synonym.getName();
@@ -246,8 +343,8 @@ public class TaxonServiceImpl
             result.setAbort();
             return result;
         }
-
-        Taxon newAcceptedTaxon = Taxon.NewInstance(synonymName, acceptedTaxon.getSec());
+        Taxon newAcceptedTaxon = Taxon.NewInstance(synonymName, newSecRef, microRef);
+        newAcceptedTaxon.setPublish(synonym.isPublish());
         dao.save(newAcceptedTaxon);
         result.setCdmEntity(newAcceptedTaxon);
         SynonymType relTypeForGroup = SynonymType.HOMOTYPIC_SYNONYM_OF();
@@ -284,23 +381,52 @@ public class TaxonServiceImpl
     public UpdateResult changeSynonymToAcceptedTaxon(UUID synonymUuid,
             UUID acceptedTaxonUuid,
             UUID newParentNodeUuid,
+            UUID newSec,
+            String microReference,
+            SecReferenceHandlingEnum secHandling,
             boolean deleteSynonym)  {
         UpdateResult result = new UpdateResult();
         Synonym synonym = CdmBase.deproxy(dao.load(synonymUuid), Synonym.class);
         Taxon acceptedTaxon = CdmBase.deproxy(dao.load(acceptedTaxonUuid), Taxon.class);
-        result =  changeSynonymToAcceptedTaxon(synonym, acceptedTaxon, deleteSynonym);
-        Taxon newTaxon = (Taxon)result.getCdmEntity();
         TaxonNode newParentNode = taxonNodeDao.load(newParentNodeUuid);
+        Reference newSecRef = null;
+        switch (secHandling){
+            case AlwaysDelete:
+                newSecRef = null;
+                break;
+            case KeepAlways:
+                newSecRef = synonym.getSec();
+                break;
+            case UseNewParentSec:
+                newSecRef = newParentNode.getTaxon() != null? newParentNode.getTaxon().getSec(): null;
+                break;
+            case KeepWhenSame:
+                Reference parentSec = newParentNode.getTaxon() != null? newParentNode.getTaxon().getSec(): null;
+                Reference synSec = synonym.getSec();
+                if (parentSec != null && synSec != null && parentSec.equals(synSec)){
+                    newSecRef = synonym.getSec();
+                }else{
+                    newSecRef = CdmBase.deproxy(referenceService.load(newSec));
+                }
+            case WarningSelect:
+                newSecRef = CdmBase.deproxy(referenceService.load(newSec));
+
+            default:
+                break;
+        }
+
+
+        result =  changeSynonymToAcceptedTaxon(synonym, acceptedTaxon, newSecRef, microReference, deleteSynonym);
+        Taxon newTaxon = (Taxon)result.getCdmEntity();
+
         TaxonNode newNode = newParentNode.addChildTaxon(newTaxon, null, null);
         taxonNodeDao.save(newNode);
         result.addUpdatedObject(newTaxon);
         result.addUpdatedObject(acceptedTaxon);
         result.setCdmEntity(newNode);
         return result;
-    }
-
-
 
+    }
 
     @Override
     @Transactional(readOnly = false)
@@ -338,6 +464,7 @@ public class TaxonServiceImpl
 */
         // Create a taxon with synonym name
         Taxon fromTaxon = Taxon.NewInstance(synonymName, null);
+        fromTaxon.setPublish(synonym.isPublish());
         save(fromTaxon);
         fromTaxon.setAppendedPhrase(synonym.getAppendedPhrase());
 
@@ -397,16 +524,15 @@ public class TaxonServiceImpl
             SynonymType relType = isHomotypicToTaxon? SynonymType.HOMOTYPIC_SYNONYM_OF() : SynonymType.HETEROTYPIC_SYNONYM_OF();
             targetTaxon.addSynonym(synonym, relType);
         }
-
     }
 
     @Override
     @Transactional(readOnly = false)
-    public void updateTitleCache(Class<? extends TaxonBase> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<TaxonBase> cacheStrategy, IProgressMonitor monitor) {
+    public UpdateResult updateCaches(Class<? extends TaxonBase> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<TaxonBase> cacheStrategy, IProgressMonitor monitor) {
         if (clazz == null){
             clazz = TaxonBase.class;
         }
-        super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);
+        return super.updateCachesImpl(clazz, stepSize, cacheStrategy, monitor);
     }
 
     @Override
@@ -416,27 +542,25 @@ public class TaxonServiceImpl
     }
 
     @Override
-    public Pager<TaxonBase> findTaxaByName(Class<? extends TaxonBase> clazz, String uninomial, String infragenericEpithet, String specificEpithet,     String infraspecificEpithet, String authorship, Rank rank, Integer pageSize,Integer pageNumber) {
-        long numberOfResults = dao.countTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank);
+    public <T extends TaxonBase> Pager<T> findTaxaByName(Class<T> clazz, String uninomial,     String infragenericEpithet, String specificEpithet,
+            String infraspecificEpithet, String authorshipCache, Rank rank, Integer pageSize,Integer pageNumber, List<String> propertyPaths) {
+        long numberOfResults = dao.countTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, authorshipCache, rank);
 
-        List<TaxonBase> results = new ArrayList<>();
+        List<T> results = new ArrayList<>();
         if(numberOfResults > 0) { // no point checking again
-            results = dao.findTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, authorship, rank, pageSize, pageNumber);
+            results = dao.findTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, authorshipCache, rank,
+                    pageSize, pageNumber, propertyPaths);
         }
 
         return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
     }
 
     @Override
-    public List<TaxonBase> listTaxaByName(Class<? extends TaxonBase> clazz, String uninomial, String infragenericEpithet, String specificEpithet,      String infraspecificEpithet, String authorship, Rank rank, Integer pageSize,Integer pageNumber) {
-        long numberOfResults = dao.countTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, rank);
+    public <T extends TaxonBase> List<T> listTaxaByName(Class<T> clazz, String uninomial, String infragenericEpithet, String specificEpithet,
+            String infraspecificEpithet, String authorshipCache, Rank rank, Integer pageSize,Integer pageNumber, List<String> propertyPaths) {
 
-        List<TaxonBase> results = new ArrayList<>();
-        if(numberOfResults > 0) { // no point checking again
-            results = dao.findTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infraspecificEpithet, authorship, rank, pageSize, pageNumber);
-        }
-
-        return results;
+        return findTaxaByName(clazz, uninomial, infragenericEpithet, specificEpithet, infragenericEpithet, authorshipCache, rank,
+                pageSize, pageNumber, propertyPaths).getRecords();
     }
 
     @Override
@@ -489,26 +613,46 @@ public class TaxonServiceImpl
 
     @Override
     public List<TaxonRelationship> listTaxonRelationships(Set<TaxonRelationshipType> types,
-            Integer pageSize, Integer pageStart, List<OrderHint> orderHints, List<String> propertyPaths) {
-        Long numberOfResults = dao.countTaxonRelationships(types);
+            Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
 
+        Long numberOfResults = dao.countTaxonRelationships(types);
         List<TaxonRelationship> results = new ArrayList<>();
         if(numberOfResults > 0) {
-            results = dao.getTaxonRelationships(types, pageSize, pageStart, orderHints, propertyPaths);
+            results = dao.getTaxonRelationships(types, pageSize, pageNumber, orderHints, propertyPaths);
         }
         return results;
     }
 
     @Override
-    public Taxon findAcceptedTaxonFor(UUID synonymUuid, UUID classificationUuid, List<String> propertyPaths){
+    public <S extends TaxonBase> Pager<S> page(Class<S> clazz, List<Restriction<?>> restrictions, Integer pageSize,
+            Integer pageIndex, List<OrderHint> orderHints, List<String> propertyPaths) {
+        return page(clazz, restrictions, pageSize, pageIndex, orderHints, propertyPaths, INCLUDE_UNPUBLISHED);
+    }
+
+    @Override
+    public <S extends TaxonBase> Pager<S> page(Class<S> clazz, List<Restriction<?>> restrictions, Integer pageSize,
+            Integer pageIndex, List<OrderHint> orderHints, List<String> propertyPaths, boolean includeUnpublished) {
 
-        Taxon result = null;
-        Long count = 0l;
+        List<S> records;
+        long resultSize = dao.count(clazz, restrictions);
+        if(AbstractPagerImpl.hasResultsInRange(resultSize, pageIndex, pageSize)){
+            records = dao.list(clazz, restrictions, pageSize, pageIndex, orderHints, propertyPaths, includeUnpublished);
+        } else {
+            records = new ArrayList<>();
+        }
+        Pager<S> pager = new DefaultPagerImpl<>(pageIndex, resultSize, pageSize, records);
+        return pager;
+    }
+
+    @Override
+    public Taxon findAcceptedTaxonFor(UUID synonymUuid, UUID classificationUuid,
+            boolean includeUnpublished, List<String> propertyPaths) throws UnpublishedException{
 
         Synonym synonym = null;
 
         try {
             synonym = (Synonym) dao.load(synonymUuid);
+            checkPublished(synonym, includeUnpublished, "Synoym is unpublished.");
         } catch (ClassCastException e){
             throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid + " is not a Synonmy");
         } catch (NullPointerException e){
@@ -518,24 +662,26 @@ public class TaxonServiceImpl
         Classification classificationFilter = null;
         if(classificationUuid != null){
             try {
-            classificationFilter = classificationDao.load(classificationUuid);
+                classificationFilter = classificationDao.load(classificationUuid);
             } catch (NullPointerException e){
+                //TODO not sure, why an NPE should be thrown in the above load method
                 throw new EntityNotFoundException("No Classification entity found for " + classificationUuid);
             }
             if(classificationFilter == null){
-
+                throw new EntityNotFoundException("No Classification entity found for " + classificationUuid);
             }
         }
 
-        count = dao.countAcceptedTaxonFor(synonym, classificationFilter) ;
+        long count = dao.countAcceptedTaxonFor(synonym, classificationFilter) ;
         if(count > 0){
-            result = dao.acceptedTaxonFor(synonym, classificationFilter, propertyPaths);
+            Taxon result = dao.acceptedTaxonFor(synonym, classificationFilter, propertyPaths);
+            checkPublished(result, includeUnpublished, "Accepted taxon unpublished");
+            return result;
+        }else{
+            return null;
         }
-
-        return result;
     }
 
-
     @Override
     public Set<Taxon> listRelatedTaxa(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, Integer maxDepth,
             boolean includeUnpublished, Integer limit, Integer start, List<String> propertyPaths) {
@@ -546,7 +692,6 @@ public class TaxonServiceImpl
         return relatedTaxa;
     }
 
-
     /**
      * Recursively collect related taxa for the given <code>taxon</code> . The returned list will also include the
      *  <code>taxon</code> supplied as parameter.
@@ -574,7 +719,8 @@ public class TaxonServiceImpl
         if(logger.isDebugEnabled()){
             logger.debug("collecting related taxa for " + taxon + " with maxDepth=" + maxDepth);
         }
-        List<TaxonRelationship> taxonRelationships = dao.getTaxonRelationships(taxon, null, includeUnpublished, null, null, null, null, null);
+        List<TaxonRelationship> taxonRelationships = dao.getTaxonRelationships(taxon,
+                (Set<TaxonRelationshipType>)null, includeUnpublished, null, null, null, null, null);
         for (TaxonRelationship taxRel : taxonRelationships) {
 
             // skip invalid data
@@ -583,7 +729,7 @@ public class TaxonServiceImpl
             }
             // filter by includeRelationships
             for (TaxonRelationshipEdge relationshipEdgeFilter : includeRelationships) {
-                if ( relationshipEdgeFilter.getTaxonRelationshipTypes().equals(taxRel.getType()) ) {
+                if ( relationshipEdgeFilter.getRelationshipTypes().equals(taxRel.getType()) ) {
                     if (relationshipEdgeFilter.getDirections().contains(Direction.relatedTo) && !taxa.contains(taxRel.getToTaxon())) {
                         if(logger.isDebugEnabled()){
                             logger.debug(maxDepth + ": " + taxon.getTitleCache() + " --[" + taxRel.getType().getLabel() + "]--> " + taxRel.getToTaxon().getTitleCache());
@@ -626,7 +772,6 @@ public class TaxonServiceImpl
         taxon = (Taxon)dao.load(taxon.getUuid(), propertyPaths);
         HomotypicGroupTaxonComparator comparator = new HomotypicGroupTaxonComparator(taxon);
 
-
         //homotypic
         result.add(taxon.getHomotypicSynonymsByHomotypicGroup(comparator));
 
@@ -637,7 +782,6 @@ public class TaxonServiceImpl
         }
 
         return result;
-
     }
 
     @Override
@@ -665,7 +809,8 @@ public class TaxonServiceImpl
         if (config.isDoSynonyms() || config.isDoTaxa() || config.isDoNamesWithoutTaxa() || config.isDoTaxaByCommonNames()){
                return dao.getTaxaByNameForEditor(config.isDoTaxa(), config.isDoSynonyms(), config.isDoNamesWithoutTaxa(),
                        config.isDoMisappliedNames(), config.isDoTaxaByCommonNames(), config.isIncludeUnpublished(),
-                       config.getTitleSearchStringSqlized(), config.getClassification(), config.getMatchMode(), config.getNamedAreas(), config.getOrder());
+                       config.getTitleSearchStringSqlized(), config.getClassification(), config.getSubtree(),
+                       config.getMatchMode(), config.getNamedAreas(), config.getOrder());
         }else{
             return new ArrayList<>();
         }
@@ -674,6 +819,7 @@ public class TaxonServiceImpl
     @Override
     public Pager<IdentifiableEntity> findTaxaAndNames(IFindTaxaAndNamesConfigurator configurator) {
 
+        @SuppressWarnings("rawtypes")
         List<IdentifiableEntity> results = new ArrayList<>();
         long numberOfResults = 0; // overall number of results (as opposed to number of results per page)
         List<TaxonBase> taxa = null;
@@ -681,30 +827,28 @@ public class TaxonServiceImpl
         // Taxa and synonyms
         long numberTaxaResults = 0L;
 
-
         List<String> propertyPath = new ArrayList<>();
         if(configurator.getTaxonPropertyPath() != null){
             propertyPath.addAll(configurator.getTaxonPropertyPath());
         }
 
-
-       if (configurator.isDoMisappliedNames() || configurator.isDoSynonyms() || configurator.isDoTaxa() || configurator.isDoTaxaByCommonNames()){
+        if (configurator.isDoMisappliedNames() || configurator.isDoSynonyms() || configurator.isDoTaxa() || configurator.isDoTaxaByCommonNames()){
             if(configurator.getPageSize() != null){ // no point counting if we need all anyway
                 numberTaxaResults =
                     dao.countTaxaByName(configurator.isDoTaxa(),configurator.isDoSynonyms(), configurator.isDoMisappliedNames(),
                         configurator.isDoTaxaByCommonNames(), configurator.isDoIncludeAuthors(), configurator.getTitleSearchStringSqlized(),
-                        configurator.getClassification(), configurator.getMatchMode(),
+                        configurator.getClassification(), configurator.getSubtree(), configurator.getMatchMode(),
                         configurator.getNamedAreas(), configurator.isIncludeUnpublished());
             }
 
             if(configurator.getPageSize() == null || numberTaxaResults > configurator.getPageSize() * configurator.getPageNumber()){ // no point checking again if less results
                 taxa = dao.getTaxaByName(configurator.isDoTaxa(), configurator.isDoSynonyms(),
                     configurator.isDoMisappliedNames(), configurator.isDoTaxaByCommonNames(), configurator.isDoIncludeAuthors(),
-                    configurator.getTitleSearchStringSqlized(), configurator.getClassification(),
+                    configurator.getTitleSearchStringSqlized(), configurator.getClassification(), configurator.getSubtree(),
                     configurator.getMatchMode(), configurator.getNamedAreas(), configurator.isIncludeUnpublished(),
                     configurator.getOrder(), configurator.getPageSize(), configurator.getPageNumber(), propertyPath);
             }
-       }
+        }
 
         if (logger.isDebugEnabled()) { logger.debug(numberTaxaResults + " matching taxa counted"); }
 
@@ -718,7 +862,7 @@ public class TaxonServiceImpl
         if (configurator.isDoNamesWithoutTaxa()) {
             int numberNameResults = 0;
 
-            List<? extends TaxonName> names =
+            List<TaxonName> names =
                 nameDao.findByName(configurator.isDoIncludeAuthors(), configurator.getTitleSearchStringSqlized(), configurator.getMatchMode(),
                         configurator.getPageSize(), configurator.getPageNumber(), null, configurator.getTaxonNamePropertyPath());
             if (logger.isDebugEnabled()) { logger.debug(names.size() + " matching name(s) found"); }
@@ -741,25 +885,6 @@ public class TaxonServiceImpl
         return dao.getUuidAndTitleCache(limit, pattern);
     }
 
-    @Override
-    public List<MediaRepresentation> getAllMedia(Taxon taxon, int size, int height, int widthOrDuration, String[] mimeTypes){
-        List<MediaRepresentation> medRep = new ArrayList<>();
-        taxon = (Taxon)dao.load(taxon.getUuid());
-        Set<TaxonDescription> descriptions = taxon.getDescriptions();
-        for (TaxonDescription taxDesc: descriptions){
-            Set<DescriptionElementBase> elements = taxDesc.getElements();
-            for (DescriptionElementBase descElem: elements){
-                for(Media media : descElem.getMedia()){
-
-                    //find the best matching representation
-                    medRep.add(MediaUtils.findBestMatchingRepresentation(media, null, size, height, widthOrDuration, mimeTypes));
-
-                }
-            }
-        }
-        return medRep;
-    }
-
     @Override
     public List<Media> listTaxonDescriptionMedia(Taxon taxon, Set<TaxonRelationshipEdge> includeRelationships, boolean limitToGalleries, List<String> propertyPath){
         return listMedia(taxon, includeRelationships, limitToGalleries, true, false, false, propertyPath);
@@ -822,19 +947,20 @@ public class TaxonServiceImpl
 
         if(includeOccurrences != null && includeOccurrences) {
             logger.trace("listMedia() - includeOccurrences");
+            @SuppressWarnings("rawtypes")
             Set<SpecimenOrObservationBase> specimensOrObservations = new HashSet<>();
             // --- Specimens
             for (Taxon t : taxa) {
                 specimensOrObservations.addAll(occurrenceDao.listByAssociatedTaxon(null, t, null, null, null, null));
             }
-            for (SpecimenOrObservationBase occurrence : specimensOrObservations) {
+            for (SpecimenOrObservationBase<?> occurrence : specimensOrObservations) {
 
 //             direct media removed from specimen #3597
 //              taxonMedia.addAll(occurrence.getMedia());
 
                 // SpecimenDescriptions
                 Set<SpecimenDescription> specimenDescriptions = occurrence.getSpecimenDescriptions();
-                for (DescriptionBase specimenDescription : specimenDescriptions) {
+                for (DescriptionBase<?> specimenDescription : specimenDescriptions) {
                     if (!limitToGalleries || specimenDescription.isImageGallery()) {
                         Set<DescriptionElementBase> elements = specimenDescription.getElements();
                         for (DescriptionElementBase element : elements) {
@@ -877,7 +1003,6 @@ public class TaxonServiceImpl
             }
         }
 
-
         logger.trace("listMedia() - initialize");
         beanInitializer.initializeAll(taxonMedia, propertyPath);
 
@@ -888,7 +1013,7 @@ public class TaxonServiceImpl
 
     @Override
     public List<TaxonBase> findTaxaByID(Set<Integer> listOfIDs) {
-        return this.dao.loadList(listOfIDs, null);
+        return this.dao.loadList(listOfIDs, null, null);
     }
 
     @Override
@@ -897,15 +1022,10 @@ public class TaxonServiceImpl
     }
 
     @Override
-    public int countSynonyms(boolean onlyAttachedToTaxon){
+    public long countSynonyms(boolean onlyAttachedToTaxon){
         return this.dao.countSynonyms(onlyAttachedToTaxon);
     }
 
-    @Override
-    public List<TaxonName> findIdenticalTaxonNames(List<String> propertyPath) {
-        return this.dao.findIdenticalTaxonNames(propertyPath);
-    }
-
     @Override
     @Transactional(readOnly=false)
     public DeleteResult deleteTaxon(UUID taxonUUID, TaxonDeletionConfigurator config, UUID classificationUuid)  {
@@ -922,6 +1042,7 @@ public class TaxonServiceImpl
        }
        taxon = HibernateProxyHelper.deproxy(taxon);
        Classification classification = HibernateProxyHelper.deproxy(classificationDao.load(classificationUuid), Classification.class);
+       config.setClassificationUuid(classificationUuid);
         result = isDeletable(taxonUUID, config);
 
         if (result.isOk()){
@@ -957,8 +1078,8 @@ public class TaxonServiceImpl
                 configRelTaxon.setDeleteConceptRelationships(true);
 
                 for (TaxonRelationship taxRel: taxon.getTaxonRelations()){
-                    if (config.isDeleteMisappliedNamesAndInvalidDesignations()
-                            && taxRel.getType().isMisappliedNameOrInvalidDesignation()
+                    if (config.isDeleteMisappliedNames()
+                            && taxRel.getType().isMisappliedName()
                             && taxon.equals(taxRel.getToTaxon())){
                         this.deleteTaxon(taxRel.getFromTaxon().getUuid(), config, classificationUuid);
                     } else if (config.isDeleteConceptRelationships() && taxRel.getType().isConceptRelationship()){
@@ -987,8 +1108,6 @@ public class TaxonServiceImpl
                         break;
                     }
                     removeDescriptions.add(desc);
-
-
                 }
                 if (result.isOk()){
                     for (TaxonDescription desc: removeDescriptions){
@@ -1000,145 +1119,99 @@ public class TaxonServiceImpl
                 }
             }
 
-
-         if (! config.isDeleteTaxonNodes() || (!config.isDeleteInAllClassifications() && classification == null && taxon.getTaxonNodes().size() > 1)){
-             result.addException(new Exception( "Taxon can't be deleted as it is used in more than one classification."));
-         }else{
-             if (taxon.getTaxonNodes().size() != 0){
-                Set<TaxonNode> nodes = taxon.getTaxonNodes();
-                Iterator<TaxonNode> iterator = nodes.iterator();
-                TaxonNode node = null;
-                boolean deleteChildren;
-                if (config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.DELETE)){
-                    deleteChildren = true;
-                }else {
-                    deleteChildren = false;
-                }
-                boolean success = true;
-                if (!config.isDeleteInAllClassifications() && !(classification == null)){
-                    while (iterator.hasNext()){
-                        node = iterator.next();
-                        if (node.getClassification().equals(classification)){
-                            break;
-                        }
-                        node = null;
-                    }
-                    if (node != null){
-                        HibernateProxyHelper.deproxy(node, TaxonNode.class);
-                        success =taxon.removeTaxonNode(node, deleteChildren);
-                        nodeService.delete(node);
-                        result.addDeletedObject(node);
-                    } else {
-                       result.setError();
-                       result.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
+             if (! config.isDeleteTaxonNodes() || (!config.isDeleteInAllClassifications() && classification == null && taxon.getTaxonNodes().size() > 1)){
+                 result.addException(new Exception( "Taxon can't be deleted as it is used in more than one classification."));
+             }else{
+                 if (taxon.getTaxonNodes().size() != 0){
+                    Set<TaxonNode> nodes = taxon.getTaxonNodes();
+                    Iterator<TaxonNode> iterator = nodes.iterator();
+                    TaxonNode node = null;
+                    boolean deleteChildren;
+                    if (config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.DELETE)){
+                        deleteChildren = true;
+                    }else {
+                        deleteChildren = false;
                     }
-                } else if (config.isDeleteInAllClassifications()){
-                    List<TaxonNode> nodesList = new ArrayList<>();
-                    nodesList.addAll(taxon.getTaxonNodes());
-                    for (ITaxonTreeNode treeNode: nodesList){
-                        TaxonNode taxonNode = (TaxonNode) treeNode;
-                        if(!deleteChildren){
-                            Object[] childNodes = taxonNode.getChildNodes().toArray();
-                            for (Object childNode: childNodes){
-                                TaxonNode childNodeCast = (TaxonNode) childNode;
-                                taxonNode.getParent().addChildNode(childNodeCast, childNodeCast.getReference(), childNodeCast.getMicroReference());
+                    boolean success = true;
+                    if (!config.isDeleteInAllClassifications() && !(classification == null)){
+                        while (iterator.hasNext()){
+                            node = iterator.next();
+                            if (node.getClassification().equals(classification)){
+                                break;
                             }
+                            node = null;
+                        }
+                        if (node != null){
+                            HibernateProxyHelper.deproxy(node, TaxonNode.class);
+                            success =taxon.removeTaxonNode(node, deleteChildren);
+                            nodeService.delete(node);
+                            result.addDeletedObject(node);
+                        } else {
+                               result.setError();
+                               result.addException(new Exception("The taxon can not be deleted because it is not used in defined classification."));
+                        }
+                    } else if (config.isDeleteInAllClassifications()){
+                        List<TaxonNode> nodesList = new ArrayList<>();
+                        nodesList.addAll(taxon.getTaxonNodes());
+                        for (ITaxonTreeNode treeNode: nodesList){
+                            TaxonNode taxonNode = (TaxonNode) treeNode;
+                            if(!deleteChildren){
+                                Object[] childNodes = taxonNode.getChildNodes().toArray();
+                                for (Object childNode: childNodes){
+                                    TaxonNode childNodeCast = (TaxonNode) childNode;
+                                    taxonNode.getParent().addChildNode(childNodeCast, childNodeCast.getReference(), childNodeCast.getMicroReference());
+                                }
+                            }
+                        }
+                        config.getTaxonNodeConfig().setDeleteElement(false);
+                        DeleteResult resultNodes = nodeService.deleteTaxonNodes(nodesList, config);
+                        if (!resultNodes.isOk()){
+                               result.addExceptions(resultNodes.getExceptions());
+                               result.setStatus(resultNodes.getStatus());
+                        } else {
+                            result.addUpdatedObjects(resultNodes.getUpdatedObjects());
                         }
                     }
-                    config.getTaxonNodeConfig().setDeleteElement(false);
-                    DeleteResult resultNodes = nodeService.deleteTaxonNodes(nodesList, config);
-                    if (!resultNodes.isOk()){
-                       result.addExceptions(resultNodes.getExceptions());
-                       result.setStatus(resultNodes.getStatus());
-                    } else {
-                        result.addUpdatedObjects(resultNodes.getUpdatedObjects());
+                    if (!success){
+                        result.setError();
+                        result.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
                     }
                 }
-                if (!success){
-                    result.setError();
-                    result.addException(new Exception("The taxon can not be deleted because the taxon node can not be removed."));
-                }
-            }
-         }
-         TaxonName name = taxon.getName();
-         taxon.setName(null);
-         this.saveOrUpdate(taxon);
-
-         if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0)  && result.isOk()){
-             try{
-                 dao.delete(taxon);
-                 result.addDeletedObject(taxon);
-             }catch(Exception e){
-                 result.addException(e);
-                 result.setError();
              }
-         } else {
-             result.setError();
-             result.addException(new Exception("The Taxon can't be deleted because it is used in a classification."));
+             TaxonName name = taxon.getName();
+             taxon.setName(null);
+             this.saveOrUpdate(taxon);
+
+             if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0)  && result.isOk()){
+                 try{
+                     dao.delete(taxon);
+                     result.addDeletedObject(taxon);
+                 }catch(Exception e){
+                     result.addException(e);
+                     result.setError();
+                 }
+             } else {
+                 result.setError();
+                 result.addException(new Exception("The Taxon can't be deleted because it is used in a classification."));
 
-         }
-         //TaxonName
-         if (config.isDeleteNameIfPossible() && result.isOk()){
-            DeleteResult nameResult = new DeleteResult();
-            //remove name if possible (and required)
-            if (name != null ){
-                nameResult = nameService.delete(name.getUuid(), config.getNameDeletionConfig());
-            }
-            if (nameResult.isError() || nameResult.isAbort()){
-                result.addRelatedObject(name);
-                result.addExceptions(nameResult.getExceptions());
-            }else{
-                result.includeResult(nameResult);
-            }
-         }
+             }
+             //TaxonName
+             if (config.isDeleteNameIfPossible() && result.isOk()){
+                 DeleteResult nameResult = new DeleteResult();
+                 //remove name if possible (and required)
+                 if (name != null ){
+                     nameResult = nameService.delete(name.getUuid(), config.getNameDeletionConfig());
+                 }
+                 if (nameResult.isError() || nameResult.isAbort()){
+                     result.addRelatedObject(name);
+                     result.addExceptions(nameResult.getExceptions());
+                 }else{
+                     result.includeResult(nameResult);
+                 }
+             }
        }
 
        return result;
-
-    }
-
-    private String checkForReferences(Taxon taxon){
-        Set<CdmBase> referencingObjects = genericDao.getReferencingObjects(taxon);
-        for (CdmBase referencingObject : referencingObjects){
-            //IIdentificationKeys (Media, Polytomous, MultiAccess)
-            if (HibernateProxyHelper.isInstanceOf(referencingObject, IIdentificationKey.class)){
-                String message = "Taxon" + taxon.getTitleCache() + "can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this name";
-
-                return message;
-            }
-
-
-           /* //PolytomousKeyNode
-            if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
-                String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
-                return message;
-            }*/
-
-            //TaxonInteraction
-            if (referencingObject.isInstanceOf(TaxonInteraction.class)){
-                String message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2";
-                return message;
-            }
-
-          //TaxonInteraction
-            if (referencingObject.isInstanceOf(DeterminationEvent.class)){
-                String message = "Taxon can't be deleted as it is used in a determination event";
-                return message;
-            }
-
-        }
-
-        referencingObjects = null;
-        return null;
-    }
-
-    private boolean checkForPolytomousKeys(Taxon taxon){
-        boolean result = false;
-        List<CdmBase> list = genericDao.getCdmBasesByFieldAndClass(PolytomousKeyNode.class, "taxon", taxon, null);
-        if (!list.isEmpty()) {
-            result = true;
-        }
-        return result;
     }
 
     @Override
@@ -1152,10 +1225,8 @@ public class TaxonServiceImpl
     @Transactional(readOnly = false)
     public DeleteResult deleteSynonym(UUID synonymUuid, SynonymDeletionConfigurator config) {
         return deleteSynonym((Synonym)dao.load(synonymUuid), config);
-
     }
 
-
     @Override
     @Transactional(readOnly = false)
     public DeleteResult deleteSynonym(Synonym synonym, SynonymDeletionConfigurator config) {
@@ -1206,18 +1277,15 @@ public class TaxonServiceImpl
                         result.addDeletedObject(name);
                     }
             }
-
         }
         return result;
     }
 
     @Override
-    public List<TaxonName> findIdenticalTaxonNameIds(List<String> propertyPath) {
-
-        return this.dao.findIdenticalNamesNew(propertyPath);
+    public Map<String, Map<UUID,Set<TaxonName>>> findIdenticalTaxonNames(List<UUID> sourceRefUuids, List<String> propertyPaths) {
+        return this.dao.findIdenticalNames(sourceRefUuids, propertyPaths);
     }
 
-
     @Override
     public Taxon findBestMatchingTaxon(String taxonName) {
         MatchingTaxonConfigurator config = MatchingTaxonConfigurator.NewInstance();
@@ -1232,11 +1300,11 @@ public class TaxonServiceImpl
         try{
             // 1. search for accepted taxa
             List<TaxonBase> taxonList = dao.findByNameTitleCache(true, false, config.isIncludeUnpublished(),
-                    config.getTaxonNameTitle(), null, MatchMode.EXACT, null, null, 0, null, null);
+                    config.getTaxonNameTitle(), null, null, MatchMode.EXACT, null, null, 0, null, null);
             boolean bestCandidateMatchesSecUuid = false;
             boolean bestCandidateIsInClassification = false;
             int countEqualCandidates = 0;
-            for(TaxonBase taxonBaseCandidate : taxonList){
+            for(TaxonBase<?> taxonBaseCandidate : taxonList){
                 if(taxonBaseCandidate instanceof Taxon){
                     Taxon newCanditate = CdmBase.deproxy(taxonBaseCandidate, Taxon.class);
                     boolean newCandidateMatchesSecUuid = isMatchesSecUuid(newCanditate, config);
@@ -1263,7 +1331,6 @@ public class TaxonServiceImpl
                         countEqualCandidates = 1;
                         continue;
                     }
-
                 }else{  //not Taxon.class
                     continue;
                 }
@@ -1280,11 +1347,10 @@ public class TaxonServiceImpl
                 }
             }
 
-
             // 2. search for synonyms
             if (config.isIncludeSynonyms()){
                 List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, config.isIncludeUnpublished(),
-                        config.getTaxonNameTitle(), null, MatchMode.EXACT, null, null, 0, null, null);
+                        config.getTaxonNameTitle(), null, null, MatchMode.EXACT, null, null, 0, null, null);
                 for(TaxonBase taxonBase : synonymList){
                     if(taxonBase instanceof Synonym){
                         Synonym synonym = CdmBase.deproxy(taxonBase, Synonym.class);
@@ -1331,7 +1397,7 @@ public class TaxonServiceImpl
 
     @Override
     public Synonym findBestMatchingSynonym(String taxonName, boolean includeUnpublished) {
-        List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, includeUnpublished, taxonName, null, MatchMode.EXACT, null, null, 0, null, null);
+        List<TaxonBase> synonymList = dao.findByNameTitleCache(false, true, includeUnpublished, taxonName, null, null, MatchMode.EXACT, null, null, 0, null, null);
         if(! synonymList.isEmpty()){
             Synonym result = CdmBase.deproxy(synonymList.iterator().next(), Synonym.class);
             if(synonymList.size() == 1){
@@ -1408,8 +1474,9 @@ public class TaxonServiceImpl
 
             newTaxon = HibernateProxyHelper.deproxy(newTaxon, Taxon.class);
             oldTaxon = HibernateProxyHelper.deproxy(oldTaxon, Taxon.class);
-            newTaxon.addSynonym(synRelation, newSynonymType);
             oldTaxon.removeSynonym(synRelation, false);
+            newTaxon.addSynonym(synRelation, newSynonymType);
+
             if (newSecundum != null || !keepSecundumIfUndefined){
                 synRelation.setSec(newSecundum);
             }
@@ -1432,19 +1499,20 @@ public class TaxonServiceImpl
     }
 
     @Override
-    public <T extends TaxonBase> List<UuidAndTitleCache<T>> getUuidAndTitleCache(Class<T> clazz, Integer limit, String pattern) {
+    public <T extends TaxonBase>List<UuidAndTitleCache<T>> getUuidAndTitleCache(Class<T> clazz, Integer limit, String pattern) {
+
         return dao.getUuidAndTitleCache(clazz, limit, pattern);
     }
 
     @Override
     public Pager<SearchResult<TaxonBase>> findByFullText(
             Class<? extends TaxonBase> clazz, String queryString,
-            Classification classification, boolean includeUnpublished, List<Language> languages,
+            Classification classification, TaxonNode subtree, boolean includeUnpublished, List<Language> languages,
             boolean highlightFragments, Integer pageSize, Integer pageNumber,
             List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, LuceneParseException {
 
-        LuceneSearch luceneSearch = prepareFindByFullTextSearch(clazz, queryString, classification, null,
-                includeUnpublished, languages, highlightFragments, null);
+        LuceneSearch luceneSearch = prepareFindByFullTextSearch(clazz, queryString, classification, subtree,
+                null, includeUnpublished, languages, highlightFragments, null);
 
         // --- execute search
         TopGroups<BytesRef> topDocsResultSet;
@@ -1461,6 +1529,7 @@ public class TaxonServiceImpl
 
         // ---  initialize taxa, thighlight matches ....
         ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
+        @SuppressWarnings("rawtypes")
         List<SearchResult<TaxonBase>> searchResults = searchResultBuilder.createResultSet(
                 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
 
@@ -1468,13 +1537,50 @@ public class TaxonServiceImpl
         return new DefaultPagerImpl<>(pageNumber, totalHits, pageSize, searchResults);
     }
 
+    @Transactional(readOnly = true)
+    @Override
+    public <S extends TaxonBase> Pager<S> findByTitleWithRestrictions(Class<S> clazz, String queryString, MatchMode matchmode, List<Restriction<?>> restrictions, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
+         long numberOfResults = dao.countByTitleWithRestrictions(clazz, queryString, matchmode, restrictions);
+
+         long numberOfResults_doubtful = dao.countByTitleWithRestrictions(clazz, "?".concat(queryString), matchmode, restrictions);
+         List<S> results = new ArrayList<>();
+         if(numberOfResults > 0 || numberOfResults_doubtful > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
+
+             results = dao.findByTitleWithRestrictions(clazz, queryString, matchmode, restrictions, pageSize, pageNumber, orderHints, propertyPaths);
+             results.addAll(dao.findByTitleWithRestrictions(clazz, "?".concat(queryString), matchmode, restrictions, pageSize, pageNumber, orderHints, propertyPaths));
+         }
+         Collections.sort(results, new TaxonComparator());
+         return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public <S extends TaxonBase> Pager<S> findByTitle(Class<S> clazz, String queryString,MatchMode matchmode, List<Criterion> criteria, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
+        long numberOfResults = dao.countByTitle(clazz, queryString, matchmode, criteria);
+        //check whether there are doubtful taxa matching
+        long numberOfResults_doubtful = dao.countByTitle(clazz, "?".concat(queryString), matchmode, criteria);
+        List<S> results = new ArrayList<>();
+        if(numberOfResults > 0 || numberOfResults_doubtful > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
+               if (numberOfResults > 0){
+                   results = dao.findByTitle(clazz, queryString, matchmode, criteria, pageSize, pageNumber, orderHints, propertyPaths);
+               }else{
+                   results = new ArrayList<>();
+               }
+               if (numberOfResults_doubtful > 0){
+                   results.addAll(dao.findByTitle(clazz, "?".concat(queryString), matchmode,  criteria, pageSize, pageNumber, orderHints, propertyPaths));
+               }
+        }
+        Collections.sort(results, new TaxonComparator());
+        return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
+    }
+
     @Override
     public Pager<SearchResult<TaxonBase>> findByDistribution(List<NamedArea> areaFilter, List<PresenceAbsenceTerm> statusFilter,
-            Classification classification,
+            Classification classification, TaxonNode subtree,
             Integer pageSize, Integer pageNumber,
             List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, LuceneParseException {
 
-        LuceneSearch luceneSearch = prepareByDistributionSearch(areaFilter, statusFilter, classification);
+        LuceneSearch luceneSearch = prepareByDistributionSearch(areaFilter, statusFilter, classification, subtree);
 
         // --- execute search
         TopGroups<BytesRef> topDocsResultSet;
@@ -1510,7 +1616,7 @@ public class TaxonServiceImpl
      * @return
      */
     protected LuceneSearch prepareFindByFullTextSearch(Class<? extends CdmBase> clazz, String queryString,
-            Classification classification, String className, boolean includeUnpublished, List<Language> languages,
+            Classification classification, TaxonNode subtree, String className, boolean includeUnpublished, List<Language> languages,
             boolean highlightFragments, SortField[] sortFields) {
 
         Builder finalQueryBuilder = new Builder();
@@ -1527,7 +1633,7 @@ public class TaxonServiceImpl
         // ---- search criteria
         luceneSearch.setCdmTypRestriction(clazz);
 
-        if(!queryString.isEmpty() && !queryString.equals("*") && !queryString.equals("?") ) {
+        if(!StringUtils.isEmpty(queryString) && !queryString.equals("*") && !queryString.equals("?") ) {
             textQueryBuilder.add(taxonBaseQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
             textQueryBuilder.add(taxonBaseQueryFactory.newDefinedTermQuery("name.rank", queryString, languages), Occur.SHOULD);
         }
@@ -1540,9 +1646,11 @@ public class TaxonServiceImpl
             finalQueryBuilder.add(textQuery, Occur.MUST);
         }
 
-
         if(classification != null){
-            finalQueryBuilder.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
+            finalQueryBuilder.add(taxonBaseQueryFactory.newEntityIdQuery(AcceptedTaxonBridge.DOC_KEY_CLASSIFICATION_ID, classification), Occur.MUST);
+        }
+        if(subtree != null){
+            finalQueryBuilder.add(taxonBaseQueryFactory.newTermQuery(AcceptedTaxonBridge.DOC_KEY_TREEINDEX, subtree.treeIndexWc(), true), Occur.MUST);
         }
         if(!includeUnpublished)  {
             String accPublishParam = TaxonBase.ACC_TAXON_BRIDGE_PREFIX + AcceptedTaxonBridge.DOC_KEY_PUBLISH_SUFFIX;
@@ -1579,7 +1687,7 @@ public class TaxonServiceImpl
      * @throws IOException
      */
     protected LuceneSearch prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge, String queryString,
-            Classification classification, boolean includeUnpublished, List<Language> languages,
+            Classification classification, TaxonNode subtree, boolean includeUnpublished, List<Language> languages,
             boolean highlightFragments, SortField[] sortFields) throws IOException {
 
         String fromField;
@@ -1611,8 +1719,10 @@ public class TaxonServiceImpl
         QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class);
 
         Builder joinFromQueryBuilder = new Builder();
-        joinFromQueryBuilder.add(taxonBaseQueryFactory.newTermQuery(queryTermField, queryString), Occur.MUST);
-        joinFromQueryBuilder.add(taxonBaseQueryFactory.newEntityIdsQuery("type.id", edge.getTaxonRelationshipTypes()), Occur.MUST);
+        if(!StringUtils.isEmpty(queryString)){
+            joinFromQueryBuilder.add(taxonBaseQueryFactory.newTermQuery(queryTermField, queryString), Occur.MUST);
+        }
+        joinFromQueryBuilder.add(taxonBaseQueryFactory.newEntityIdsQuery("type.id", edge.getRelationshipTypes()), Occur.MUST);
         if(!includeUnpublished){
             joinFromQueryBuilder.add(taxonBaseQueryFactory.newBooleanQuery(publishField, true), Occur.MUST);
             joinFromQueryBuilder.add(taxonBaseQueryFactory.newBooleanQuery(publishFieldInvers, true), Occur.MUST);
@@ -1628,7 +1738,10 @@ public class TaxonServiceImpl
         finalQueryBuilder.add(joinQuery, Occur.MUST);
 
         if(classification != null){
-            finalQueryBuilder.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
+            finalQueryBuilder.add(taxonBaseQueryFactory.newEntityIdQuery(AcceptedTaxonBridge.DOC_KEY_CLASSIFICATION_ID, classification), Occur.MUST);
+        }
+        if(subtree != null){
+            finalQueryBuilder.add(taxonBaseQueryFactory.newTermQuery(AcceptedTaxonBridge.DOC_KEY_TREEINDEX, subtree.treeIndexWc(), true), Occur.MUST);
         }
 
         luceneSearch.setQuery(finalQueryBuilder.build());
@@ -1641,7 +1754,8 @@ public class TaxonServiceImpl
 
     @Override
     public Pager<SearchResult<TaxonBase>> findTaxaAndNamesByFullText(
-            EnumSet<TaxaAndNamesSearchMode> searchModes, String queryString, Classification classification,
+            EnumSet<TaxaAndNamesSearchMode> searchModes, String queryString,
+            Classification classification, TaxonNode subtree,
             Set<NamedArea> namedAreas, Set<PresenceAbsenceTerm> distributionStatus, List<Language> languages,
             boolean highlightFragments, Integer pageSize,
             Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)
@@ -1658,7 +1772,7 @@ public class TaxonServiceImpl
 
         // convert sets to lists
         List<NamedArea> namedAreaList = null;
-        List<PresenceAbsenceTerm>distributionStatusList = null;
+        List<PresenceAbsenceTerm> distributionStatusList = null;
         if(namedAreas != null){
             namedAreaList = new ArrayList<>(namedAreas.size());
             namedAreaList.addAll(namedAreas);
@@ -1686,7 +1800,6 @@ public class TaxonServiceImpl
 //        SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)};
 //        SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)};
 
-
         boolean addDistributionFilter = namedAreas != null && namedAreas.size() > 0;
 
         List<LuceneSearch> luceneSearches = new ArrayList<>();
@@ -1746,7 +1859,8 @@ public class TaxonServiceImpl
             } else if (!searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
                 className = "eu.etaxonomy.cdm.model.taxon.Synonym";
             }
-            luceneSearches.add(prepareFindByFullTextSearch(taxonBaseSubclass, queryString, classification, className,
+            luceneSearches.add(prepareFindByFullTextSearch(taxonBaseSubclass,
+                    queryString, classification, subtree, className,
                     includeUnpublished, languages, highlightFragments, sortFields));
             idFieldMap.put(CdmBaseType.TAXON, "id");
             /* A) does not work!!!!
@@ -1784,7 +1898,7 @@ public class TaxonServiceImpl
                     "inDescription.taxon.id",
                     true,
                     QueryFactory.addTypeRestriction(
-                                createByDescriptionElementFullTextQuery(queryString, classification, null, languages, descriptionElementQueryFactory)
+                                createByDescriptionElementFullTextQuery(queryString, classification, subtree, null, languages, descriptionElementQueryFactory)
                                 , CommonTaxonName.class
                                 ).build(), "id", null, ScoreMode.Max);
             if (logger.isDebugEnabled()){logger.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery.toString());}
@@ -1842,7 +1956,7 @@ public class TaxonServiceImpl
 
             luceneSearches.add(prepareFindByTaxonRelationFullTextSearch(
                     new TaxonRelationshipEdge(relTypes, Direction.relatedTo),
-                    queryString, classification, includeUnpublished, languages, highlightFragments, sortFields));
+                    queryString, classification, subtree, includeUnpublished, languages, highlightFragments, sortFields));
             idFieldMap.put(CdmBaseType.TAXON, "id");
 
             if(addDistributionFilter){
@@ -1889,7 +2003,6 @@ public class TaxonServiceImpl
             }
         }
 
-
         // search by pro parte synonyms
         if(searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) {
             //TODO merge with misapplied name search once #7487 is fixed
@@ -1898,7 +2011,7 @@ public class TaxonServiceImpl
 
             luceneSearches.add(prepareFindByTaxonRelationFullTextSearch(
                     new TaxonRelationshipEdge(relTypes, Direction.relatedTo),
-                    queryString, classification, includeUnpublished, languages, highlightFragments, sortFields));
+                    queryString, classification, subtree, includeUnpublished, languages, highlightFragments, sortFields));
             idFieldMap.put(CdmBaseType.TAXON, "id");
 
             if(addDistributionFilter){
@@ -1911,12 +2024,9 @@ public class TaxonServiceImpl
             }
         }//end pro parte synonyms
 
-
-
         LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider,
                 luceneSearches.toArray(new LuceneSearch[luceneSearches.size()]));
 
-
         if(addDistributionFilter){
 
             // B)
@@ -2022,7 +2132,7 @@ public class TaxonServiceImpl
      */
     protected LuceneSearch prepareByDistributionSearch(
             List<NamedArea> namedAreaList, List<PresenceAbsenceTerm> distributionStatusList,
-            Classification classification) throws IOException {
+            Classification classification, TaxonNode subtree) throws IOException {
 
         Builder finalQueryBuilder = new Builder();
 
@@ -2040,7 +2150,10 @@ public class TaxonServiceImpl
         finalQueryBuilder.add(byAreaQuery, Occur.MUST);
 
         if(classification != null){
-            finalQueryBuilder.add(taxonQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST);
+            finalQueryBuilder.add(taxonQueryFactory.newEntityIdQuery(AcceptedTaxonBridge.DOC_KEY_CLASSIFICATION_ID, classification), Occur.MUST);
+        }
+        if(subtree != null){
+            finalQueryBuilder.add(taxonQueryFactory.newTermQuery(AcceptedTaxonBridge.DOC_KEY_TREEINDEX, subtree.treeIndexWc(), true), Occur.MUST);
         }
         BooleanQuery finalQuery = finalQueryBuilder.build();
         logger.info("prepareByAreaSearch() query: " + finalQuery.toString());
@@ -2052,11 +2165,10 @@ public class TaxonServiceImpl
     @Override
     public Pager<SearchResult<TaxonBase>> findByDescriptionElementFullText(
             Class<? extends DescriptionElementBase> clazz, String queryString,
-            Classification classification, List<Feature> features, List<Language> languages,
+            Classification classification, TaxonNode subtree, List<Feature> features, List<Language> languages,
             boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, LuceneParseException {
 
-
-        LuceneSearch luceneSearch = prepareByDescriptionElementFullTextSearch(clazz, queryString, classification, features, languages, highlightFragments);
+        LuceneSearch luceneSearch = prepareByDescriptionElementFullTextSearch(clazz, queryString, classification, subtree, features, languages, highlightFragments);
 
         // --- execute search
         TopGroups<BytesRef> topDocsResultSet;
@@ -2079,18 +2191,17 @@ public class TaxonServiceImpl
 
         int totalHits = topDocsResultSet != null ? topDocsResultSet.totalGroupCount : 0;
         return new DefaultPagerImpl<>(pageNumber, Long.valueOf(totalHits), pageSize, searchResults);
-
     }
 
-
     @Override
     public Pager<SearchResult<TaxonBase>> findByEverythingFullText(String queryString,
-            Classification classification, boolean includeUnpublished, List<Language> languages, boolean highlightFragments,
+            Classification classification, TaxonNode subtree, boolean includeUnpublished, List<Language> languages, boolean highlightFragments,
             Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) throws IOException, LuceneParseException, LuceneMultiSearchException {
 
-        LuceneSearch luceneSearchByDescriptionElement = prepareByDescriptionElementFullTextSearch(null, queryString, classification,
+        LuceneSearch luceneSearchByDescriptionElement = prepareByDescriptionElementFullTextSearch(null, queryString,
+                classification, subtree,
                 null, languages, highlightFragments);
-        LuceneSearch luceneSearchByTaxonBase = prepareFindByFullTextSearch(null, queryString, classification, null,
+        LuceneSearch luceneSearchByTaxonBase = prepareFindByFullTextSearch(null, queryString, classification, subtree, null,
                 includeUnpublished, languages, highlightFragments, null);
 
         LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider, luceneSearchByDescriptionElement, luceneSearchByTaxonBase);
@@ -2117,10 +2228,8 @@ public class TaxonServiceImpl
 
         int totalHits = topDocsResultSet != null ? topDocsResultSet.totalGroupCount : 0;
         return new DefaultPagerImpl<>(pageNumber, Long.valueOf(totalHits), pageSize, searchResults);
-
     }
 
-
     /**
      * @param clazz
      * @param queryString
@@ -2132,7 +2241,7 @@ public class TaxonServiceImpl
      * @return
      */
     protected LuceneSearch prepareByDescriptionElementFullTextSearch(Class<? extends CdmBase> clazz,
-            String queryString, Classification classification, List<Feature> features,
+            String queryString, Classification classification, TaxonNode subtree, List<Feature> features,
             List<Language> languages, boolean highlightFragments) {
 
         LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, DescriptionElementBase.class);
@@ -2140,7 +2249,7 @@ public class TaxonServiceImpl
 
         SortField[] sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("inDescription.taxon.titleCache__sort", SortField.Type.STRING, false)};
 
-        BooleanQuery finalQuery = createByDescriptionElementFullTextQuery(queryString, classification, features,
+        BooleanQuery finalQuery = createByDescriptionElementFullTextQuery(queryString, classification, subtree, features,
                 languages, descriptionElementQueryFactory);
 
         luceneSearch.setSortFields(sortFields);
@@ -2161,49 +2270,60 @@ public class TaxonServiceImpl
      * @param descriptionElementQueryFactory
      * @return
      */
-    private BooleanQuery createByDescriptionElementFullTextQuery(String queryString, Classification classification,
-            List<Feature> features, List<Language> languages, QueryFactory descriptionElementQueryFactory) {
+    private BooleanQuery createByDescriptionElementFullTextQuery(String queryString,
+            Classification classification, TaxonNode subtree, List<Feature> features,
+            List<Language> languages, QueryFactory descriptionElementQueryFactory) {
+
         Builder finalQueryBuilder = new Builder();
         Builder textQueryBuilder = new Builder();
-        textQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
 
-        // common name
-        Builder nameQueryBuilder = new Builder();
-        if(languages == null || languages.size() == 0){
-            nameQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
-        } else {
-            Builder languageSubQueryBuilder = new Builder();
-            for(Language lang : languages){
-                languageSubQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("language.uuid",  lang.getUuid().toString(), false), Occur.SHOULD);
+        if(!StringUtils.isEmpty(queryString)){
+
+            textQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
+
+            // common name
+            Builder nameQueryBuilder = new Builder();
+            if(languages == null || languages.size() == 0){
+                nameQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
+            } else {
+                Builder languageSubQueryBuilder = new Builder();
+                for(Language lang : languages){
+                    languageSubQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("language.uuid",  lang.getUuid().toString(), false), Occur.SHOULD);
+                }
+                nameQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
+                nameQueryBuilder.add(languageSubQueryBuilder.build(), Occur.MUST);
             }
-            nameQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST);
-            nameQueryBuilder.add(languageSubQueryBuilder.build(), Occur.MUST);
-        }
-        textQueryBuilder.add(nameQueryBuilder.build(), Occur.SHOULD);
+            textQueryBuilder.add(nameQueryBuilder.build(), Occur.SHOULD);
 
 
-        // text field from TextData
-        textQueryBuilder.add(descriptionElementQueryFactory.newMultilanguageTextQuery("text", queryString, languages), Occur.SHOULD);
+            // text field from TextData
+            textQueryBuilder.add(descriptionElementQueryFactory.newMultilanguageTextQuery("text", queryString, languages), Occur.SHOULD);
 
-        // --- TermBase fields - by representation ----
-        // state field from CategoricalData
-        textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.state", queryString, languages), Occur.SHOULD);
+            // --- TermBase fields - by representation ----
+            // state field from CategoricalData
+            textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.state", queryString, languages), Occur.SHOULD);
 
-        // state field from CategoricalData
-        textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.modifyingText", queryString, languages), Occur.SHOULD);
+            // state field from CategoricalData
+            textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.modifyingText", queryString, languages), Occur.SHOULD);
 
-        // area field from Distribution
-        textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("area", queryString, languages), Occur.SHOULD);
+            // area field from Distribution
+            textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("area", queryString, languages), Occur.SHOULD);
 
-        // status field from Distribution
-        textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("status", queryString, languages), Occur.SHOULD);
+            // status field from Distribution
+            textQueryBuilder.add(descriptionElementQueryFactory.newDefinedTermQuery("status", queryString, languages), Occur.SHOULD);
+
+            finalQueryBuilder.add(textQueryBuilder.build(), Occur.MUST);
 
-        finalQueryBuilder.add(textQueryBuilder.build(), Occur.MUST);
+        }
         // --- classification ----
 
+
         if(classification != null){
             finalQueryBuilder.add(descriptionElementQueryFactory.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification), Occur.MUST);
         }
+        if(subtree != null){
+            finalQueryBuilder.add(descriptionElementQueryFactory.newTermQuery("inDescription.taxon.taxonNodes.treeIndex", subtree.treeIndexWc(), true), Occur.MUST);
+        }
 
         // --- IdentifieableEntity fields - by uuid
         if(features != null && features.size() > 0 ){
@@ -2218,38 +2338,9 @@ public class TaxonServiceImpl
         return finalQuery;
     }
 
-    /**
-     * DefinedTerm representations and MultilanguageString maps are stored in the Lucene index by the {@link DefinedTermBaseClassBridge}
-     * and {@link MultilanguageTextFieldBridge } in a consistent way. One field per language and also in one additional field for all languages.
-     * This method is a convenient means to retrieve a Lucene query string for such the fields.
-     *
-     * @param name name of the term field as in the Lucene index. Must be field created by {@link DefinedTermBaseClassBridge}
-     * or {@link MultilanguageTextFieldBridge }
-     * @param languages the languages to search for exclusively. Can be <code>null</code> to search in all languages
-     * @param stringBuilder a StringBuilder to be reused, if <code>null</code> a new StringBuilder will be instantiated and is returned
-     * @return the StringBuilder given a parameter or a new one if the stringBuilder parameter was null.
-     *
-     * TODO move to utiliy class !!!!!!!!
-     */
-    private StringBuilder appendLocalizedFieldQuery(String name, List<Language> languages, StringBuilder stringBuilder) {
-
-        if(stringBuilder == null){
-            stringBuilder = new StringBuilder();
-        }
-        if(languages == null || languages.size() == 0){
-            stringBuilder.append(name + ".ALL:(%1$s) ");
-        } else {
-            for(Language lang : languages){
-                stringBuilder.append(name + "." + lang.getUuid().toString() + ":(%1$s) ");
-            }
-        }
-        return stringBuilder;
-    }
-
     @Override
     public List<Synonym> createInferredSynonyms(Taxon taxon, Classification classification, SynonymType type, boolean doWithMisappliedNames){
 
-
         List <Synonym> inferredSynonyms = new ArrayList<>();
         List<Synonym> inferredSynonymsToBeRemoved = new ArrayList<>();
 
@@ -2283,8 +2374,6 @@ public class TaxonServiceImpl
                     TaxonName parentName =  parent.getTaxon().getName();
                     IZoologicalName zooParentName = CdmBase.deproxy(parentName);
                     Taxon parentTaxon = CdmBase.deproxy(parent.getTaxon());
-                    Rank rankOfTaxon = taxonName.getRank();
-
 
                     //create inferred synonyms for species, subspecies
                     if ((parentName.isGenus() || parentName.isSpecies() || parentName.getRank().equals(Rank.SUBGENUS())) ){
@@ -2348,141 +2437,87 @@ public class TaxonServiceImpl
                             }
 
                             if (!taxonNames.isEmpty()){
-                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
-                            IZoologicalName name;
-                            if (!synNotInCDM.isEmpty()){
-                                inferredSynonymsToBeRemoved.clear();
-
-                                for (Synonym syn :inferredSynonyms){
-                                    name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
-                                    if (!synNotInCDM.contains(name.getNameCache())){
-                                        inferredSynonymsToBeRemoved.add(syn);
+                                List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
+                                if (!synNotInCDM.isEmpty()){
+                                    inferredSynonymsToBeRemoved.clear();
+
+                                    for (Synonym syn :inferredSynonyms){
+                                        IZoologicalName name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
+                                        if (!synNotInCDM.contains(name.getNameCache())){
+                                            inferredSynonymsToBeRemoved.add(syn);
+                                        }
                                     }
-                                }
 
-                                // Remove identified Synonyms from inferredSynonyms
-                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
-                                    inferredSynonyms.remove(synonym);
+                                    // Remove identified Synonyms from inferredSynonyms
+                                    for (Synonym synonym : inferredSynonymsToBeRemoved) {
+                                        inferredSynonyms.remove(synonym);
+                                    }
                                 }
                             }
-                        }
-
-                    }else if (type.equals(SynonymType.INFERRED_GENUS_OF())){
-
-                        for (Synonym synonymRelationOfTaxon:synonymsOfTaxon){
-
-                            inferredGenus = createInferredGenus(taxon,
-                                    zooHashMap, taxonName, epithetOfTaxon,
-                                    genusOfTaxon, taxonNames, zooParentName, synonymRelationOfTaxon);
 
-                            inferredSynonyms.add(inferredGenus);
-                            zooHashMap.put(inferredGenus.getName().getUuid(), inferredGenus.getName());
-                            taxonNames.add(inferredGenus.getName().getNameCache());
-                        }
+                        }else if (type.equals(SynonymType.INFERRED_GENUS_OF())){
 
-                        if (doWithMisappliedNames){
+                            for (Synonym synonymRelationOfTaxon:synonymsOfTaxon){
 
-                            for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
-                                Taxon misappliedName = taxonRelationship.getFromTaxon();
-                                inferredGenus = createInferredGenus(taxon, zooHashMap, taxonName, infraspecificEpithetOfTaxon, genusOfTaxon, taxonNames, zooParentName,  misappliedName);
+                                inferredGenus = createInferredGenus(taxon,
+                                        zooHashMap, taxonName, epithetOfTaxon,
+                                        genusOfTaxon, taxonNames, zooParentName, synonymRelationOfTaxon);
 
                                 inferredSynonyms.add(inferredGenus);
                                 zooHashMap.put(inferredGenus.getName().getUuid(), inferredGenus.getName());
-                                 taxonNames.add(inferredGenus.getName().getNameCache());
+                                taxonNames.add(inferredGenus.getName().getNameCache());
                             }
-                        }
 
+                            if (doWithMisappliedNames){
 
-                        if (!taxonNames.isEmpty()){
-                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
-                            IZoologicalName name;
-                            if (!synNotInCDM.isEmpty()){
-                                inferredSynonymsToBeRemoved.clear();
-
-                                for (Synonym syn :inferredSynonyms){
-                                    name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
-                                    if (!synNotInCDM.contains(name.getNameCache())){
-                                        inferredSynonymsToBeRemoved.add(syn);
-                                    }
-                                }
+                                for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
+                                    Taxon misappliedName = taxonRelationship.getFromTaxon();
+                                    inferredGenus = createInferredGenus(taxon, zooHashMap, taxonName, infraspecificEpithetOfTaxon, genusOfTaxon, taxonNames, zooParentName,  misappliedName);
 
-                                // Remove identified Synonyms from inferredSynonyms
-                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
-                                    inferredSynonyms.remove(synonym);
+                                    inferredSynonyms.add(inferredGenus);
+                                    zooHashMap.put(inferredGenus.getName().getUuid(), inferredGenus.getName());
+                                     taxonNames.add(inferredGenus.getName().getNameCache());
                                 }
                             }
-                        }
-
-                    }else if (type.equals(SynonymType.POTENTIAL_COMBINATION_OF())){
-
-                        Reference sourceReference = null; // TODO: Determination of sourceReference is redundant
-                        IZoologicalName inferredSynName;
-                        //for all synonyms of the parent...
-                        for (Synonym synonymRelationOfParent:synonyMsOfParent){
-                            TaxonName synName;
-                            HibernateProxyHelper.deproxy(synonymRelationOfParent);
-
-                            synName = synonymRelationOfParent.getName();
-
-                            // Set the sourceReference
-                            sourceReference = synonymRelationOfParent.getSec();
-
-                            // Determine the idInSource
-                            String idInSourceParent = getIdInSource(synonymRelationOfParent);
-
-                            IZoologicalName parentSynZooName = getZoologicalName(synName.getUuid(), zooHashMap);
-                            String synParentGenus = parentSynZooName.getGenusOrUninomial();
-                            String synParentInfragenericName = null;
-                            String synParentSpecificEpithet = null;
-
-                            if (parentSynZooName.isInfraGeneric()){
-                                synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
-                            }
-                            if (parentSynZooName.isSpecies()){
-                                synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
-                            }
-
-                           /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
-                                synonymsGenus.put(synGenusName, idInSource);
-                            }*/
 
-                            //for all synonyms of the taxon
 
-                            for (Synonym synonymRelationOfTaxon:synonymsOfTaxon){
-
-                                IZoologicalName zooSynName = getZoologicalName(synonymRelationOfTaxon.getName().getUuid(), zooHashMap);
-                                potentialCombination = createPotentialCombination(idInSourceParent, parentSynZooName, zooSynName,
-                                        synParentGenus,
-                                        synParentInfragenericName,
-                                        synParentSpecificEpithet, synonymRelationOfTaxon, zooHashMap);
+                            if (!taxonNames.isEmpty()){
+                                List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
+                                IZoologicalName name;
+                                if (!synNotInCDM.isEmpty()){
+                                    inferredSynonymsToBeRemoved.clear();
 
-                                taxon.addSynonym(potentialCombination, SynonymType.POTENTIAL_COMBINATION_OF());
-                                inferredSynonyms.add(potentialCombination);
-                                zooHashMap.put(potentialCombination.getName().getUuid(), potentialCombination.getName());
-                                 taxonNames.add(potentialCombination.getName().getNameCache());
+                                    for (Synonym syn :inferredSynonyms){
+                                        name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
+                                        if (!synNotInCDM.contains(name.getNameCache())){
+                                            inferredSynonymsToBeRemoved.add(syn);
+                                        }
+                                    }
 
+                                    // Remove identified Synonyms from inferredSynonyms
+                                    for (Synonym synonym : inferredSynonymsToBeRemoved) {
+                                        inferredSynonyms.remove(synonym);
+                                    }
+                                }
                             }
 
-                        }
-
-                        if (doWithMisappliedNames){
-
-                            for (TaxonRelationship parentRelationship: taxonRelListParent){
+                        }else if (type.equals(SynonymType.POTENTIAL_COMBINATION_OF())){
 
-                                TaxonName misappliedParentName;
-
-                                Taxon misappliedParent = parentRelationship.getFromTaxon();
-                                misappliedParentName = misappliedParent.getName();
+                            Reference sourceReference = null; // TODO: Determination of sourceReference is redundant
+                            //for all synonyms of the parent...
+                            for (Synonym synonymRelationOfParent:synonyMsOfParent){
+                                TaxonName synName;
+                                HibernateProxyHelper.deproxy(synonymRelationOfParent);
 
-                                HibernateProxyHelper.deproxy(misappliedParent);
+                                synName = synonymRelationOfParent.getName();
 
                                 // Set the sourceReference
-                                sourceReference = misappliedParent.getSec();
+                                sourceReference = synonymRelationOfParent.getSec();
 
                                 // Determine the idInSource
-                                String idInSourceParent = getIdInSource(misappliedParent);
+                                String idInSourceParent = getIdInSource(synonymRelationOfParent);
 
-                                IZoologicalName parentSynZooName = getZoologicalName(misappliedParentName.getUuid(), zooHashMap);
+                                IZoologicalName parentSynZooName = getZoologicalName(synName.getUuid(), zooHashMap);
                                 String synParentGenus = parentSynZooName.getGenusOrUninomial();
                                 String synParentInfragenericName = null;
                                 String synParentSpecificEpithet = null;
@@ -2494,16 +2529,19 @@ public class TaxonServiceImpl
                                     synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
                                 }
 
+                               /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
+                                    synonymsGenus.put(synGenusName, idInSource);
+                                }*/
 
-                                for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
-                                    Taxon misappliedName = taxonRelationship.getFromTaxon();
-                                    IZoologicalName zooMisappliedName = getZoologicalName(misappliedName.getName().getUuid(), zooHashMap);
-                                    potentialCombination = createPotentialCombination(
-                                            idInSourceParent, parentSynZooName, zooMisappliedName,
+                                //for all synonyms of the taxon
+
+                                for (Synonym synonymRelationOfTaxon:synonymsOfTaxon){
+
+                                    IZoologicalName zooSynName = getZoologicalName(synonymRelationOfTaxon.getName().getUuid(), zooHashMap);
+                                    potentialCombination = createPotentialCombination(idInSourceParent, parentSynZooName, zooSynName,
                                             synParentGenus,
                                             synParentInfragenericName,
-                                            synParentSpecificEpithet, misappliedName, zooHashMap);
-
+                                            synParentSpecificEpithet, synonymRelationOfTaxon, zooHashMap);
 
                                     taxon.addSynonym(potentialCombination, SynonymType.POTENTIAL_COMBINATION_OF());
                                     inferredSynonyms.add(potentialCombination);
@@ -2511,29 +2549,74 @@ public class TaxonServiceImpl
                                      taxonNames.add(potentialCombination.getName().getNameCache());
                                 }
                             }
-                        }
 
-                        if (!taxonNames.isEmpty()){
-                            List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
-                            IZoologicalName name;
-                            if (!synNotInCDM.isEmpty()){
-                                inferredSynonymsToBeRemoved.clear();
-                                for (Synonym syn :inferredSynonyms){
-                                    try{
-                                        name = syn.getName();
-                                    }catch (ClassCastException e){
-                                        name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
+                            if (doWithMisappliedNames){
+
+                                for (TaxonRelationship parentRelationship: taxonRelListParent){
+
+                                    TaxonName misappliedParentName;
+
+                                    Taxon misappliedParent = parentRelationship.getFromTaxon();
+                                    misappliedParentName = misappliedParent.getName();
+
+                                    HibernateProxyHelper.deproxy(misappliedParent);
+
+                                    // Set the sourceReference
+                                    sourceReference = misappliedParent.getSec();
+
+                                    // Determine the idInSource
+                                    String idInSourceParent = getIdInSource(misappliedParent);
+
+                                    IZoologicalName parentSynZooName = getZoologicalName(misappliedParentName.getUuid(), zooHashMap);
+                                    String synParentGenus = parentSynZooName.getGenusOrUninomial();
+                                    String synParentInfragenericName = null;
+                                    String synParentSpecificEpithet = null;
+
+                                    if (parentSynZooName.isInfraGeneric()){
+                                        synParentInfragenericName = parentSynZooName.getInfraGenericEpithet();
+                                    }
+                                    if (parentSynZooName.isSpecies()){
+                                        synParentSpecificEpithet = parentSynZooName.getSpecificEpithet();
                                     }
-                                    if (!synNotInCDM.contains(name.getNameCache())){
-                                        inferredSynonymsToBeRemoved.add(syn);
+
+                                    for (TaxonRelationship taxonRelationship: taxonRelListTaxon){
+                                        Taxon misappliedName = taxonRelationship.getFromTaxon();
+                                        IZoologicalName zooMisappliedName = getZoologicalName(misappliedName.getName().getUuid(), zooHashMap);
+                                        potentialCombination = createPotentialCombination(
+                                                idInSourceParent, parentSynZooName, zooMisappliedName,
+                                                synParentGenus,
+                                                synParentInfragenericName,
+                                                synParentSpecificEpithet, misappliedName, zooHashMap);
+
+                                        taxon.addSynonym(potentialCombination, SynonymType.POTENTIAL_COMBINATION_OF());
+                                        inferredSynonyms.add(potentialCombination);
+                                        zooHashMap.put(potentialCombination.getName().getUuid(), potentialCombination.getName());
+                                         taxonNames.add(potentialCombination.getName().getNameCache());
+                                    }
+                                }
+                            }
+
+                            if (!taxonNames.isEmpty()){
+                                List<String> synNotInCDM = dao.taxaByNameNotInDB(taxonNames);
+                                IZoologicalName name;
+                                if (!synNotInCDM.isEmpty()){
+                                    inferredSynonymsToBeRemoved.clear();
+                                    for (Synonym syn :inferredSynonyms){
+                                        try{
+                                            name = syn.getName();
+                                        }catch (ClassCastException e){
+                                            name = getZoologicalName(syn.getName().getUuid(), zooHashMap);
+                                        }
+                                        if (!synNotInCDM.contains(name.getNameCache())){
+                                            inferredSynonymsToBeRemoved.add(syn);
+                                        }
+                                     }
+                                    // Remove identified Synonyms from inferredSynonyms
+                                    for (Synonym synonym : inferredSynonymsToBeRemoved) {
+                                        inferredSynonyms.remove(synonym);
                                     }
-                                 }
-                                // Remove identified Synonyms from inferredSynonyms
-                                for (Synonym synonym : inferredSynonymsToBeRemoved) {
-                                    inferredSynonyms.remove(synonym);
                                 }
                             }
-                         }
                         }
                     }else {
                         logger.info("The synonym type is not defined.");
@@ -2541,7 +2624,6 @@ public class TaxonServiceImpl
                     }
                 }
             }
-
         }
 
         return inferredSynonyms;
@@ -2597,7 +2679,6 @@ public class TaxonServiceImpl
             inferredSynName.setInfraGenericEpithet(synParentInfragenericName);
         }
 
-
         potentialCombination = Synonym.NewInstance(inferredSynName, null);
 
         // Set the sourceReference
@@ -2640,14 +2721,13 @@ public class TaxonServiceImpl
         synName = syn.getName();
         IZoologicalName synZooName = getZoologicalName(synName.getUuid(), zooHashMap);
         String synSpeciesEpithetName = synZooName.getSpecificEpithet();
-                     /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
+         /* if (synonymsEpithet != null && !synonymsEpithet.contains(synSpeciesEpithetName)){
             synonymsEpithet.add(synSpeciesEpithetName);
         }*/
 
         inferredSynName = TaxonNameFactory.NewZoologicalInstance(taxon.getName().getRank());
         //TODO:differ between parent is genus and taxon is species, parent is subgenus and taxon is species, parent is species and taxon is subspecies and parent is genus and taxon is subgenus...
 
-
         inferredSynName.setGenusOrUninomial(genusOfTaxon);
         if (zooParentName.isInfraGeneric()){
             inferredSynName.setInfraGenericEpithet(zooParentName.getInfraGenericEpithet());
@@ -2661,7 +2741,6 @@ public class TaxonServiceImpl
             inferredSynName.setInfraSpecificEpithet(synZooName.getInfraGenericEpithet());
         }
 
-
         inferredGenus = Synonym.NewInstance(inferredSynName, null);
 
         // Set the sourceReference
@@ -2731,7 +2810,7 @@ public class TaxonServiceImpl
             synSpecificEpithet = zooSynName.getSpecificEpithet();
         }
 
-                     /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
+           /* if (synGenusName != null && !synonymsGenus.containsKey(synGenusName)){
             synonymsGenus.put(synGenusName, idInSource);
         }*/
 
@@ -2782,8 +2861,6 @@ public class TaxonServiceImpl
 
         inferredSynName.addSource(originalSource);
 
-
-
         taxon.addSynonym(inferredEpithet, SynonymType.INFERRED_EPITHET_OF());
 
         return inferredEpithet;
@@ -2830,11 +2907,9 @@ public class TaxonServiceImpl
             logger.warn("No idInSource for TaxonBase " + taxonBase.getUuid() + " - " + taxonBase.getTitleCache());
         }
 
-
         return idInSource;
     }
 
-
     /**
      * Returns the citation for a given Synonym.
      * @param syn
@@ -2952,11 +3027,32 @@ public class TaxonServiceImpl
         Set<CdmBase> references = commonService.getReferencingObjectsForDeletion(taxonBase);
         if (taxonBase instanceof Taxon){
             TaxonDeletionConfigurator taxonConfig = (TaxonDeletionConfigurator) config;
-            result = isDeletableForTaxon(references, taxonConfig);
+            List<String> propertyPaths = new ArrayList<>();
+            propertyPaths.add("taxonNodes");
+            Taxon taxon = (Taxon)load(taxonBaseUuid, propertyPaths);
+
+            result = isDeletableForTaxon(references, taxonConfig );
+
+            if (taxonConfig.isDeleteNameIfPossible()){
+                if (taxonBase.getName() != null){
+                    DeleteResult nameResult = nameService.isDeletable(taxonBase.getName().getUuid(), taxonConfig.getNameDeletionConfig(), taxon.getUuid());
+                    if (!nameResult.isOk()){
+                        result.addExceptions(nameResult.getExceptions());
+                    }
+                }
+
+            }
         }else{
             SynonymDeletionConfigurator synonymConfig = (SynonymDeletionConfigurator) config;
             result = isDeletableForSynonym(references, synonymConfig);
+            if (synonymConfig.isDeleteNameIfPossible()){
+                DeleteResult nameResult = nameService.isDeletable(taxonBase.getName().getUuid(), synonymConfig.getNameDeletionConfig(), taxonBase.getUuid());
+                if (!nameResult.isOk()){
+                    result.addExceptions(nameResult.getExceptions());
+                }
+            }
         }
+
         return result;
     }
 
@@ -2970,6 +3066,7 @@ public class TaxonServiceImpl
                 result.addRelatedObject(ref);
                 result.setAbort();
             }
+
         }
 
         return result;
@@ -2989,11 +3086,16 @@ public class TaxonServiceImpl
                 }
 
                 if (!config.isDeleteTaxonNodes() && (ref instanceof TaxonNode)){
+
                     message = "The taxon can't be deleted as long as it belongs to a taxon node.";
                 }
+                if (ref instanceof TaxonNode && config.getClassificationUuid() != null && !config.isDeleteInAllClassifications() && !((TaxonNode)ref).getClassification().getUuid().equals(config.getClassificationUuid())){
+                    message = "The taxon can't be deleted as long as it is used in more than one classification";
+
+                }
                 if (!config.isDeleteTaxonRelationships() && (ref instanceof TaxonRelationship)){
-                    if (!config.isDeleteMisappliedNamesAndInvalidDesignations() &&
-                            (((TaxonRelationship)ref).getType().isMisappliedNameOrInvalidDesignation())){
+                    if (!config.isDeleteMisappliedNames() &&
+                            (((TaxonRelationship)ref).getType().isMisappliedName())){
                         message = "The taxon can't be deleted as long as it has misapplied names or invalid designations.";
                     } else{
                         message = "The taxon can't be deleted as long as it belongs to taxon relationship.";
@@ -3007,7 +3109,6 @@ public class TaxonServiceImpl
                    message = "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this taxon";
                 }
 
-
                /* //PolytomousKeyNode
                 if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){
                     String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node";
@@ -3065,16 +3166,14 @@ public class TaxonServiceImpl
         return result;
     }
 
-    /**
-     * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
-     * data structure.
-     * @return the set of conceptually related taxa for further use
-     */
     /**
      * @param uncheckedTaxa
      * @param existingTaxa
      * @param config
-     * @return
+     *
+     * Computes all children and conceptually congruent and included taxa and adds them to the existingTaxa
+     * data structure.
+     * @return the set of conceptually related taxa for further use
      */
     private Set<Taxon> makeRelatedIncluded(Set<Taxon> uncheckedTaxa, IncludedTaxaDTO existingTaxa, IncludedTaxonConfiguration config) {
 
@@ -3131,10 +3230,13 @@ public class TaxonServiceImpl
                 if (config.includeDoubtful == false && fromRel.isDoubtful()){
                     continue;
                 }
-                if (fromRel.getType().equals(TaxonRelationshipType.CONGRUENT_TO()) ||
-                        !config.onlyCongruent && fromRel.getType().equals(TaxonRelationshipType.INCLUDES()) ||
-                        !config.onlyCongruent && fromRel.getType().equals(TaxonRelationshipType.CONGRUENT_OR_INCLUDES())
-                        ){
+                TaxonRelationshipType fromRelType = fromRel.getType();
+                if (fromRelType.equals(TaxonRelationshipType.CONGRUENT_TO()) ||
+                        !config.onlyCongruent && (
+                            fromRelType.equals(TaxonRelationshipType.INCLUDES()) ||
+                            fromRelType.equals(TaxonRelationshipType.CONGRUENT_OR_INCLUDES())
+                        )
+                    ){
                     result.add(fromRel.getToTaxon());
                 }
             }
@@ -3143,7 +3245,9 @@ public class TaxonServiceImpl
                 if (config.includeDoubtful == false && toRel.isDoubtful()){
                     continue;
                 }
-                if (toRel.getType().equals(TaxonRelationshipType.CONGRUENT_TO())){
+                TaxonRelationshipType fromRelType = toRel.getType();
+                if (fromRelType.equals(TaxonRelationshipType.CONGRUENT_TO()) ||
+                        !config.includeDoubtful && fromRelType.equals(TaxonRelationshipType.TAXONOMICALLY_INCLUDED_IN())){
                     result.add(toRel.getFromTaxon());
                 }
             }
@@ -3163,8 +3267,9 @@ public class TaxonServiceImpl
 
     @Override
     public List<TaxonBase> findTaxaByName(MatchingTaxonConfigurator config){
+        @SuppressWarnings("rawtypes")
         List<TaxonBase> taxonList = dao.getTaxaByName(true, config.isIncludeSynonyms(), false, false, false,
-                config.getTaxonNameTitle(), null, MatchMode.EXACT, null, config.isIncludeSynonyms(), null, 0, 0, config.getPropertyPath());
+                config.getTaxonNameTitle(), null, null, MatchMode.EXACT, null, config.isIncludeSynonyms(), null, 0, 0, config.getPropertyPath());
         return taxonList;
     }
 
@@ -3238,7 +3343,7 @@ public class TaxonServiceImpl
                return result;
        }
 
-       @Override
+    @Override
        public UpdateResult moveFactualDateToAnotherTaxon(UUID fromTaxonUuid, UUID toTaxonUuid){
                UpdateResult result = new UpdateResult();
 
@@ -3273,13 +3378,65 @@ public class TaxonServiceImpl
        @Override
        @Transactional(readOnly = false)
        public UpdateResult swapSynonymAndAcceptedTaxon(UUID synonymUUid,
-                       UUID acceptedTaxonUuid) {
+                       UUID acceptedTaxonUuid, boolean setNameInSource) {
                TaxonBase<?> base = this.load(synonymUUid);
                Synonym syn = HibernateProxyHelper.deproxy(base, Synonym.class);
                base = this.load(acceptedTaxonUuid);
                Taxon taxon = HibernateProxyHelper.deproxy(base, Taxon.class);
 
-               return this.swapSynonymAndAcceptedTaxon(syn, taxon);
+               return this.swapSynonymAndAcceptedTaxon(syn, taxon, setNameInSource);
        }
 
+    @Override
+    public TaxonRelationshipsDTO listTaxonRelationships(UUID taxonUuid, Set<TaxonRelationshipType> directTypes,
+            Set<TaxonRelationshipType> inversTypes,
+            Direction direction, boolean groupMisapplications,
+            boolean includeUnpublished,
+            Integer pageSize, Integer pageNumber) {
+        TaxonBase<?> taxonBase = dao.load(taxonUuid);
+        if (taxonBase == null || !taxonBase.isInstanceOf(TaxonBase.class)){
+            //TODO handle
+            throw new RuntimeException("Taxon for uuid " + taxonUuid + " not found");
+        }else{
+            Taxon taxon = CdmBase.deproxy(taxonBase, Taxon.class);
+            boolean doDirect = (direction == null || direction == Direction.relatedTo);
+            boolean doInvers = (direction == null || direction == Direction.relatedFrom);
+
+            TaxonRelationshipsDTO dto = new TaxonRelationshipsDTO();
+
+            //TODO paging is difficult because misapplication string is an attribute
+            //of toplevel dto
+//        long numberOfResults = dao.countTaxonRelationships(taxon, type, includeUnpublished, TaxonRelationship.Direction.relatedFrom);
+//        List<TaxonRelationshipsDTO> results = new ArrayList<>();
+//        if(numberOfResults > 0) { // no point checking again
+//            results = dao.getTaxonRelationships(taxon, type, includeUnpublished, pageSize, pageNumber, orderHints, propertyPaths, TaxonRelationship.Direction.relatedFrom);
+//        }
+//
+//        return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);;
+
+            //TODO languages
+            List<Language> languages = null;
+            if (doDirect){
+                direction = Direction.relatedTo;
+                //TODO order hints, property path
+                List<TaxonRelationship> relations = dao.getTaxonRelationships(taxon, directTypes, includeUnpublished, pageSize, pageNumber, null, null, direction.invers());
+                for (TaxonRelationship relation : relations){
+                    dto.addRelation(relation, direction, languages);
+                }
+            }
+            if (doInvers){
+                direction = Direction.relatedFrom;
+                //TODO order hints, property path
+                List<TaxonRelationship> relations = dao.getTaxonRelationships(taxon, inversTypes, includeUnpublished, pageSize, pageNumber, null, null, direction.invers());
+                for (TaxonRelationship relation : relations){
+                    dto.addRelation(relation, direction, languages);
+                }
+            }
+            if (groupMisapplications){
+                //TODO
+                dto.createMisapplicationString();
+            }
+            return dto;
+        }
+    }
 }