ref #6668 Include deleted derivatives recursively into DeleteResult
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / OccurrenceServiceImpl.java
index c7e77fb94c264c1a4b722e4a3b044b240644e4ae..e2f1411fc00f2ccea3d6a2f049c854d953b82cc0 100644 (file)
-// $Id$\r
-/**\r
- * Copyright (C) 2007 EDIT\r
- * European Distributed Institute of Taxonomy\r
- * http://www.e-taxonomy.eu\r
- *\r
- * The contents of this file are subject to the Mozilla Public License Version 1.1\r
- * See LICENSE.TXT at the top of this package for the full license terms.\r
- */\r
-\r
-package eu.etaxonomy.cdm.api.service;\r
-\r
-import java.io.IOException;\r
-import java.net.URI;\r
-import java.net.URISyntaxException;\r
-import java.util.ArrayList;\r
-import java.util.Arrays;\r
-import java.util.Collection;\r
-import java.util.HashMap;\r
-import java.util.HashSet;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Map.Entry;\r
-import java.util.Set;\r
-import java.util.UUID;\r
-\r
-import org.apache.log4j.Logger;\r
-import org.apache.lucene.index.CorruptIndexException;\r
-import org.apache.lucene.queryParser.ParseException;\r
-import org.apache.lucene.search.BooleanClause.Occur;\r
-import org.apache.lucene.search.BooleanQuery;\r
-import org.apache.lucene.search.SortField;\r
-import org.hibernate.TransientObjectException;\r
-import org.hibernate.search.spatial.impl.Rectangle;\r
-import org.joda.time.Partial;\r
-import org.springframework.beans.factory.annotation.Autowired;\r
-import org.springframework.stereotype.Service;\r
-import org.springframework.transaction.annotation.Transactional;\r
-\r
-import eu.etaxonomy.cdm.api.facade.DerivedUnitFacade;\r
-import eu.etaxonomy.cdm.api.facade.DerivedUnitFacadeConfigurator;\r
-import eu.etaxonomy.cdm.api.facade.DerivedUnitFacadeNotSupportedException;\r
-import eu.etaxonomy.cdm.api.service.dto.DerivateHierarchyDTO;\r
-import eu.etaxonomy.cdm.api.service.molecular.ISequenceService;\r
-import eu.etaxonomy.cdm.api.service.pager.Pager;\r
-import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;\r
-import eu.etaxonomy.cdm.api.service.search.ILuceneIndexToolProvider;\r
-import eu.etaxonomy.cdm.api.service.search.ISearchResultBuilder;\r
-import eu.etaxonomy.cdm.api.service.search.LuceneSearch;\r
-import eu.etaxonomy.cdm.api.service.search.LuceneSearch.TopGroupsWithMaxScore;\r
-import eu.etaxonomy.cdm.api.service.search.QueryFactory;\r
-import eu.etaxonomy.cdm.api.service.search.SearchResult;\r
-import eu.etaxonomy.cdm.api.service.search.SearchResultBuilder;\r
-import eu.etaxonomy.cdm.api.service.util.TaxonRelationshipEdge;\r
-import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;\r
-import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;\r
-import eu.etaxonomy.cdm.model.CdmBaseType;\r
-import eu.etaxonomy.cdm.model.agent.AgentBase;\r
-import eu.etaxonomy.cdm.model.common.DefinedTerm;\r
-import eu.etaxonomy.cdm.model.common.DefinedTermBase;\r
-import eu.etaxonomy.cdm.model.common.ICdmBase;\r
-import eu.etaxonomy.cdm.model.common.Language;\r
-import eu.etaxonomy.cdm.model.common.UuidAndTitleCache;\r
-import eu.etaxonomy.cdm.model.description.DescriptionBase;\r
-import eu.etaxonomy.cdm.model.description.DescriptionElementBase;\r
-import eu.etaxonomy.cdm.model.description.IndividualsAssociation;\r
-import eu.etaxonomy.cdm.model.location.Country;\r
-import eu.etaxonomy.cdm.model.location.NamedArea;\r
-import eu.etaxonomy.cdm.model.media.Media;\r
-import eu.etaxonomy.cdm.model.media.MediaRepresentation;\r
-import eu.etaxonomy.cdm.model.media.MediaRepresentationPart;\r
-import eu.etaxonomy.cdm.model.molecular.DnaSample;\r
-import eu.etaxonomy.cdm.model.molecular.Sequence;\r
-import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;\r
-import eu.etaxonomy.cdm.model.name.TaxonNameBase;\r
-import eu.etaxonomy.cdm.model.name.TypeDesignationStatusBase;\r
-import eu.etaxonomy.cdm.model.occurrence.DerivationEvent;\r
-import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;\r
-import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;\r
-import eu.etaxonomy.cdm.model.occurrence.FieldUnit;\r
-import eu.etaxonomy.cdm.model.occurrence.GatheringEvent;\r
-import eu.etaxonomy.cdm.model.occurrence.MediaSpecimen;\r
-import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;\r
-import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;\r
-import eu.etaxonomy.cdm.model.taxon.Taxon;\r
-import eu.etaxonomy.cdm.model.taxon.TaxonBase;\r
-import eu.etaxonomy.cdm.persistence.dao.common.IDefinedTermDao;\r
-import eu.etaxonomy.cdm.persistence.dao.initializer.AbstractBeanInitializer;\r
-import eu.etaxonomy.cdm.persistence.dao.occurrence.IOccurrenceDao;\r
-import eu.etaxonomy.cdm.persistence.query.OrderHint;\r
-import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;\r
-\r
-/**\r
- * @author a.babadshanjan\r
- * @created 01.09.2008\r
- */\r
-@Service\r
-@Transactional(readOnly = true)\r
-public class OccurrenceServiceImpl extends IdentifiableServiceBase<SpecimenOrObservationBase,IOccurrenceDao> implements IOccurrenceService {\r
-\r
-    static private final Logger logger = Logger.getLogger(OccurrenceServiceImpl.class);\r
-\r
-    @Autowired\r
-    private IDefinedTermDao definedTermDao;\r
-\r
-    @Autowired\r
-    private IDescriptionService descriptionService;\r
-\r
-    @Autowired\r
-    private ITaxonService taxonService;\r
-\r
-    @Autowired\r
-    private ITermService termService;\r
-\r
-    @Autowired\r
-    private INameService nameService;\r
-\r
-    @Autowired\r
-    private ISequenceService sequenceService;\r
-\r
-    @Autowired\r
-    private AbstractBeanInitializer beanInitializer;\r
-\r
-    @Autowired\r
-    private ILuceneIndexToolProvider luceneIndexToolProvider;\r
-\r
-\r
-    public OccurrenceServiceImpl() {\r
-        logger.debug("Load OccurrenceService Bean");\r
-    }\r
-\r
-\r
-    /* (non-Javadoc)\r
-     * @see eu.etaxonomy.cdm.api.service.IIdentifiableEntityService#updateTitleCache(java.lang.Integer, eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy)\r
-     */\r
-    @Override\r
-    @Transactional(readOnly = false)\r
-    public void updateTitleCache(Class<? extends SpecimenOrObservationBase> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<SpecimenOrObservationBase> cacheStrategy, IProgressMonitor monitor) {\r
-        if (clazz == null){\r
-            clazz = SpecimenOrObservationBase.class;\r
-        }\r
-        super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);\r
-    }\r
-\r
-\r
-    /**\r
-     * FIXME Candidate for harmonization\r
-     * move to termService\r
-     */\r
-    @Override\r
-    public Country getCountryByIso(String iso639) {\r
-        return this.definedTermDao.getCountryByIso(iso639);\r
-\r
-    }\r
-\r
-    /**\r
-     * FIXME Candidate for harmonization\r
-     * move to termService\r
-     */\r
-    @Override\r
-    public List<Country> getCountryByName(String name) {\r
-        List<? extends DefinedTermBase> terms = this.definedTermDao.findByTitle(Country.class, name, null, null, null, null, null, null) ;\r
-        List<Country> countries = new ArrayList<Country>();\r
-        for (int i=0;i<terms.size();i++){\r
-            countries.add((Country)terms.get(i));\r
-        }\r
-        return countries;\r
-    }\r
-\r
-    @Override\r
-    @Autowired\r
-    protected void setDao(IOccurrenceDao dao) {\r
-        this.dao = dao;\r
-    }\r
-\r
-    @Override\r
-    public Pager<DerivationEvent> getDerivationEvents(SpecimenOrObservationBase occurence, Integer pageSize,Integer pageNumber, List<String> propertyPaths) {\r
-        Integer numberOfResults = dao.countDerivationEvents(occurence);\r
-\r
-        List<DerivationEvent> results = new ArrayList<DerivationEvent>();\r
-        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)\r
-            results = dao.getDerivationEvents(occurence, pageSize, pageNumber,propertyPaths);\r
-        }\r
-\r
-        return new DefaultPagerImpl<DerivationEvent>(pageNumber, numberOfResults, pageSize, results);\r
-    }\r
-\r
-    /* (non-Javadoc)\r
-     * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#countDeterminations(eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase, eu.etaxonomy.cdm.model.taxon.TaxonBase)\r
-     */\r
-    @Override\r
-    public int countDeterminations(SpecimenOrObservationBase occurence, TaxonBase taxonbase) {\r
-        return dao.countDeterminations(occurence, taxonbase);\r
-    }\r
-\r
-    @Override\r
-    public Pager<DeterminationEvent> getDeterminations(SpecimenOrObservationBase occurrence, TaxonBase taxonBase, Integer pageSize,Integer pageNumber, List<String> propertyPaths) {\r
-        Integer numberOfResults = dao.countDeterminations(occurrence, taxonBase);\r
-\r
-        List<DeterminationEvent> results = new ArrayList<DeterminationEvent>();\r
-        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)\r
-            results = dao.getDeterminations(occurrence,taxonBase, pageSize, pageNumber, propertyPaths);\r
-        }\r
-\r
-        return new DefaultPagerImpl<DeterminationEvent>(pageNumber, numberOfResults, pageSize, results);\r
-    }\r
-\r
-    @Override\r
-    public Pager<Media> getMedia(SpecimenOrObservationBase occurence,Integer pageSize, Integer pageNumber, List<String> propertyPaths) {\r
-        Integer numberOfResults = dao.countMedia(occurence);\r
-\r
-        List<Media> results = new ArrayList<Media>();\r
-        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)\r
-            results = dao.getMedia(occurence, pageSize, pageNumber, propertyPaths);\r
-        }\r
-\r
-        return new DefaultPagerImpl<Media>(pageNumber, numberOfResults, pageSize, results);\r
-    }\r
-\r
-    /* (non-Javadoc)\r
-     * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#list(java.lang.Class, eu.etaxonomy.cdm.model.taxon.TaxonBase, java.lang.Integer, java.lang.Integer, java.util.List, java.util.List)\r
-     */\r
-    @Override\r
-    public Pager<SpecimenOrObservationBase> list(Class<? extends SpecimenOrObservationBase> type, TaxonBase determinedAs, Integer pageSize, Integer pageNumber,        List<OrderHint> orderHints, List<String> propertyPaths) {\r
-        Integer numberOfResults = dao.count(type,determinedAs);\r
-        List<SpecimenOrObservationBase> results = new ArrayList<SpecimenOrObservationBase>();\r
-        pageNumber = pageNumber == null ? 0 : pageNumber;\r
-        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)\r
-            Integer start = pageSize == null ? 0 : pageSize * pageNumber;\r
-            results = dao.list(type,determinedAs, pageSize, start, orderHints,propertyPaths);\r
-        }\r
-        return new DefaultPagerImpl<SpecimenOrObservationBase>(pageNumber, numberOfResults, pageSize, results);\r
-    }\r
-\r
-    @Override\r
-    public List<UuidAndTitleCache<DerivedUnit>> getDerivedUnitUuidAndTitleCache() {\r
-        return dao.getDerivedUnitUuidAndTitleCache();\r
-    }\r
-\r
-    @Override\r
-    public List<UuidAndTitleCache<FieldUnit>> getFieldUnitUuidAndTitleCache() {\r
-        return dao.getFieldUnitUuidAndTitleCache();\r
-    }\r
-\r
-    /* (non-Javadoc)\r
-     * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#getDerivedUnitFacade(eu.etaxonomy.cdm.model.occurrence.DerivedUnit)\r
-     */\r
-    @Override\r
-    public DerivedUnitFacade getDerivedUnitFacade(DerivedUnit derivedUnit, List<String> propertyPaths) throws DerivedUnitFacadeNotSupportedException {\r
-        derivedUnit = (DerivedUnit)dao.load(derivedUnit.getUuid(), null);\r
-        DerivedUnitFacadeConfigurator config = DerivedUnitFacadeConfigurator.NewInstance();\r
-        config.setThrowExceptionForNonSpecimenPreservationMethodRequest(false);\r
-        DerivedUnitFacade derivedUnitFacade = DerivedUnitFacade.NewInstance(derivedUnit, config);\r
-        beanInitializer.initialize(derivedUnitFacade, propertyPaths);\r
-        return derivedUnitFacade;\r
-    }\r
-\r
-    /* (non-Javadoc)\r
-     * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#listDerivedUnitFacades(eu.etaxonomy.cdm.model.description.DescriptionBase, java.util.List)\r
-     */\r
-    @Override\r
-    public List<DerivedUnitFacade> listDerivedUnitFacades(\r
-            DescriptionBase description, List<String> propertyPaths) {\r
-\r
-        List<DerivedUnitFacade> derivedUnitFacadeList = new ArrayList<DerivedUnitFacade>();\r
-        IndividualsAssociation tempIndividualsAssociation;\r
-        SpecimenOrObservationBase tempSpecimenOrObservationBase;\r
-        List<DescriptionElementBase> elements = descriptionService.listDescriptionElements(description, null, IndividualsAssociation.class, null, 0, Arrays.asList(new String []{"associatedSpecimenOrObservation"}));\r
-        for(DescriptionElementBase element : elements){\r
-            if(element instanceof IndividualsAssociation){\r
-                tempIndividualsAssociation = (IndividualsAssociation)element;\r
-                if(tempIndividualsAssociation.getAssociatedSpecimenOrObservation() != null){\r
-                    tempSpecimenOrObservationBase = HibernateProxyHelper.deproxy(tempIndividualsAssociation.getAssociatedSpecimenOrObservation(), SpecimenOrObservationBase.class);\r
-                    if(tempSpecimenOrObservationBase instanceof DerivedUnit){\r
-                        try {\r
-                            derivedUnitFacadeList.add(DerivedUnitFacade.NewInstance((DerivedUnit)tempSpecimenOrObservationBase));\r
-                        } catch (DerivedUnitFacadeNotSupportedException e) {\r
-                            logger.warn(tempIndividualsAssociation.getAssociatedSpecimenOrObservation().getTitleCache() + " : " +e.getMessage());\r
-                        }\r
-                    }\r
-                }\r
-\r
-            }\r
-        }\r
-\r
-        beanInitializer.initializeAll(derivedUnitFacadeList, propertyPaths);\r
-\r
-        return derivedUnitFacadeList;\r
-    }\r
-\r
-\r
-    /* (non-Javadoc)\r
-     * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#listByAnyAssociation(java.lang.Class, java.util.Set, eu.etaxonomy.cdm.model.taxon.Taxon, java.lang.Integer, java.lang.Integer, java.util.List, java.util.List)\r
-     */\r
-    @Override\r
-    public <T extends SpecimenOrObservationBase> List<T> listByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,\r
-            Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {\r
-\r
-        return pageByAssociatedTaxon(type, includeRelationships, associatedTaxon, maxDepth, pageSize, pageNumber, orderHints, propertyPaths).getRecords();\r
-    }\r
-\r
-    /* (non-Javadoc)\r
-     * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#listByAnyAssociation(java.lang.Class, java.util.Set, eu.etaxonomy.cdm.model.taxon.Taxon, java.lang.Integer, java.lang.Integer, java.util.List, java.util.List)\r
-     */\r
-    @Override\r
-    public Collection<FieldUnit> listFieldUnitsByAssociatedTaxon(Set<TaxonRelationshipEdge> includeRelationships,\r
-            Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {\r
-\r
-        if(!getSession().contains(associatedTaxon)){\r
-            associatedTaxon = (Taxon) taxonService.load(associatedTaxon.getUuid());\r
-        }\r
-\r
-        Set<FieldUnit> fieldUnits = new HashSet<FieldUnit>();\r
-\r
-        List<SpecimenOrObservationBase> records = pageByAssociatedTaxon(null, includeRelationships, associatedTaxon, maxDepth, pageSize, pageNumber, orderHints, propertyPaths).getRecords();\r
-        for(SpecimenOrObservationBase<?> specimen:records){\r
-            fieldUnits.addAll(getFieldUnits(specimen.getUuid()));\r
-        }\r
-        return fieldUnits;\r
-    }\r
-\r
-    @Override\r
-    public DerivateHierarchyDTO assembleDerivateHierarchyDTO(FieldUnit fieldUnit, UUID associatedTaxonUuid){\r
-\r
-        if(!getSession().contains(fieldUnit)){\r
-            fieldUnit = (FieldUnit) load(fieldUnit.getUuid());\r
-        }\r
-        TaxonBase associatedTaxon = taxonService.load(associatedTaxonUuid);\r
-\r
-        DerivateHierarchyDTO dto = new DerivateHierarchyDTO();\r
-        Map<UUID, TypeDesignationStatusBase> typeSpecimenUUIDtoTypeDesignationStatus = new HashMap<UUID, TypeDesignationStatusBase>();\r
-\r
-        //gather types for this taxon name\r
-        TaxonNameBase<?,?> name = associatedTaxon.getName();\r
-        Set<?> typeDesignations = name.getSpecimenTypeDesignations();\r
-        for (Object object : typeDesignations) {\r
-            if(object instanceof SpecimenTypeDesignation){\r
-                SpecimenTypeDesignation specimenTypeDesignation = (SpecimenTypeDesignation)object;\r
-                DerivedUnit typeSpecimen = specimenTypeDesignation.getTypeSpecimen();\r
-                final TypeDesignationStatusBase typeStatus = specimenTypeDesignation.getTypeStatus();\r
-                typeSpecimenUUIDtoTypeDesignationStatus.put(typeSpecimen.getUuid(), typeStatus);\r
-            }\r
-        }\r
-\r
-        if(fieldUnit.getGatheringEvent()!=null){\r
-            GatheringEvent gatheringEvent = fieldUnit.getGatheringEvent();\r
-            //Country\r
-            final NamedArea country = gatheringEvent.getCountry();\r
-            dto.setCountry(country!=null?country.getDescription():"");\r
-            //Collection\r
-            final AgentBase collector = gatheringEvent.getCollector();\r
-            final String fieldNumber = fieldUnit.getFieldNumber();\r
-            dto.setCollection(((collector!=null?collector:"") + " " + (fieldNumber!=null?fieldNumber:"")).trim());\r
-            //Date\r
-            final Partial gatheringDate = gatheringEvent.getGatheringDate();\r
-            dto.setDate(gatheringDate!=null?gatheringDate.toString():"");\r
-        }\r
-\r
-        //Taxon Name\r
-        dto.setTaxonName(associatedTaxon.getName().getFullTitleCache());\r
-\r
-\r
-        Collection<DerivedUnit> derivedUnits = new ArrayList<DerivedUnit>();\r
-        getDerivedUnitsFor(fieldUnit, derivedUnits);\r
-\r
-        //Herbaria map\r
-        Map<eu.etaxonomy.cdm.model.occurrence.Collection, Integer> collectionToCountMap = new HashMap<eu.etaxonomy.cdm.model.occurrence.Collection, Integer>();\r
-        //List of accession numbers for citation\r
-        List<String> preservedSpecimenAccessionNumbers = new ArrayList<String>();\r
-\r
-        //iterate over sub derivates\r
-        for (DerivedUnit derivedUnit : derivedUnits) {\r
-            //current accession number\r
-            String currentAccessionNumber = derivedUnit.getAccessionNumber()!=null?derivedUnit.getAccessionNumber():"";\r
-            //current herbarium\r
-            String currentHerbarium = "";\r
-            eu.etaxonomy.cdm.model.occurrence.Collection collection = derivedUnit.getCollection();\r
-            if(collection!=null){\r
-                currentHerbarium = collection.getCode()!=null?collection.getCode():"";\r
-                //count herbaria\r
-                Integer count = collectionToCountMap.get(collection);\r
-                if(count==null){\r
-                    count = 1;\r
-                }\r
-                else{\r
-                    count++;\r
-                }\r
-                collectionToCountMap.put(collection, count);\r
-            }\r
-            //check if derived unit is a type\r
-            if(typeSpecimenUUIDtoTypeDesignationStatus.keySet().contains(derivedUnit.getUuid())){\r
-                dto.setHasType(true);\r
-                TypeDesignationStatusBase typeDesignationStatus = typeSpecimenUUIDtoTypeDesignationStatus.get(derivedUnit.getUuid());\r
-                String typeStatus = typeDesignationStatus.getLabel();\r
-                dto.addTypes(typeStatus, currentAccessionNumber);\r
-            }\r
-            //assemble molecular data\r
-            if(derivedUnit instanceof DnaSample){\r
-                if(derivedUnit.getRecordBasis()==SpecimenOrObservationType.TissueSample){\r
-                    //TODO implement TissueSample assembly for web service\r
-                }\r
-                if(derivedUnit.getRecordBasis()==SpecimenOrObservationType.DnaSample){\r
-                    dto.setHasDna(true);\r
-\r
-                    DnaSample dna = (DnaSample)derivedUnit;\r
-                    for(Sequence sequence:dna.getSequences()){\r
-                        URI boldUri = null;\r
-                        try {\r
-                            boldUri = sequence.getBoldUri();\r
-                        } catch (URISyntaxException e1) {\r
-                            logger.error("Could not create BOLD URI", e1);\r
-                        }\r
-                        final DefinedTerm dnaMarker = sequence.getDnaMarker();\r
-                        dto.addMolecularData(boldUri!=null?boldUri.toString():"", dnaMarker!=null?dnaMarker.getLabel():"[no marker]");\r
-                    }\r
-                }\r
-            }\r
-            //assemble media data\r
-            else if(derivedUnit instanceof MediaSpecimen){\r
-\r
-                MediaSpecimen media = (MediaSpecimen)derivedUnit;\r
-                String mediaUriString = getMediaUriString(media);\r
-                if(media.getKindOfUnit()!=null){\r
-                    //specimen scan\r
-                    if(media.getKindOfUnit().getUuid().equals(UUID.fromString("acda15be-c0e2-4ea8-8783-b9b0c4ad7f03"))){\r
-                        dto.setHasSpecimenScan(true);\r
-                            final String imageLinkText = currentHerbarium+" "+currentAccessionNumber;\r
-                            dto.addSpecimenScan(mediaUriString==null?"":mediaUriString, !imageLinkText.equals(" ")?imageLinkText:"[no accession]");\r
-                    }\r
-                    //detail image\r
-                    else if(media.getKindOfUnit().getUuid().equals(UUID.fromString("31eb8d02-bf5d-437c-bcc6-87a626445f34"))){\r
-                        dto.setHasDetailImage(true);\r
-                        String motif = "";\r
-                        if(media.getMediaSpecimen()!=null && media.getMediaSpecimen().getTitle()!=null){\r
-                            motif = media.getMediaSpecimen().getTitle().getText();\r
-                        }\r
-                        dto.addDetailImage(mediaUriString==null?"":mediaUriString, motif!=null?motif:"[no motif]");\r
-                    }\r
-                }\r
-            }\r
-            //assemble preserved specimen data\r
-            else if(derivedUnit.getRecordBasis()==SpecimenOrObservationType.PreservedSpecimen){\r
-                if(!currentAccessionNumber.isEmpty()){\r
-                    preservedSpecimenAccessionNumbers.add(currentAccessionNumber);\r
-                }\r
-            }\r
-        }\r
-\r
-        final String separator = ", ";\r
-        //assemble citation\r
-        String citation = "";\r
-        citation += !dto.getCountry().isEmpty()?dto.getCountry()+separator:"";\r
-        if(fieldUnit.getGatheringEvent()!=null){\r
-            if(fieldUnit.getGatheringEvent().getLocality()!=null){\r
-                citation += fieldUnit.getGatheringEvent().getLocality().getText();\r
-                citation += separator;\r
-            }\r
-            if(fieldUnit.getGatheringEvent().getExactLocation()!=null\r
-                    && fieldUnit.getGatheringEvent().getExactLocation().getLatitude()!=null\r
-                    && fieldUnit.getGatheringEvent().getExactLocation().getLongitude()!=null){\r
-                citation += fieldUnit.getGatheringEvent().getExactLocation().getLatitude().toString();\r
-                citation += separator;\r
-                citation += fieldUnit.getGatheringEvent().getExactLocation().getLongitude().toString();\r
-                citation += separator;\r
-            }\r
-        }\r
-        citation += !dto.getCollection().isEmpty()?dto.getCollection()+separator:"";\r
-        if(!preservedSpecimenAccessionNumbers.isEmpty()){\r
-            citation += "(";\r
-            for(String accessionNumber:preservedSpecimenAccessionNumbers){\r
-                if(!accessionNumber.isEmpty()){\r
-                    citation += accessionNumber+separator;\r
-                }\r
-            }\r
-            citation = removeTail(citation, separator);\r
-            citation += ")";\r
-        }\r
-        citation = removeTail(citation, separator);\r
-        dto.setCitation(citation);\r
-\r
-        //assemble herbaria string\r
-        String herbariaString = "";\r
-        for(Entry<eu.etaxonomy.cdm.model.occurrence.Collection, Integer> e:collectionToCountMap.entrySet()){\r
-            eu.etaxonomy.cdm.model.occurrence.Collection collection = e.getKey();\r
-            if(collection.getCode()!=null){\r
-                herbariaString += collection.getCode();\r
-            }\r
-            if(e.getValue()>1){\r
-                herbariaString += "("+e.getValue()+")";\r
-            }\r
-            herbariaString += separator;\r
-        }\r
-        herbariaString = removeTail(herbariaString, separator);\r
-        dto.setHerbarium(herbariaString);\r
-\r
-        return dto;\r
-    }\r
-\r
-\r
-    /**\r
-     * @param string\r
-     * @param tail\r
-     * @return\r
-     */\r
-    private String removeTail(String string, final String tail) {\r
-        if(string.endsWith(tail)){\r
-            string = string.substring(0, string.length()-tail.length());\r
-        }\r
-        return string;\r
-    }\r
-\r
-    private String getMediaUriString(MediaSpecimen mediaSpecimen){\r
-        String mediaUri = null;\r
-        Collection<MediaRepresentation> mediaRepresentations = mediaSpecimen.getMediaSpecimen().getRepresentations();\r
-        if(mediaRepresentations!=null && !mediaRepresentations.isEmpty()){\r
-            Collection<MediaRepresentationPart> mediaRepresentationParts = mediaRepresentations.iterator().next().getParts();\r
-            if(mediaRepresentationParts!=null && !mediaRepresentationParts.isEmpty()){\r
-                MediaRepresentationPart part = mediaRepresentationParts.iterator().next();\r
-                if(part.getUri()!=null){\r
-                    mediaUri = part.getUri().toASCIIString();\r
-                }\r
-            }\r
-        }\r
-        return mediaUri;\r
-    }\r
-\r
-    private void getDerivedUnitsFor(SpecimenOrObservationBase<?> specimen, Collection<DerivedUnit> derivedUnits){\r
-        for(DerivationEvent derivationEvent:specimen.getDerivationEvents()){\r
-            for(DerivedUnit derivative:derivationEvent.getDerivatives()){\r
-                derivedUnits.add(derivative);\r
-                getDerivedUnitsFor(derivative, derivedUnits);\r
-            }\r
-        }\r
-    }\r
-\r
-\r
-    /* (non-Javadoc)\r
-     * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#pageByAssociatedTaxon(java.lang.Class, java.util.Set, eu.etaxonomy.cdm.model.taxon.Taxon, java.lang.Integer, java.lang.Integer, java.lang.Integer, java.util.List, java.util.List)\r
-     */\r
-    @SuppressWarnings("unchecked")\r
-    @Override\r
-    public <T extends SpecimenOrObservationBase> Pager<T> pageByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,\r
-            Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {\r
-\r
-        Set<Taxon> taxa = new HashSet<Taxon>();\r
-        Set<Integer> occurrenceIds = new HashSet<Integer>();\r
-        List<T> occurrences = new ArrayList<T>();\r
-\r
-//        Integer limit = PagerUtils.limitFor(pageSize);\r
-//        Integer start = PagerUtils.startFor(pageSize, pageNumber);\r
-\r
-        if(!getSession().contains(associatedTaxon)){\r
-            associatedTaxon = (Taxon) taxonService.load(associatedTaxon.getUuid());\r
-        }\r
-\r
-        if(includeRelationships != null) {\r
-            taxa = taxonService.listRelatedTaxa(associatedTaxon, includeRelationships, maxDepth, null, null, propertyPaths);\r
-        }\r
-\r
-        taxa.add(associatedTaxon);\r
-\r
-        for (Taxon taxon : taxa) {\r
-            List<T> perTaxonOccurrences = dao.listByAssociatedTaxon(type, taxon, null, null, orderHints, propertyPaths);\r
-            for (SpecimenOrObservationBase o : perTaxonOccurrences) {\r
-                occurrenceIds.add(o.getId());\r
-            }\r
-        }\r
-        occurrences = (List<T>) dao.listByIds(occurrenceIds, pageSize, pageNumber, orderHints, propertyPaths);\r
-\r
-        return new DefaultPagerImpl<T>(pageNumber, occurrenceIds.size(), pageSize, occurrences);\r
-\r
-    }\r
-\r
-    /* (non-Javadoc)\r
-     * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#pageByAssociatedTaxon(java.lang.Class, java.util.Set, eu.etaxonomy.cdm.model.taxon.Taxon, java.lang.Integer, java.lang.Integer, java.lang.Integer, java.util.List, java.util.List)\r
-     */\r
-    @SuppressWarnings("unchecked")\r
-    @Override\r
-    public <T extends SpecimenOrObservationBase> Pager<T> pageByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,\r
-            String taxonUUID, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {\r
-\r
-        UUID uuid = UUID.fromString(taxonUUID);\r
-        Taxon tax = (Taxon) taxonService.load(uuid);\r
-       //TODO REMOVE NULL STATEMENT\r
-type=null;\r
-        return pageByAssociatedTaxon( type,includeRelationships,tax, maxDepth, pageSize, pageNumber, orderHints, propertyPaths );\r
-\r
-    }\r
-\r
-\r
-    @Override\r
-    public Pager<SearchResult<SpecimenOrObservationBase>> findByFullText(\r
-            Class<? extends SpecimenOrObservationBase> clazz, String queryString, Rectangle boundingBox, List<Language> languages,\r
-            boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,\r
-            List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {\r
-\r
-        LuceneSearch luceneSearch = prepareByFullTextSearch(clazz, queryString, boundingBox, languages, highlightFragments);\r
-\r
-        // --- execute search\r
-        TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);\r
-\r
-        Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();\r
-        idFieldMap.put(CdmBaseType.SPECIMEN_OR_OBSERVATIONBASE, "id");\r
-\r
-        // --- initialize taxa, highlight matches ....\r
-        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());\r
-        @SuppressWarnings("rawtypes")\r
-        List<SearchResult<SpecimenOrObservationBase>> searchResults = searchResultBuilder.createResultSet(\r
-                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);\r
-\r
-        int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;\r
-\r
-        return new DefaultPagerImpl<SearchResult<SpecimenOrObservationBase>>(pageNumber, totalHits, pageSize,\r
-                searchResults);\r
-\r
-    }\r
-\r
-\r
-    /**\r
-     * @param clazz\r
-     * @param queryString\r
-     * @param languages\r
-     * @param highlightFragments\r
-     * @return\r
-     */\r
-    private LuceneSearch prepareByFullTextSearch(Class<? extends SpecimenOrObservationBase> clazz, String queryString, Rectangle bbox,\r
-            List<Language> languages, boolean highlightFragments) {\r
-\r
-        BooleanQuery finalQuery = new BooleanQuery();\r
-        BooleanQuery textQuery = new BooleanQuery();\r
-\r
-        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, FieldUnit.class);\r
-        QueryFactory queryFactory = luceneIndexToolProvider.newQueryFactoryFor(FieldUnit.class);\r
-\r
-        // --- criteria\r
-        luceneSearch.setCdmTypRestriction(clazz);\r
-        if(queryString != null){\r
-            textQuery.add(queryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);\r
-            finalQuery.add(textQuery, Occur.MUST);\r
-        }\r
-\r
-        // --- spacial query\r
-        if(bbox != null){\r
-            finalQuery.add(QueryFactory.buildSpatialQueryByRange(bbox, "gatheringEvent.exactLocation.point"), Occur.MUST);\r
-        }\r
-\r
-        luceneSearch.setQuery(finalQuery);\r
-\r
-        // --- sorting\r
-        SortField[] sortFields = new  SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};\r
-        luceneSearch.setSortFields(sortFields);\r
-\r
-        if(highlightFragments){\r
-            luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray());\r
-        }\r
-        return luceneSearch;\r
-    }\r
-\r
-\r
-    /* (non-Javadoc)\r
-     * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#getFieldUnits(eu.etaxonomy.cdm.model.occurrence.DerivedUnit)\r
-     */\r
-    @Override\r
-    public Collection<FieldUnit> getFieldUnits(UUID derivedUnitUuid) {\r
-        //It will search recursively over all {@link DerivationEvent}s and get the "originals" ({@link SpecimenOrObservationBase})\r
-        //from which this DerivedUnit was derived until all FieldUnits are found.\r
-\r
-        //FIXME: use HQL queries to increase performance\r
-        SpecimenOrObservationBase<?> specimen = load(derivedUnitUuid);\r
-//        specimen = HibernateProxyHelper.deproxy(specimen, SpecimenOrObservationBase.class);\r
-        Collection<FieldUnit> fieldUnits = new ArrayList<FieldUnit>();\r
-\r
-        if(specimen instanceof FieldUnit){\r
-            fieldUnits.add((FieldUnit) specimen);\r
-        }\r
-        else if(specimen instanceof DerivedUnit){\r
-            getFieldUnits((DerivedUnit) specimen, fieldUnits);\r
-        }\r
-        return fieldUnits;\r
-    }\r
-\r
-\r
-    /**\r
-     * @param original\r
-     * @param fieldUnits\r
-     */\r
-    private void getFieldUnits(DerivedUnit derivedUnit, Collection<FieldUnit> fieldUnits) {\r
-        Set<SpecimenOrObservationBase> originals = derivedUnit.getOriginals();\r
-        if(originals!=null && !originals.isEmpty()){\r
-            for(SpecimenOrObservationBase<?> original:originals){\r
-                if(original instanceof FieldUnit){\r
-                    fieldUnits.add((FieldUnit) original);\r
-                }\r
-                else if(original instanceof DerivedUnit){\r
-                    getFieldUnits((DerivedUnit) original, fieldUnits);\r
-                }\r
-            }\r
-        }\r
-    }\r
-\r
-    /* (non-Javadoc)\r
-     * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#moveSequence(eu.etaxonomy.cdm.model.molecular.DnaSample, eu.etaxonomy.cdm.model.molecular.DnaSample, eu.etaxonomy.cdm.model.molecular.Sequence)\r
-     */\r
-    @Override\r
-    public boolean moveSequence(DnaSample from, DnaSample to, Sequence sequence) {\r
-        //reload specimens to avoid session conflicts\r
-        from = (DnaSample) load(from.getUuid());\r
-        to = (DnaSample) load(to.getUuid());\r
-        sequence = sequenceService.load(sequence.getUuid());\r
-\r
-        if(from==null || to==null || sequence==null){\r
-            throw new TransientObjectException("One of the CDM entities has not been saved to the data base yet. Moving only works for persisted/saved CDM entities.\n" +\r
-                    "Operation was move "+sequence+ " from "+from+" to "+to);\r
-        }\r
-        from.removeSequence(sequence);\r
-        saveOrUpdate(from);\r
-        to.addSequence(sequence);\r
-        saveOrUpdate(to);\r
-        return true;\r
-    }\r
-\r
-    /* (non-Javadoc)\r
-     * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#moveDerivate(eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase, eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase, eu.etaxonomy.cdm.model.occurrence.DerivedUnit)\r
-     */\r
-    @Override\r
-    public boolean moveDerivate(SpecimenOrObservationBase<?> from, SpecimenOrObservationBase<?> to, DerivedUnit derivate) {\r
-        //reload specimens to avoid session conflicts\r
-        from = load(from.getUuid());\r
-        to = load(to.getUuid());\r
-        derivate = (DerivedUnit) load(derivate.getUuid());\r
-\r
-        if(from==null || to==null || derivate==null){\r
-            throw new TransientObjectException("One of the CDM entities has not been saved to the data base yet. Moving only works for persisted/saved CDM entities.\n" +\r
-                       "Operation was move "+derivate+ " from "+from+" to "+to);\r
-        }\r
-\r
-        SpecimenOrObservationType derivateType = derivate.getRecordBasis();\r
-        SpecimenOrObservationType toType = to.getRecordBasis();\r
-        //check if type is a sub derivate type\r
-        if(toType==SpecimenOrObservationType.FieldUnit //moving to FieldUnit always works\r
-                || derivateType==SpecimenOrObservationType.Media //moving media always works\r
-                || (derivateType.isKindOf(toType) && toType!=derivateType)){ //moving only to parent derivate type\r
-            //remove derivation event from parent specimen of dragged object\r
-            DerivationEvent eventToRemove = null;\r
-            for(DerivationEvent event:from.getDerivationEvents()){\r
-                if(event.getDerivatives().contains(derivate)){\r
-                    eventToRemove = event;\r
-                    break;\r
-                }\r
-            }\r
-            from.removeDerivationEvent(eventToRemove);\r
-            saveOrUpdate(from);\r
-            //add new derivation event to target\r
-            to.addDerivationEvent(DerivationEvent.NewSimpleInstance(to, derivate, eventToRemove==null?null:eventToRemove.getType()));\r
-            saveOrUpdate(to);\r
-            return true;\r
-        }\r
-        return false;\r
-    }\r
-\r
-    @Override\r
-    public Collection<ICdmBase> getNonCascadedAssociatedElements(SpecimenOrObservationBase<?> specimen){\r
-        //potential fields that are not persisted cascadingly\r
-        /*\r
-         * SOOB\r
-        -DescriptionBase\r
-        -determinations\r
-        --modifier TERM\r
-        -kindOfUnit TERM\r
-        -lifeStage TERM\r
-        -sex TERM\r
-\r
-        FieldUnit\r
-        -GatheringEvent\r
-        --Country TERM\r
-        --CollectingAreas TERM\r
-\r
-        DerivedUnit\r
-        -collection\r
-        --institute\r
-        ---types TERM\r
-        -preservationMethod\r
-        --medium TERM\r
-        -storedUnder CDM TaxonNameBase\r
-        */\r
-\r
-        Collection<ICdmBase> nonCascadedCdmEntities = new HashSet<ICdmBase>();\r
-\r
-        //Choose the correct entry point to traverse the graph (FieldUnit or DerivedUnit)\r
-\r
-        //FieldUnit\r
-        if(specimen instanceof FieldUnit){\r
-            nonCascadedCdmEntities.addAll(getFieldUnitNonCascadedAssociatedElements((FieldUnit)specimen));\r
-        }\r
-        //DerivedUnit\r
-        else if(specimen instanceof DerivedUnit){\r
-            DerivedUnit derivedUnit = (DerivedUnit)specimen;\r
-            if(derivedUnit.getDerivedFrom()!=null){\r
-                Collection<FieldUnit> fieldUnits = new ArrayList<FieldUnit>();\r
-                getFieldUnits(derivedUnit, fieldUnits);\r
-                for(FieldUnit fieldUnit:fieldUnits){\r
-                    nonCascadedCdmEntities.addAll(getFieldUnitNonCascadedAssociatedElements(fieldUnit));\r
-                }\r
-            }\r
-        }\r
-        return nonCascadedCdmEntities;\r
-    }\r
-\r
-    private Collection<ICdmBase> getFieldUnitNonCascadedAssociatedElements(FieldUnit fieldUnit){\r
-        //get non cascaded element on SpecimenOrObservationBase level\r
-        Collection<ICdmBase> nonCascadedCdmEntities = getSpecimenOrObservationNonCascadedAssociatedElements(fieldUnit);\r
-\r
-        //get FieldUnit specific elements\r
-        GatheringEvent gatheringEvent = fieldUnit.getGatheringEvent();\r
-        if(gatheringEvent!=null){\r
-            //country\r
-            if(gatheringEvent.getCountry()!=null){\r
-                nonCascadedCdmEntities.add(gatheringEvent.getCountry());\r
-            }\r
-            //collecting areas\r
-            for (NamedArea namedArea : gatheringEvent.getCollectingAreas()) {\r
-                nonCascadedCdmEntities.add(namedArea);\r
-            }\r
-        }\r
-        for (DerivationEvent derivationEvent : fieldUnit.getDerivationEvents()) {\r
-            for (DerivedUnit derivedUnit : derivationEvent.getDerivatives()) {\r
-                nonCascadedCdmEntities.addAll(getDerivedUnitNonCascadedAssociatedElements(derivedUnit));\r
-            }\r
-        }\r
-        return nonCascadedCdmEntities;\r
-    }\r
-\r
-    private Collection<ICdmBase> getDerivedUnitNonCascadedAssociatedElements(DerivedUnit derivedUnit){\r
-        //get non cascaded element on SpecimenOrObservationBase level\r
-        Collection<ICdmBase> nonCascadedCdmEntities = getSpecimenOrObservationNonCascadedAssociatedElements(derivedUnit);\r
-\r
-        //get DerivedUnit specific elements\r
-        if(derivedUnit.getCollection()!=null && derivedUnit.getCollection().getInstitute()!=null){\r
-            for (DefinedTerm type : derivedUnit.getCollection().getInstitute().getTypes()) {\r
-                nonCascadedCdmEntities.add(type);\r
-            }\r
-        }\r
-        if(derivedUnit.getPreservation()!=null && derivedUnit.getPreservation().getMedium()!=null){\r
-            nonCascadedCdmEntities.add(derivedUnit.getPreservation().getMedium());\r
-        }\r
-        if(derivedUnit.getStoredUnder()!=null){\r
-            nonCascadedCdmEntities.add(derivedUnit.getStoredUnder());\r
-        }\r
-        return nonCascadedCdmEntities;\r
-    }\r
-\r
-    /**\r
-     * @param specimen\r
-     * @return\r
-     */\r
-    private Collection<ICdmBase> getSpecimenOrObservationNonCascadedAssociatedElements(\r
-            SpecimenOrObservationBase<?> specimen) {\r
-        Collection<ICdmBase> nonCascadedCdmEntities = new HashSet<ICdmBase>();\r
-        //scan SpecimenOrObservationBase\r
-        for(DeterminationEvent determinationEvent:specimen.getDeterminations()){\r
-            //modifier\r
-            if(determinationEvent.getModifier()!=null){\r
-                nonCascadedCdmEntities.add(determinationEvent.getModifier());\r
-            }\r
-        }\r
-        //kindOfUnit\r
-        if(specimen.getKindOfUnit()!=null){\r
-            nonCascadedCdmEntities.add(specimen.getKindOfUnit());\r
-        }\r
-        //lifeStage\r
-        if(specimen.getLifeStage()!=null){\r
-            nonCascadedCdmEntities.add(specimen.getLifeStage());\r
-        }\r
-        //sex\r
-        if(specimen.getSex()!=null){\r
-            nonCascadedCdmEntities.add(specimen.getSex());\r
-        }\r
-        return nonCascadedCdmEntities;\r
-    }\r
-\r
-    /* (non-Javadoc)\r
-     * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#deleteDerivateHierarchy(eu.etaxonomy.cdm.model.common.ICdmBase)\r
-     */\r
-    @Override\r
-    public DeleteResult deleteDerivateHierarchy(ICdmBase from) {\r
-        DeleteResult deleteResult = new DeleteResult();\r
-        return deleteResult;\r
-    }\r
-\r
-}\r
+/**
+ * Copyright (C) 2007 EDIT
+ * European Distributed Institute of Taxonomy
+ * http://www.e-taxonomy.eu
+ *
+ * The contents of this file are subject to the Mozilla Public License Version 1.1
+ * See LICENSE.TXT at the top of this package for the full license terms.
+ */
+
+package eu.etaxonomy.cdm.api.service;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.UUID;
+
+import org.apache.log4j.Logger;
+import org.apache.lucene.queryparser.classic.ParseException;
+import org.apache.lucene.search.BooleanClause.Occur;
+import org.apache.lucene.search.BooleanQuery.Builder;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.grouping.TopGroups;
+import org.apache.lucene.util.BytesRef;
+import org.hibernate.TransientObjectException;
+import org.hibernate.search.spatial.impl.Rectangle;
+import org.joda.time.Partial;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataRetrievalFailureException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import eu.etaxonomy.cdm.api.facade.DerivedUnitFacade;
+import eu.etaxonomy.cdm.api.facade.DerivedUnitFacadeConfigurator;
+import eu.etaxonomy.cdm.api.facade.DerivedUnitFacadeNotSupportedException;
+import eu.etaxonomy.cdm.api.service.UpdateResult.Status;
+import eu.etaxonomy.cdm.api.service.config.DeleteConfiguratorBase;
+import eu.etaxonomy.cdm.api.service.config.FindOccurrencesConfigurator;
+import eu.etaxonomy.cdm.api.service.config.FindOccurrencesConfigurator.AssignmentStatus;
+import eu.etaxonomy.cdm.api.service.config.IIdentifiableEntityServiceConfigurator;
+import eu.etaxonomy.cdm.api.service.config.SpecimenDeleteConfigurator;
+import eu.etaxonomy.cdm.api.service.dto.DerivateDTO;
+import eu.etaxonomy.cdm.api.service.dto.DerivateDataDTO;
+import eu.etaxonomy.cdm.api.service.dto.DerivateDataDTO.ContigFile;
+import eu.etaxonomy.cdm.api.service.dto.DerivateDataDTO.Link;
+import eu.etaxonomy.cdm.api.service.dto.DerivateDataDTO.MolecularData;
+import eu.etaxonomy.cdm.api.service.dto.FieldUnitDTO;
+import eu.etaxonomy.cdm.api.service.dto.PreservedSpecimenDTO;
+import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;
+import eu.etaxonomy.cdm.api.service.molecular.ISequenceService;
+import eu.etaxonomy.cdm.api.service.pager.Pager;
+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;
+import eu.etaxonomy.cdm.api.service.search.LuceneParseException;
+import eu.etaxonomy.cdm.api.service.search.LuceneSearch;
+import eu.etaxonomy.cdm.api.service.search.QueryFactory;
+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.CdmUtils;
+import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
+import eu.etaxonomy.cdm.format.CdmFormatterFactory;
+import eu.etaxonomy.cdm.format.ICdmFormatter.FormatKey;
+import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
+import eu.etaxonomy.cdm.model.CdmBaseType;
+import eu.etaxonomy.cdm.model.agent.AgentBase;
+import eu.etaxonomy.cdm.model.common.CdmBase;
+import eu.etaxonomy.cdm.model.common.DefinedTerm;
+import eu.etaxonomy.cdm.model.common.DefinedTermBase;
+import eu.etaxonomy.cdm.model.common.ICdmBase;
+import eu.etaxonomy.cdm.model.common.Language;
+import eu.etaxonomy.cdm.model.description.CategoricalData;
+import eu.etaxonomy.cdm.model.description.DescriptionBase;
+import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
+import eu.etaxonomy.cdm.model.description.IndividualsAssociation;
+import eu.etaxonomy.cdm.model.description.QuantitativeData;
+import eu.etaxonomy.cdm.model.description.SpecimenDescription;
+import eu.etaxonomy.cdm.model.description.TaxonDescription;
+import eu.etaxonomy.cdm.model.location.Country;
+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.MediaRepresentationPart;
+import eu.etaxonomy.cdm.model.media.MediaUtils;
+import eu.etaxonomy.cdm.model.molecular.AmplificationResult;
+import eu.etaxonomy.cdm.model.molecular.DnaSample;
+import eu.etaxonomy.cdm.model.molecular.Sequence;
+import eu.etaxonomy.cdm.model.molecular.SingleRead;
+import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
+import eu.etaxonomy.cdm.model.name.TaxonName;
+import eu.etaxonomy.cdm.model.name.TypeDesignationStatusBase;
+import eu.etaxonomy.cdm.model.occurrence.DerivationEvent;
+import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
+import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
+import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
+import eu.etaxonomy.cdm.model.occurrence.GatheringEvent;
+import eu.etaxonomy.cdm.model.occurrence.MediaSpecimen;
+import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
+import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;
+import eu.etaxonomy.cdm.model.taxon.Taxon;
+import eu.etaxonomy.cdm.model.taxon.TaxonBase;
+import eu.etaxonomy.cdm.persistence.dao.common.IDefinedTermDao;
+import eu.etaxonomy.cdm.persistence.dao.initializer.AbstractBeanInitializer;
+import eu.etaxonomy.cdm.persistence.dao.molecular.ISingleReadDao;
+import eu.etaxonomy.cdm.persistence.dao.occurrence.IOccurrenceDao;
+import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
+import eu.etaxonomy.cdm.persistence.query.OrderHint;
+import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
+import eu.etaxonomy.cdm.strategy.cache.common.IdentifiableEntityDefaultCacheStrategy;
+
+/**
+ * @author a.babadshanjan
+ * @created 01.09.2008
+ */
+@Service
+@Transactional(readOnly = true)
+public class OccurrenceServiceImpl extends IdentifiableServiceBase<SpecimenOrObservationBase, IOccurrenceDao> implements IOccurrenceService {
+
+    static private final Logger logger = Logger.getLogger(OccurrenceServiceImpl.class);
+
+    @Autowired
+    private IDefinedTermDao definedTermDao;
+
+    @Autowired
+    private IDescriptionService descriptionService;
+
+    @Autowired
+    private INameService nameService;
+
+    @Autowired
+    private IEventBaseService eventService;
+
+    @Autowired
+    private ITaxonService taxonService;
+
+    @Autowired
+    private ISequenceService sequenceService;
+
+    @Autowired
+    private ISingleReadDao singleReadDao;
+
+    @Autowired
+    private AbstractBeanInitializer beanInitializer;
+
+    @Autowired
+    private ILuceneIndexToolProvider luceneIndexToolProvider;
+
+    private static final String SEPARATOR_STRING = ", ";
+
+    public OccurrenceServiceImpl() {
+        logger.debug("Load OccurrenceService Bean");
+    }
+
+
+    @Override
+    @Transactional(readOnly = false)
+    public void updateTitleCache(Class<? extends SpecimenOrObservationBase> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<SpecimenOrObservationBase> cacheStrategy, IProgressMonitor monitor) {
+        if (clazz == null) {
+            clazz = SpecimenOrObservationBase.class;
+        }
+        super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);
+    }
+
+    /**
+     * FIXME Candidate for harmonization
+     * move to termService
+     */
+    @Override
+    public Country getCountryByIso(String iso639) {
+        return this.definedTermDao.getCountryByIso(iso639);
+
+    }
+
+    /**
+     * FIXME Candidate for harmonization
+     * move to termService
+     */
+    @Override
+    public List<Country> getCountryByName(String name) {
+        List<? extends DefinedTermBase> terms = this.definedTermDao.findByTitle(Country.class, name, null, null, null, null, null, null);
+        List<Country> countries = new ArrayList<>();
+        for (int i = 0; i < terms.size(); i++) {
+            countries.add((Country) terms.get(i));
+        }
+        return countries;
+    }
+
+    @Override
+    @Autowired
+    protected void setDao(IOccurrenceDao dao) {
+        this.dao = dao;
+    }
+
+    @Override
+    public Pager<DerivationEvent> getDerivationEvents(SpecimenOrObservationBase occurence, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
+        Integer numberOfResults = dao.countDerivationEvents(occurence);
+
+        List<DerivationEvent> results = new ArrayList<>();
+        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
+            results = dao.getDerivationEvents(occurence, pageSize, pageNumber, propertyPaths);
+        }
+
+        return new DefaultPagerImpl<DerivationEvent>(pageNumber, numberOfResults, pageSize, results);
+    }
+
+    @Override
+    public int countDeterminations(SpecimenOrObservationBase occurence, TaxonBase taxonbase) {
+        return dao.countDeterminations(occurence, taxonbase);
+    }
+
+    @Override
+    public Pager<DeterminationEvent> getDeterminations(SpecimenOrObservationBase occurrence, TaxonBase taxonBase, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
+        Integer numberOfResults = dao.countDeterminations(occurrence, taxonBase);
+
+        List<DeterminationEvent> results = new ArrayList<>();
+        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
+            results = dao.getDeterminations(occurrence, taxonBase, pageSize, pageNumber, propertyPaths);
+        }
+
+        return new DefaultPagerImpl<DeterminationEvent>(pageNumber, numberOfResults, pageSize, results);
+    }
+
+    @Override
+    public Pager<Media> getMedia(SpecimenOrObservationBase occurence, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
+        Integer numberOfResults = dao.countMedia(occurence);
+
+        List<Media> results = new ArrayList<>();
+        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
+            results = dao.getMedia(occurence, pageSize, pageNumber, propertyPaths);
+        }
+
+        return new DefaultPagerImpl<Media>(pageNumber, numberOfResults, pageSize, results);
+    }
+
+    @Override
+    public Pager<Media> getMediainHierarchy(SpecimenOrObservationBase rootOccurence, Integer pageSize,
+            Integer pageNumber, List<String> propertyPaths) {
+        List<Media> media = new ArrayList<>();
+        //media specimens
+        if(rootOccurence.isInstanceOf(MediaSpecimen.class)){
+            MediaSpecimen mediaSpecimen = HibernateProxyHelper.deproxy(rootOccurence, MediaSpecimen.class);
+            media.add(mediaSpecimen.getMediaSpecimen());
+        }
+        // pherograms & gelPhotos
+        if (rootOccurence.isInstanceOf(DnaSample.class)) {
+            DnaSample dnaSample = CdmBase.deproxy(rootOccurence, DnaSample.class);
+            Set<Sequence> sequences = dnaSample.getSequences();
+            //we do show only those gelPhotos which lead to a consensus sequence
+            for (Sequence sequence : sequences) {
+                Set<Media> dnaRelatedMedia = new HashSet<>();
+                for (SingleRead singleRead : sequence.getSingleReads()){
+                    AmplificationResult amplification = singleRead.getAmplificationResult();
+                    dnaRelatedMedia.add(amplification.getGelPhoto());
+                    dnaRelatedMedia.add(singleRead.getPherogram());
+                    dnaRelatedMedia.remove(null);
+                }
+                media.addAll(dnaRelatedMedia);
+            }
+        }
+        if(rootOccurence.isInstanceOf(DerivedUnit.class)){
+            DerivedUnit derivedUnit = HibernateProxyHelper.deproxy(rootOccurence, DerivedUnit.class);
+            for (DerivationEvent derivationEvent : derivedUnit.getDerivationEvents()) {
+                for (DerivedUnit childDerivative : derivationEvent.getDerivatives()) {
+                    media.addAll(getMediainHierarchy(childDerivative, pageSize, pageNumber, propertyPaths).getRecords());
+                }
+            }
+        }
+        return new DefaultPagerImpl<Media>(pageNumber, media.size(), pageSize, media);
+    }
+
+    @Override
+    public Pager<SpecimenOrObservationBase> list(Class<? extends SpecimenOrObservationBase> type, TaxonName determinedAs, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
+        Integer numberOfResults = dao.count(type, determinedAs);
+        List<SpecimenOrObservationBase> results = new ArrayList<>();
+        pageNumber = pageNumber == null ? 0 : pageNumber;
+        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
+            Integer start = pageSize == null ? 0 : pageSize * pageNumber;
+            results = dao.list(type, determinedAs, pageSize, start, orderHints, propertyPaths);
+        }
+        return new DefaultPagerImpl<SpecimenOrObservationBase>(pageNumber, numberOfResults, pageSize, results);
+    }
+
+    @Override
+    public Pager<SpecimenOrObservationBase> list(Class<? extends SpecimenOrObservationBase> type, TaxonBase determinedAs, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
+        Integer numberOfResults = dao.count(type, determinedAs);
+        List<SpecimenOrObservationBase> results = new ArrayList<>();
+        pageNumber = pageNumber == null ? 0 : pageNumber;
+        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
+            Integer start = pageSize == null ? 0 : pageSize * pageNumber;
+            results = dao.list(type, determinedAs, pageSize, start, orderHints, propertyPaths);
+        }
+        return new DefaultPagerImpl<SpecimenOrObservationBase>(pageNumber, numberOfResults, pageSize, results);
+    }
+
+    @Override
+    public List<UuidAndTitleCache<DerivedUnit>> getDerivedUnitUuidAndTitleCache(Integer limit, String pattern) {
+        return dao.getDerivedUnitUuidAndTitleCache(limit, pattern);
+    }
+
+    @Override
+    public List<UuidAndTitleCache<FieldUnit>> getFieldUnitUuidAndTitleCache() {
+        return dao.getFieldUnitUuidAndTitleCache();
+    }
+
+    @Override
+    public DerivedUnitFacade getDerivedUnitFacade(DerivedUnit derivedUnit, List<String> propertyPaths) throws DerivedUnitFacadeNotSupportedException {
+        derivedUnit = (DerivedUnit) dao.load(derivedUnit.getUuid(), null);
+        DerivedUnitFacadeConfigurator config = DerivedUnitFacadeConfigurator.NewInstance();
+        config.setThrowExceptionForNonSpecimenPreservationMethodRequest(false);
+        DerivedUnitFacade derivedUnitFacade = DerivedUnitFacade.NewInstance(derivedUnit, config);
+        beanInitializer.initialize(derivedUnitFacade, propertyPaths);
+        return derivedUnitFacade;
+    }
+
+    @Override
+    public List<DerivedUnitFacade> listDerivedUnitFacades(
+            DescriptionBase description, List<String> propertyPaths) {
+
+        List<DerivedUnitFacade> derivedUnitFacadeList = new ArrayList<>();
+        IndividualsAssociation tempIndividualsAssociation;
+        SpecimenOrObservationBase tempSpecimenOrObservationBase;
+        List<IndividualsAssociation> elements = descriptionService.listDescriptionElements(description, null, IndividualsAssociation.class, null, 0, Arrays.asList(new String []{"associatedSpecimenOrObservation"}));
+        for (IndividualsAssociation element : elements) {
+            tempIndividualsAssociation = HibernateProxyHelper.deproxy(element, IndividualsAssociation.class);
+            if (tempIndividualsAssociation.getAssociatedSpecimenOrObservation() != null) {
+                tempSpecimenOrObservationBase = HibernateProxyHelper.deproxy(tempIndividualsAssociation.getAssociatedSpecimenOrObservation(), SpecimenOrObservationBase.class);
+                if (tempSpecimenOrObservationBase.isInstanceOf(DerivedUnit.class)) {
+                    try {
+                        derivedUnitFacadeList.add(DerivedUnitFacade.NewInstance(HibernateProxyHelper.deproxy(tempSpecimenOrObservationBase, DerivedUnit.class)));
+                    } catch (DerivedUnitFacadeNotSupportedException e) {
+                        logger.warn(tempIndividualsAssociation.getAssociatedSpecimenOrObservation().getTitleCache() + " : " + e.getMessage());
+                    }
+                }
+            }
+        }
+
+        beanInitializer.initializeAll(derivedUnitFacadeList, propertyPaths);
+
+        return derivedUnitFacadeList;
+    }
+
+
+    @Override
+    public <T extends SpecimenOrObservationBase> List<T> listByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,
+            Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
+
+        return pageByAssociatedTaxon(type, includeRelationships, associatedTaxon, maxDepth, pageSize, pageNumber, orderHints, propertyPaths).getRecords();
+    }
+
+    @Override
+    public Collection<SpecimenOrObservationBase> listFieldUnitsByAssociatedTaxon(Taxon associatedTaxon, List<OrderHint> orderHints, List<String> propertyPaths) {
+        return pageFieldUnitsByAssociatedTaxon(null, associatedTaxon, null, null, null, null, propertyPaths).getRecords();
+    }
+
+    @Override
+    public Pager<SpecimenOrObservationBase> pageFieldUnitsByAssociatedTaxon(Set<TaxonRelationshipEdge> includeRelationships,
+            Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
+            List<String> propertyPaths) {
+
+        if (!getSession().contains(associatedTaxon)) {
+            associatedTaxon = (Taxon) taxonService.load(associatedTaxon.getUuid());
+        }
+
+        // gather the IDs of all relevant field units
+        Set<UUID> fieldUnitUuids = new HashSet<>();
+        List<SpecimenOrObservationBase> records = listByAssociatedTaxon(null, includeRelationships, associatedTaxon, maxDepth, null, null, orderHints, propertyPaths);
+        for (SpecimenOrObservationBase<?> specimen : records) {
+            for (FieldUnit fieldUnit : getFieldUnits(specimen.getUuid())) {
+                fieldUnitUuids.add(fieldUnit.getUuid());
+            }
+        }
+        //dao.list() does the paging of the field units. Passing the field units directly to the Pager would not work
+        List<SpecimenOrObservationBase> fieldUnits = dao.list(fieldUnitUuids, pageSize, pageNumber, orderHints, propertyPaths);
+        return new DefaultPagerImpl<SpecimenOrObservationBase>(pageNumber, fieldUnitUuids.size(), pageSize, fieldUnits);
+    }
+
+    @Override
+    public FieldUnitDTO assembleFieldUnitDTO(FieldUnit fieldUnit, UUID associatedTaxonUuid) {
+
+        if (!getSession().contains(fieldUnit)) {
+            fieldUnit = (FieldUnit) load(fieldUnit.getUuid());
+        }
+        TaxonBase associatedTaxon = taxonService.load(associatedTaxonUuid);
+
+        FieldUnitDTO fieldUnitDTO = new FieldUnitDTO();
+
+        if (fieldUnit.getGatheringEvent() != null) {
+            GatheringEvent gatheringEvent = fieldUnit.getGatheringEvent();
+            // Country
+            NamedArea country = gatheringEvent.getCountry();
+            fieldUnitDTO.setCountry(country != null ? country.getLabel() : null);
+            // Collection
+            AgentBase collector = gatheringEvent.getCollector();
+            String fieldNumber = fieldUnit.getFieldNumber();
+            String collectionString = "";
+            if (collector != null || fieldNumber != null) {
+                collectionString += collector != null ? collector : "";
+                if (!collectionString.isEmpty()) {
+                    collectionString += " ";
+                }
+                collectionString += (fieldNumber != null ? fieldNumber : "");
+                collectionString.trim();
+            }
+            fieldUnitDTO.setCollection(collectionString);
+            // Date
+            Partial gatheringDate = gatheringEvent.getGatheringDate();
+            String dateString = null;
+            if (gatheringDate != null) {
+                dateString = gatheringDate.toString();
+            }
+            else if(gatheringEvent.getTimeperiod()!=null && gatheringEvent.getTimeperiod().getFreeText()!=null){
+                dateString = gatheringEvent.getTimeperiod().getFreeText();
+            }
+            fieldUnitDTO.setDate(dateString);
+        }
+
+        // Taxon Name
+        fieldUnitDTO.setTaxonName(associatedTaxon.getName().getTitleCache());
+
+        // Herbaria map
+        Map<eu.etaxonomy.cdm.model.occurrence.Collection, Integer> collectionToCountMap = new HashMap<>();
+        // List of accession numbers for citation
+        List<String> preservedSpecimenAccessionNumbers = new ArrayList<>();
+
+        // assemble preserved specimen DTOs
+        Set<DerivationEvent> derivationEvents = fieldUnit.getDerivationEvents();
+        for (DerivationEvent derivationEvent : derivationEvents) {
+            Set<DerivedUnit> derivatives = derivationEvent.getDerivatives();
+            for (DerivedUnit derivedUnit : derivatives) {
+                if(!derivedUnit.isPublish()){
+                    continue;
+                }
+                // collect accession numbers for citation
+                String mostSignificantIdentifier = getMostSignificantIdentifier(derivedUnit);
+                if (mostSignificantIdentifier != null) {
+                    preservedSpecimenAccessionNumbers.add(mostSignificantIdentifier);
+                }
+                // collect collections for herbaria column
+                if (derivedUnit.getCollection() != null) {
+                    Integer herbariumCount = collectionToCountMap.get(derivedUnit.getCollection());
+                    if (herbariumCount == null) {
+                        herbariumCount = 0;
+                    }
+                    collectionToCountMap.put(derivedUnit.getCollection(), herbariumCount + 1);
+                }
+                if (derivedUnit.getRecordBasis().equals(SpecimenOrObservationType.PreservedSpecimen)) {
+                    PreservedSpecimenDTO preservedSpecimenDTO = assemblePreservedSpecimenDTO(derivedUnit, fieldUnitDTO);
+                    fieldUnitDTO.addPreservedSpecimenDTO(preservedSpecimenDTO);
+                    fieldUnitDTO.setHasCharacterData(fieldUnitDTO.isHasCharacterData() || preservedSpecimenDTO.isHasCharacterData());
+                    fieldUnitDTO.setHasDetailImage(fieldUnitDTO.isHasDetailImage() || preservedSpecimenDTO.isHasDetailImage());
+                    fieldUnitDTO.setHasDna(fieldUnitDTO.isHasDna() || preservedSpecimenDTO.isHasDna());
+                    fieldUnitDTO.setHasSpecimenScan(fieldUnitDTO.isHasSpecimenScan() || preservedSpecimenDTO.isHasSpecimenScan());
+                }
+            }
+        }
+        // assemble derivate data DTO
+        assembleDerivateDataDTO(fieldUnitDTO, fieldUnit);
+
+        // assemble citation
+        String citation = fieldUnit.getTitleCache();
+        if((CdmUtils.isBlank(citation) || citation.equals(IdentifiableEntityDefaultCacheStrategy.TITLE_CACHE_GENERATION_NOT_IMPLEMENTED))
+                && !fieldUnit.isProtectedTitleCache()){
+            fieldUnit.setTitleCache(null);
+            citation = fieldUnit.getTitleCache();
+        }
+        if (!preservedSpecimenAccessionNumbers.isEmpty()) {
+            citation += " (";
+            for (String accessionNumber : preservedSpecimenAccessionNumbers) {
+                if (!accessionNumber.isEmpty()) {
+                    citation += accessionNumber + SEPARATOR_STRING;
+                }
+            }
+            citation = removeTail(citation, SEPARATOR_STRING);
+            citation += ")";
+        }
+        fieldUnitDTO.setCitation(citation);
+
+        // assemble herbaria string
+        String herbariaString = "";
+        for (Entry<eu.etaxonomy.cdm.model.occurrence.Collection, Integer> e : collectionToCountMap.entrySet()) {
+            eu.etaxonomy.cdm.model.occurrence.Collection collection = e.getKey();
+            if (collection.getCode() != null) {
+                herbariaString += collection.getCode();
+            }
+            if (e.getValue() > 1) {
+                herbariaString += "(" + e.getValue() + ")";
+            }
+            herbariaString += SEPARATOR_STRING;
+        }
+        herbariaString = removeTail(herbariaString, SEPARATOR_STRING);
+        fieldUnitDTO.setHerbarium(herbariaString);
+
+        return fieldUnitDTO;
+    }
+
+    @Override
+    public PreservedSpecimenDTO assemblePreservedSpecimenDTO(DerivedUnit derivedUnit) {
+        return assemblePreservedSpecimenDTO(derivedUnit, null);
+    }
+
+    @Override
+    public String getMostSignificantIdentifier(DerivedUnit derivedUnit) {
+        if (derivedUnit.getAccessionNumber() != null && !derivedUnit.getAccessionNumber().isEmpty()) {
+            return derivedUnit.getAccessionNumber();
+        }
+        else if(derivedUnit.getBarcode()!=null && !derivedUnit.getBarcode().isEmpty()){
+            return derivedUnit.getBarcode();
+        }
+        else if(derivedUnit.getCatalogNumber()!=null && !derivedUnit.getCatalogNumber().isEmpty()){
+            return derivedUnit.getCatalogNumber();
+        }
+        return null;
+    }
+
+    public PreservedSpecimenDTO assemblePreservedSpecimenDTO(DerivedUnit derivedUnit, FieldUnitDTO fieldUnitDTO) {
+        if (!getSession().contains(derivedUnit)) {
+            derivedUnit = (DerivedUnit) load(derivedUnit.getUuid());
+        }
+        PreservedSpecimenDTO preservedSpecimenDTO = new PreservedSpecimenDTO();
+
+        //specimen identifier
+        FormatKey collectionKey = FormatKey.COLLECTION_CODE;
+        String specimenIdentifier = CdmFormatterFactory.format(derivedUnit, collectionKey);
+        if (CdmUtils.isBlank(specimenIdentifier)) {
+            collectionKey = FormatKey.COLLECTION_NAME;
+        }
+        specimenIdentifier = CdmFormatterFactory.format(derivedUnit, new FormatKey[] {
+                collectionKey, FormatKey.SPACE,
+                FormatKey.MOST_SIGNIFICANT_IDENTIFIER, FormatKey.SPACE });
+        if(CdmUtils.isBlank(specimenIdentifier)){
+            specimenIdentifier = derivedUnit.getUuid().toString();
+        }
+        preservedSpecimenDTO.setAccessionNumber(specimenIdentifier);
+        preservedSpecimenDTO.setUuid(derivedUnit.getUuid().toString());
+
+        //preferred stable URI
+        preservedSpecimenDTO.setPreferredStableUri(derivedUnit.getPreferredStableUri());
+
+        // citation
+        Collection<FieldUnit> fieldUnits = getFieldUnits(derivedUnit);
+        if (fieldUnits.size() == 1) {
+            preservedSpecimenDTO.setCitation(fieldUnits.iterator().next().getTitleCache());
+        }
+        else{
+            preservedSpecimenDTO.setCitation("No Citation available. This specimen either has no or multiple field units.");
+        }
+
+        // character state data
+        Collection<DescriptionElementBase> characterDataForSpecimen = getCharacterDataForSpecimen(derivedUnit);
+        if (!characterDataForSpecimen.isEmpty()) {
+            if (fieldUnitDTO != null) {
+                fieldUnitDTO.setHasCharacterData(true);
+            }
+        }
+        for (DescriptionElementBase descriptionElementBase : characterDataForSpecimen) {
+            String character = descriptionElementBase.getFeature().getLabel();
+            ArrayList<Language> languages = new ArrayList<>(Collections.singleton(Language.DEFAULT()));
+            if (descriptionElementBase instanceof QuantitativeData) {
+                QuantitativeData quantitativeData = (QuantitativeData) descriptionElementBase;
+                DefaultQuantitativeDescriptionBuilder builder = new DefaultQuantitativeDescriptionBuilder();
+                String state = builder.build(quantitativeData, languages).getText(Language.DEFAULT());
+                preservedSpecimenDTO.addCharacterData(character, state);
+            }
+            else if(descriptionElementBase instanceof CategoricalData){
+                CategoricalData categoricalData = (CategoricalData) descriptionElementBase;
+                DefaultCategoricalDescriptionBuilder builder = new DefaultCategoricalDescriptionBuilder();
+                String state = builder.build(categoricalData, languages).getText(Language.DEFAULT());
+                preservedSpecimenDTO.addCharacterData(character, state);
+            }
+        }
+        // check type designations
+        Collection<SpecimenTypeDesignation> specimenTypeDesignations = listTypeDesignations(derivedUnit, null, null, null, null);
+        for (SpecimenTypeDesignation specimenTypeDesignation : specimenTypeDesignations) {
+            if (fieldUnitDTO != null) {
+                fieldUnitDTO.setHasType(true);
+            }
+            TypeDesignationStatusBase<?> typeStatus = specimenTypeDesignation.getTypeStatus();
+            if (typeStatus != null) {
+                List<String> typedTaxaNames = new ArrayList<>();
+                String label = typeStatus.getLabel();
+                Set<TaxonName> typifiedNames = specimenTypeDesignation.getTypifiedNames();
+                for (TaxonName taxonName : typifiedNames) {
+                    typedTaxaNames.add(taxonName.getFullTitleCache());
+                }
+                preservedSpecimenDTO.addTypes(label, typedTaxaNames);
+            }
+        }
+
+        // individuals associations
+        Collection<IndividualsAssociation> individualsAssociations = listIndividualsAssociations(derivedUnit, null, null, null, null);
+        for (IndividualsAssociation individualsAssociation : individualsAssociations) {
+            if (individualsAssociation.getInDescription() != null) {
+                if (individualsAssociation.getInDescription().isInstanceOf(TaxonDescription.class)) {
+                    TaxonDescription taxonDescription = HibernateProxyHelper.deproxy(individualsAssociation.getInDescription(), TaxonDescription.class);
+                    Taxon taxon = taxonDescription.getTaxon();
+                    if (taxon != null) {
+                        preservedSpecimenDTO.addAssociatedTaxon(taxon);
+                    }
+                }
+            }
+        }
+        // assemble sub derivates
+        preservedSpecimenDTO.setDerivateDataDTO(assembleDerivateDataDTO(preservedSpecimenDTO, derivedUnit));
+        return preservedSpecimenDTO;
+    }
+
+    private DerivateDataDTO assembleDerivateDataDTO(DerivateDTO derivateDTO, SpecimenOrObservationBase<?> specimenOrObservation) {
+        DerivateDataDTO derivateDataDTO = new DerivateDataDTO();
+        Collection<DerivedUnit> childDerivates = getDerivedUnitsFor(specimenOrObservation);
+        for (DerivedUnit childDerivate : childDerivates) {
+            // assemble molecular data
+            //pattern: DNAMarker [contig1, primer1_1, primer1_2, ...][contig2, primer2_1, ...]...
+            if (childDerivate.isInstanceOf(DnaSample.class)) {
+                if (childDerivate.getRecordBasis() == SpecimenOrObservationType.TissueSample) {
+                    // TODO implement TissueSample assembly for web service
+                }
+                if (childDerivate.getRecordBasis() == SpecimenOrObservationType.DnaSample) {
+
+                    DnaSample dna = HibernateProxyHelper.deproxy(childDerivate, DnaSample.class);
+                    if (!dna.getSequences().isEmpty()) {
+                        derivateDTO.setHasDna(true);
+                    }
+                    for (Sequence sequence : dna.getSequences()) {
+                        URI boldUri = null;
+                        try {
+                            boldUri = sequence.getBoldUri();
+                        } catch (URISyntaxException e1) {
+                            logger.error("Could not create BOLD URI", e1);
+                        }
+                        final DefinedTerm dnaMarker = sequence.getDnaMarker();
+                        Link providerLink = null;
+                        if(boldUri!=null && dnaMarker!=null){
+                               providerLink = new DerivateDataDTO.Link(boldUri, dnaMarker.getLabel());
+                        }
+                        MolecularData molecularData = derivateDataDTO.addProviderLink(providerLink);
+
+                        //contig file
+                        ContigFile contigFile = null;
+                        if (sequence.getContigFile() != null) {
+                            MediaRepresentationPart contigMediaRepresentationPart = MediaUtils.getFirstMediaRepresentationPart(sequence.getContigFile());
+                            if (contigMediaRepresentationPart != null) {
+                                contigFile = molecularData.addContigFile(new Link(contigMediaRepresentationPart.getUri(), "contig"));
+                            }
+                        }
+                        else{
+                               contigFile = molecularData.addContigFile(null);
+                        }
+                        // primer files
+                        if (sequence.getSingleReads() != null) {
+                            int readCount = 1;
+                            for (SingleRead singleRead : sequence.getSingleReads()) {
+                                MediaRepresentationPart pherogramMediaRepresentationPart = MediaUtils.getFirstMediaRepresentationPart(singleRead.getPherogram());
+                                if (pherogramMediaRepresentationPart != null) {
+                                    contigFile.addPrimerLink(pherogramMediaRepresentationPart.getUri(), "read"+readCount++);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            // assemble media data
+            else if (childDerivate.isInstanceOf(MediaSpecimen.class)) {
+                MediaSpecimen media = HibernateProxyHelper.deproxy(childDerivate, MediaSpecimen.class);
+
+                URI mediaUri = getMediaUri(media);
+                if (media.getKindOfUnit() != null) {
+                    // specimen scan
+                    if (media.getKindOfUnit().getUuid().equals(DefinedTerm.uuidSpecimenScan)) {
+                        derivateDataDTO.addSpecimenScanUuid(media.getMediaSpecimen().getUuid());
+                        derivateDTO.setHasSpecimenScan(true);
+                        String imageLinkText = "scan";
+                        if (derivateDTO instanceof PreservedSpecimenDTO && ((PreservedSpecimenDTO) derivateDTO).getAccessionNumber() != null) {
+                            imageLinkText = ((PreservedSpecimenDTO) derivateDTO).getAccessionNumber();
+                        }
+                        derivateDataDTO.addSpecimenScan(mediaUri, imageLinkText);
+                    }
+                    // detail image
+                    else if (media.getKindOfUnit().getUuid().equals(DefinedTerm.uuidDetailImage)) {
+                        derivateDataDTO.addDetailImageUuid(media.getMediaSpecimen().getUuid());
+                        derivateDTO.setHasDetailImage(true);
+                        String motif = "detail image";
+                        if (media.getMediaSpecimen()!=null){
+                               if(CdmUtils.isNotBlank(media.getMediaSpecimen().getTitleCache())) {
+                                       motif = media.getMediaSpecimen().getTitleCache();
+                               }
+                        }
+                        derivateDataDTO.addDetailImage(mediaUri, motif);
+                    }
+                }
+            }
+        }
+        return derivateDataDTO;
+    }
+
+    private String removeTail(String string, final String tail) {
+        if (string.endsWith(tail)) {
+            string = string.substring(0, string.length() - tail.length());
+        }
+        return string;
+    }
+
+    private URI getMediaUri(MediaSpecimen mediaSpecimen) {
+        URI mediaUri = null;
+        Collection<MediaRepresentation> mediaRepresentations = mediaSpecimen.getMediaSpecimen().getRepresentations();
+        if (mediaRepresentations != null && !mediaRepresentations.isEmpty()) {
+            Collection<MediaRepresentationPart> mediaRepresentationParts = mediaRepresentations.iterator().next().getParts();
+            if (mediaRepresentationParts != null && !mediaRepresentationParts.isEmpty()) {
+                MediaRepresentationPart part = mediaRepresentationParts.iterator().next();
+                if (part.getUri() != null) {
+                    mediaUri = part.getUri();
+                }
+            }
+        }
+        return mediaUri;
+    }
+
+    private Collection<DerivedUnit> getDerivedUnitsFor(SpecimenOrObservationBase<?> specimen) {
+        Collection<DerivedUnit> derivedUnits = new ArrayList<>();
+        for (DerivationEvent derivationEvent : specimen.getDerivationEvents()) {
+            for (DerivedUnit derivative : derivationEvent.getDerivatives()) {
+                derivedUnits.add(derivative);
+                derivedUnits.addAll(getDerivedUnitsFor(derivative));
+            }
+        }
+        return derivedUnits;
+    }
+
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T extends SpecimenOrObservationBase> Pager<T> pageByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,
+            Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
+
+        Set<Taxon> taxa = new HashSet<>();
+        Set<Integer> occurrenceIds = new HashSet<>();
+        List<T> occurrences = new ArrayList<>();
+
+        // Integer limit = PagerUtils.limitFor(pageSize);
+        // Integer start = PagerUtils.startFor(pageSize, pageNumber);
+
+        if (!getSession().contains(associatedTaxon)) {
+            associatedTaxon = (Taxon) taxonService.load(associatedTaxon.getUuid());
+        }
+
+        if (includeRelationships != null) {
+            taxa = taxonService.listRelatedTaxa(associatedTaxon, includeRelationships, maxDepth, null, null, propertyPaths);
+        }
+
+        taxa.add(associatedTaxon);
+
+        for (Taxon taxon : taxa) {
+            List<T> perTaxonOccurrences = dao.listByAssociatedTaxon(type, taxon, null, null, orderHints, propertyPaths);
+            for (SpecimenOrObservationBase o : perTaxonOccurrences) {
+                occurrenceIds.add(o.getId());
+            }
+        }
+        occurrences = (List<T>) dao.loadList(occurrenceIds, propertyPaths);
+
+        return new DefaultPagerImpl<T>(pageNumber, occurrenceIds.size(), pageSize, occurrences);
+
+    }
+
+    @Override
+    public <T extends SpecimenOrObservationBase> Pager<T> pageByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,
+            String taxonUUID, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
+
+        UUID uuid = UUID.fromString(taxonUUID);
+        Taxon tax = (Taxon) taxonService.load(uuid);
+        // TODO REMOVE NULL STATEMENT
+        type = null;
+        return pageByAssociatedTaxon(type, includeRelationships, tax, maxDepth, pageSize, pageNumber, orderHints, propertyPaths);
+
+    }
+
+    @Override
+    public Pager<SearchResult<SpecimenOrObservationBase>> findByFullText(
+            Class<? extends SpecimenOrObservationBase> clazz, String queryString, Rectangle boundingBox, List<Language> languages,
+            boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
+            List<String> propertyPaths) throws IOException, LuceneParseException {
+
+        LuceneSearch luceneSearch = prepareByFullTextSearch(clazz, queryString, boundingBox, languages, highlightFragments);
+
+        // --- execute search
+        TopGroups<BytesRef> topDocsResultSet;
+        try {
+            topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
+        } catch (ParseException e) {
+            LuceneParseException parseException = new LuceneParseException(e.getMessage());
+            parseException.setStackTrace(e.getStackTrace());
+            throw parseException;
+        }
+
+        Map<CdmBaseType, String> idFieldMap = new HashMap<>();
+        idFieldMap.put(CdmBaseType.SPECIMEN_OR_OBSERVATIONBASE, "id");
+
+        // --- initialize taxa, highlight matches ....
+        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
+        @SuppressWarnings("rawtypes")
+        List<SearchResult<SpecimenOrObservationBase>> searchResults = searchResultBuilder.createResultSet(
+                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
+
+        int totalHits = topDocsResultSet != null ? topDocsResultSet.totalGroupCount : 0;
+
+        return new DefaultPagerImpl<SearchResult<SpecimenOrObservationBase>>(pageNumber, totalHits, pageSize,
+                searchResults);
+
+    }
+
+    private LuceneSearch prepareByFullTextSearch(Class<? extends SpecimenOrObservationBase> clazz, String queryString, Rectangle bbox,
+            List<Language> languages, boolean highlightFragments) {
+
+        Builder finalQueryBuilder = new Builder();
+        Builder textQueryBuilder = new Builder();
+
+        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, FieldUnit.class);
+        QueryFactory queryFactory = luceneIndexToolProvider.newQueryFactoryFor(FieldUnit.class);
+
+        // --- criteria
+        luceneSearch.setCdmTypRestriction(clazz);
+        if (queryString != null) {
+            textQueryBuilder.add(queryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
+            finalQueryBuilder.add(textQueryBuilder.build(), Occur.MUST);
+        }
+
+        // --- spacial query
+        if (bbox != null) {
+            finalQueryBuilder.add(QueryFactory.buildSpatialQueryByRange(bbox, "gatheringEvent.exactLocation.point"), Occur.MUST);
+        }
+
+        luceneSearch.setQuery(finalQueryBuilder.build());
+
+        // --- sorting
+        SortField[] sortFields = new SortField[] { SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.Type.STRING, false) };
+        luceneSearch.setSortFields(sortFields);
+
+        if (highlightFragments) {
+            luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray());
+        }
+        return luceneSearch;
+    }
+
+
+    @Override
+    public Collection<FieldUnit> getFieldUnits(UUID derivedUnitUuid) {
+        //It will search recursively over all {@link DerivationEvent}s and get the "originals" ({@link SpecimenOrObservationBase})
+        //from which this DerivedUnit was derived until all FieldUnits are found.
+
+        // FIXME: use HQL queries to increase performance
+        SpecimenOrObservationBase<?> specimen = load(derivedUnitUuid);
+//        specimen = HibernateProxyHelper.deproxy(specimen, SpecimenOrObservationBase.class);
+        Collection<FieldUnit> fieldUnits = new ArrayList<>();
+
+        if (specimen.isInstanceOf(FieldUnit.class)) {
+            fieldUnits.add(HibernateProxyHelper.deproxy(specimen, FieldUnit.class));
+        }
+        else if(specimen.isInstanceOf(DerivedUnit.class)){
+            fieldUnits.addAll(getFieldUnits(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class)));
+        }
+        return fieldUnits;
+    }
+
+    private Collection<FieldUnit> getFieldUnits(DerivedUnit derivedUnit) {
+        Collection<FieldUnit> fieldUnits = new HashSet<>();
+        Set<SpecimenOrObservationBase> originals = derivedUnit.getOriginals();
+        if (originals != null && !originals.isEmpty()) {
+            for (SpecimenOrObservationBase<?> original : originals) {
+                if (original.isInstanceOf(FieldUnit.class)) {
+                    fieldUnits.add(HibernateProxyHelper.deproxy(original, FieldUnit.class));
+                }
+                else if(original.isInstanceOf(DerivedUnit.class)){
+                    fieldUnits.addAll(getFieldUnits(HibernateProxyHelper.deproxy(original, DerivedUnit.class)));
+                }
+            }
+        }
+        return fieldUnits;
+    }
+
+    @Override
+    @Transactional(readOnly = false)
+    public UpdateResult moveSequence(DnaSample from, DnaSample to, Sequence sequence) {
+        return moveSequence(from.getUuid(), to.getUuid(), sequence.getUuid());
+    }
+
+    @Override
+    @Transactional(readOnly = false)
+    public UpdateResult moveSequence(UUID fromUuid, UUID toUuid, UUID sequenceUuid) {
+        // reload specimens to avoid session conflicts
+        DnaSample from = (DnaSample) load(fromUuid);
+        DnaSample to = (DnaSample) load(toUuid);
+        Sequence sequence = sequenceService.load(sequenceUuid);
+
+        if (from == null || to == null || sequence == null) {
+            throw new TransientObjectException("One of the CDM entities has not been saved to the data base yet. Moving only works for persisted/saved CDM entities.\n" +
+                    "Operation was move "+sequence+ " from "+from+" to "+to);
+        }
+        UpdateResult result = new UpdateResult();
+        from.removeSequence(sequence);
+        saveOrUpdate(from);
+        to.addSequence(sequence);
+        saveOrUpdate(to);
+        result.setStatus(Status.OK);
+        result.addUpdatedObject(from);
+        result.addUpdatedObject(to);
+        return result;
+    }
+
+    @Override
+    @Transactional(readOnly = false)
+    public boolean moveDerivate(SpecimenOrObservationBase<?> from, SpecimenOrObservationBase<?> to, DerivedUnit derivate) {
+        return moveDerivate(from!=null?from.getUuid():null, to.getUuid(), derivate.getUuid()).isOk();
+    }
+
+    @Override
+    @Transactional(readOnly = false)
+    public UpdateResult moveDerivate(UUID specimenFromUuid, UUID specimenToUuid, UUID derivateUuid) {
+        // reload specimens to avoid session conflicts
+        SpecimenOrObservationBase<?> from = null;
+        if(specimenFromUuid!=null){
+            from = load(specimenFromUuid);
+        }
+        SpecimenOrObservationBase<?> to = load(specimenToUuid);
+        DerivedUnit derivate = (DerivedUnit) load(derivateUuid);
+
+        if ((specimenFromUuid!=null && from == null) || to == null || derivate == null) {
+            throw new TransientObjectException("One of the CDM entities has not been saved to the data base yet. Moving only works for persisted/saved CDM entities.\n" +
+                    "Operation was move "+derivate+ " from "+from+" to "+to);
+        }
+        UpdateResult result = new UpdateResult();
+        SpecimenOrObservationType derivateType = derivate.getRecordBasis();
+        SpecimenOrObservationType toType = to.getRecordBasis();
+        // check if type is a sub derivate type
+        if(toType==SpecimenOrObservationType.FieldUnit //moving to FieldUnit always works
+                || derivateType==SpecimenOrObservationType.Media //moving media always works
+                || (derivateType.isKindOf(toType) && toType!=derivateType)){ //moving only to parent derivate type
+            if(from!=null){
+                // remove derivation event from parent specimen of dragged object
+                DerivationEvent eventToRemove = null;
+                for (DerivationEvent event : from.getDerivationEvents()) {
+                    if (event.getDerivatives().contains(derivate)) {
+                        eventToRemove = event;
+                        break;
+                    }
+                }
+                from.removeDerivationEvent(eventToRemove);
+                if(eventToRemove!=null){
+                    // add new derivation event to target and copy the event parameters of the old one
+                    DerivationEvent derivedFromNewOriginalEvent = DerivationEvent.NewSimpleInstance(to, derivate, null);
+                    derivedFromNewOriginalEvent.setActor(eventToRemove.getActor());
+                    derivedFromNewOriginalEvent.setDescription(eventToRemove.getDescription());
+                    derivedFromNewOriginalEvent.setInstitution(eventToRemove.getInstitution());
+                    derivedFromNewOriginalEvent.setTimeperiod(eventToRemove.getTimeperiod());
+                    derivedFromNewOriginalEvent.setType(eventToRemove.getType());
+                    to.addDerivationEvent(derivedFromNewOriginalEvent);
+                    derivate.setDerivedFrom(derivedFromNewOriginalEvent);
+                }
+            }
+            else{
+                //derivative had no parent before so we use empty derivation event
+                DerivationEvent derivedFromNewOriginalEvent = DerivationEvent.NewSimpleInstance(to, derivate, null);
+                to.addDerivationEvent(derivedFromNewOriginalEvent);
+                derivate.setDerivedFrom(derivedFromNewOriginalEvent);
+            }
+
+            if(from!=null){
+                saveOrUpdate(from);
+            }
+            saveOrUpdate(to);
+            result.setStatus(Status.OK);
+            result.addUpdatedObject(from);
+            result.addUpdatedObject(to);
+        } else {
+            result.setStatus(Status.ERROR);
+        }
+        return result;
+    }
+
+    @Override
+    public Collection<ICdmBase> getNonCascadedAssociatedElements(SpecimenOrObservationBase<?> specimen) {
+        // potential fields that are not persisted cascadingly
+        /*
+         * SOOB
+        -DescriptionBase
+        -determinations
+        --modifier TERM
+        -kindOfUnit TERM
+        -lifeStage TERM
+        -sex TERM
+
+        FieldUnit
+        -GatheringEvent
+        --Country TERM
+        --CollectingAreas TERM
+
+        DerivedUnit
+        -collection
+        --institute
+        ---types TERM
+        -preservationMethod
+        --medium TERM
+        -storedUnder CDM TaxonName
+         */
+
+        Collection<ICdmBase> nonCascadedCdmEntities = new HashSet<>();
+
+        //Choose the correct entry point to traverse the graph (FieldUnit or DerivedUnit)
+
+        // FieldUnit
+        if (specimen.isInstanceOf(FieldUnit.class)) {
+            nonCascadedCdmEntities.addAll(getFieldUnitNonCascadedAssociatedElements(HibernateProxyHelper.deproxy(specimen, FieldUnit.class)));
+        }
+        // DerivedUnit
+        else if (specimen.isInstanceOf(DerivedUnit.class)) {
+            DerivedUnit derivedUnit = HibernateProxyHelper.deproxy(specimen, DerivedUnit.class);
+            if (derivedUnit.getDerivedFrom() != null) {
+                Collection<FieldUnit> fieldUnits = getFieldUnits(derivedUnit);
+                for (FieldUnit fieldUnit : fieldUnits) {
+                    nonCascadedCdmEntities.addAll(getFieldUnitNonCascadedAssociatedElements(fieldUnit));
+                }
+            }
+        }
+        return nonCascadedCdmEntities;
+    }
+
+    private Collection<ICdmBase> getFieldUnitNonCascadedAssociatedElements(FieldUnit fieldUnit) {
+        // get non cascaded element on SpecimenOrObservationBase level
+        Collection<ICdmBase> nonCascadedCdmEntities = getSpecimenOrObservationNonCascadedAssociatedElements(fieldUnit);
+
+        // get FieldUnit specific elements
+        GatheringEvent gatheringEvent = fieldUnit.getGatheringEvent();
+        if (gatheringEvent != null) {
+            // country
+            if (gatheringEvent.getCountry() != null) {
+                nonCascadedCdmEntities.add(gatheringEvent.getCountry());
+            }
+            // collecting areas
+            for (NamedArea namedArea : gatheringEvent.getCollectingAreas()) {
+                nonCascadedCdmEntities.add(namedArea);
+            }
+        }
+        for (DerivationEvent derivationEvent : fieldUnit.getDerivationEvents()) {
+            for (DerivedUnit derivedUnit : derivationEvent.getDerivatives()) {
+                nonCascadedCdmEntities.addAll(getDerivedUnitNonCascadedAssociatedElements(derivedUnit));
+            }
+        }
+        return nonCascadedCdmEntities;
+    }
+
+    private Collection<ICdmBase> getDerivedUnitNonCascadedAssociatedElements(DerivedUnit derivedUnit) {
+        // get non cascaded element on SpecimenOrObservationBase level
+        Collection<ICdmBase> nonCascadedCdmEntities = getSpecimenOrObservationNonCascadedAssociatedElements(derivedUnit);
+
+        // get DerivedUnit specific elements
+        if (derivedUnit.getCollection() != null && derivedUnit.getCollection().getInstitute() != null) {
+            for (DefinedTerm type : derivedUnit.getCollection().getInstitute().getTypes()) {
+                nonCascadedCdmEntities.add(type);
+            }
+        }
+        if (derivedUnit.getPreservation() != null && derivedUnit.getPreservation().getMedium() != null) {
+            nonCascadedCdmEntities.add(derivedUnit.getPreservation().getMedium());
+        }
+        if (derivedUnit.getStoredUnder() != null) {
+            nonCascadedCdmEntities.add(derivedUnit.getStoredUnder());
+        }
+        return nonCascadedCdmEntities;
+    }
+
+    private Collection<ICdmBase> getSpecimenOrObservationNonCascadedAssociatedElements(
+            SpecimenOrObservationBase<?> specimen) {
+        Collection<ICdmBase> nonCascadedCdmEntities = new HashSet<>();
+        // scan SpecimenOrObservationBase
+        for (DeterminationEvent determinationEvent : specimen.getDeterminations()) {
+            // modifier
+            if (determinationEvent.getModifier() != null) {
+                nonCascadedCdmEntities.add(determinationEvent.getModifier());
+            }
+        }
+        // kindOfUnit
+        if (specimen.getKindOfUnit() != null) {
+            nonCascadedCdmEntities.add(specimen.getKindOfUnit());
+        }
+        // lifeStage
+        if (specimen.getLifeStage() != null) {
+            nonCascadedCdmEntities.add(specimen.getLifeStage());
+        }
+        // sex
+        if (specimen.getSex() != null) {
+            nonCascadedCdmEntities.add(specimen.getSex());
+        }
+        return nonCascadedCdmEntities;
+    }
+
+    @Override
+    public DeleteResult isDeletable(UUID specimenUuid, DeleteConfiguratorBase config) {
+        DeleteResult deleteResult = new DeleteResult();
+        SpecimenOrObservationBase specimen = this.load(specimenUuid);
+        SpecimenDeleteConfigurator specimenDeleteConfigurator = (SpecimenDeleteConfigurator) config;
+
+        // check elements found by super method
+        Set<CdmBase> relatedObjects = super.isDeletable(specimenUuid, config).getRelatedObjects();
+        for (CdmBase cdmBase : relatedObjects) {
+            // check for type designation
+            if (cdmBase.isInstanceOf(SpecimenTypeDesignation.class) && !specimenDeleteConfigurator.isDeleteFromTypeDesignation()) {
+                deleteResult.setAbort();
+                deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation is a type specimen."));
+                deleteResult.addRelatedObject(cdmBase);
+                break;
+            }
+            // check for IndividualsAssociations
+            else if (cdmBase.isInstanceOf(IndividualsAssociation.class) && !specimenDeleteConfigurator.isDeleteFromIndividualsAssociation()) {
+                deleteResult.setAbort();
+                deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation is still associated via IndividualsAssociations"));
+                deleteResult.addRelatedObject(cdmBase);
+                break;
+            }
+            // check for taxon description
+            else if(cdmBase.isInstanceOf(TaxonDescription.class)
+                    && HibernateProxyHelper.deproxy(cdmBase, TaxonDescription.class).getDescribedSpecimenOrObservation().equals(specimen)
+                    && !specimenDeleteConfigurator.isDeleteFromDescription()){
+                deleteResult.setAbort();
+                deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation is still used as \"Described Specimen\" in a taxon description."));
+                deleteResult.addRelatedObject(cdmBase);
+                break;
+            }
+            // check for children and parents (derivation events)
+            else if (cdmBase.isInstanceOf(DerivationEvent.class)) {
+                DerivationEvent derivationEvent = HibernateProxyHelper.deproxy(cdmBase, DerivationEvent.class);
+                // check if derivation event is empty
+                if (!derivationEvent.getDerivatives().isEmpty() && derivationEvent.getOriginals().contains(specimen)) {
+                    // if derivationEvent is the childEvent and contains derivations
+//                    if (derivationEvent.getDerivatives().contains(specimen)) {
+//                        //if it is the parent event the specimen is still deletable
+//                        continue;
+//                    }
+                    if(!specimenDeleteConfigurator.isDeleteChildren()){
+                        //if children should not be deleted then it is undeletable
+                        deleteResult.setAbort();
+                        deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation still has child derivatives."));
+                        deleteResult.addRelatedObject(cdmBase);
+                        break;
+                    }
+                    else{
+                        // check all children if they can be deleted
+                        Set<DerivedUnit> derivatives = derivationEvent.getDerivatives();
+                        DeleteResult childResult = new DeleteResult();
+                        for (DerivedUnit derivedUnit : derivatives) {
+                            childResult.includeResult(isDeletable(derivedUnit.getUuid(), specimenDeleteConfigurator));
+                        }
+                        if (!childResult.isOk()) {
+                            deleteResult.setAbort();
+                            deleteResult.includeResult(childResult);
+                            deleteResult.addRelatedObject(cdmBase);
+                            break;
+                        }
+                    }
+                }
+            }
+            // check for amplification
+            else if (cdmBase.isInstanceOf(AmplificationResult.class)
+                    && !specimenDeleteConfigurator.isDeleteMolecularData()
+                    && !specimenDeleteConfigurator.isDeleteChildren()) {
+                deleteResult.setAbort();
+                deleteResult.addException(new ReferencedObjectUndeletableException("DnaSample is used in amplification results."));
+                deleteResult.addRelatedObject(cdmBase);
+                break;
+            }
+            // check for sequence
+            else if (cdmBase.isInstanceOf(Sequence.class)
+                    && !specimenDeleteConfigurator.isDeleteMolecularData()
+                    && !specimenDeleteConfigurator.isDeleteChildren()) {
+                deleteResult.setAbort();
+                deleteResult.addException(new ReferencedObjectUndeletableException("DnaSample is used in sequences."));
+                deleteResult.addRelatedObject(cdmBase);
+                break;
+            }
+        }
+        if (deleteResult.isOk()) {
+            //add all related object if deletion is OK so they can be handled by the delete() method
+            deleteResult.addRelatedObjects(relatedObjects);
+        }
+        return deleteResult;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Transactional(readOnly = false)
+    @Override
+    public DeleteResult delete(UUID specimenUuid, SpecimenDeleteConfigurator config) {
+        return delete(load(specimenUuid), config);
+    }
+
+
+    @Transactional(readOnly = false)
+    @Override
+    public DeleteResult delete(SpecimenOrObservationBase<?> specimen, SpecimenDeleteConfigurator config) {
+        specimen = HibernateProxyHelper.deproxy(specimen, SpecimenOrObservationBase.class);
+
+        DeleteResult deleteResult = isDeletable(specimen.getUuid(), config);
+        if (!deleteResult.isOk()) {
+            return deleteResult;
+        }
+
+        if (config.isDeleteChildren()) {
+            Set<DerivationEvent> derivationEvents = specimen.getDerivationEvents();
+            //clone to avoid concurrent modification
+            //can happen if the child is deleted and deleted its own derivedFrom event
+            Set<DerivationEvent> derivationEventsClone = new HashSet<>(derivationEvents);
+            for (DerivationEvent derivationEvent : derivationEventsClone) {
+                Set<DerivedUnit> derivatives = derivationEvent.getDerivatives();
+                Iterator<DerivedUnit> it = derivatives.iterator();
+                Set<DerivedUnit> derivativesToDelete = new HashSet<>();
+                while (it.hasNext()) {
+                    DerivedUnit unit = it.next();
+                    derivativesToDelete.add(unit);
+                }
+                for (DerivedUnit unit:derivativesToDelete){
+                    deleteResult.includeResult(delete(unit, config));
+                }
+            }
+        }
+
+
+
+
+        // check related objects
+        Set<CdmBase> relatedObjects = deleteResult.getRelatedObjects();
+
+        for (CdmBase relatedObject : relatedObjects) {
+            // check for TypeDesignations
+            if (relatedObject.isInstanceOf(SpecimenTypeDesignation.class)) {
+                SpecimenTypeDesignation designation = HibernateProxyHelper.deproxy(relatedObject, SpecimenTypeDesignation.class);
+                designation.setTypeSpecimen(null);
+                List<TaxonName> typifiedNames = new ArrayList<>();
+                typifiedNames.addAll(designation.getTypifiedNames());
+                for (TaxonName taxonName : typifiedNames) {
+                    taxonName.removeTypeDesignation(designation);
+                }
+            }
+            // delete IndividualsAssociation
+            if (relatedObject.isInstanceOf(IndividualsAssociation.class)) {
+                IndividualsAssociation association = HibernateProxyHelper.deproxy(relatedObject, IndividualsAssociation.class);
+                association.setAssociatedSpecimenOrObservation(null);
+                association.getInDescription().removeElement(association);
+            }
+            // check for "described specimen" (deprecated)
+            if (relatedObject.isInstanceOf(TaxonDescription.class)) {
+                TaxonDescription description = HibernateProxyHelper.deproxy(relatedObject, TaxonDescription.class);
+                description.setDescribedSpecimenOrObservation(null);
+            }
+            // check for specimen description
+            if (relatedObject.isInstanceOf(SpecimenDescription.class)) {
+                SpecimenDescription specimenDescription = HibernateProxyHelper.deproxy(relatedObject, SpecimenDescription.class);
+                specimenDescription.setDescribedSpecimenOrObservation(null);
+                // check if description is a description of the given specimen
+                if (specimen.getDescriptions().contains(specimenDescription)) {
+                    specimen.removeDescription(specimenDescription);
+                }
+                DeleteResult descriptionDelete = descriptionService.isDeletable(specimenDescription.getUuid(), null);
+                if (descriptionDelete.isOk()){
+                    descriptionService.delete(specimenDescription);
+                }
+            }
+            // check for amplification
+            if (relatedObject.isInstanceOf(AmplificationResult.class)) {
+                AmplificationResult amplificationResult = HibernateProxyHelper.deproxy(relatedObject, AmplificationResult.class);
+                amplificationResult.getDnaSample().removeAmplificationResult(amplificationResult);
+            }
+            // check for sequence
+            if (relatedObject.isInstanceOf(Sequence.class)) {
+                Sequence sequence = HibernateProxyHelper.deproxy(relatedObject, Sequence.class);
+                sequence.getDnaSample().removeSequence(sequence);
+            }
+            // check for children and parents (derivation events)
+            if (relatedObject.isInstanceOf(DerivationEvent.class)) {
+                DerivationEvent derivationEvent = HibernateProxyHelper.deproxy(relatedObject, DerivationEvent.class);
+                // parent derivation event (derivedFrom)
+                if (derivationEvent.getDerivatives().contains(specimen) && specimen.isInstanceOf(DerivedUnit.class)) {
+                    derivationEvent.removeDerivative(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class));
+                    if (derivationEvent.getDerivatives().isEmpty()) {
+                        Set<SpecimenOrObservationBase> originals = derivationEvent.getOriginals();
+                        for (SpecimenOrObservationBase specimenOrObservationBase : originals) {
+                            specimenOrObservationBase.removeDerivationEvent(derivationEvent);
+                            deleteResult.addUpdatedObject(specimenOrObservationBase);
+                        }
+                        // if derivationEvent has no derivates anymore, delete it
+                        eventService.delete(derivationEvent);
+                    }
+                }
+                else{
+                    //child derivation events should not occur since we delete the hierarchy from bottom to top
+                }
+            }
+        }
+        if (specimen instanceof FieldUnit){
+            FieldUnit fieldUnit = HibernateProxyHelper.deproxy(specimen, FieldUnit.class);
+            GatheringEvent event = fieldUnit.getGatheringEvent();
+            fieldUnit.setGatheringEvent(null);
+            if (event != null){
+                DeleteResult result = eventService.isDeletable(event.getUuid(), null);
+                if (result.isOk()){
+                    eventService.delete(event);
+                }
+            }
+
+        }
+        deleteResult.includeResult(delete(specimen));
+
+        return deleteResult;
+    }
+
+    @Override
+    public Collection<IndividualsAssociation> listIndividualsAssociations(SpecimenOrObservationBase<?> specimen, Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
+        return dao.listIndividualsAssociations(specimen, null, null, null, null);
+    }
+
+    @Override
+    public Collection<TaxonBase<?>> listAssociatedTaxa(SpecimenOrObservationBase<?> specimen, Integer limit, Integer start,
+            List<OrderHint> orderHints, List<String> propertyPaths) {
+        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
+
+        //individuals associations
+        associatedTaxa.addAll(listIndividualsAssociationTaxa(specimen, limit, start, orderHints, propertyPaths));
+        //type designation
+        if(specimen.isInstanceOf(DerivedUnit.class)){
+            associatedTaxa.addAll(listTypeDesignationTaxa(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class), limit, start, orderHints, propertyPaths));
+        }
+        //determinations
+        associatedTaxa.addAll(listDeterminedTaxa(specimen, limit, start, orderHints, propertyPaths));
+
+        return associatedTaxa;
+    }
+
+
+    @Override
+    public Collection<TaxonBase<?>> listDeterminedTaxa(SpecimenOrObservationBase<?> specimen, Integer limit, Integer start,
+            List<OrderHint> orderHints, List<String> propertyPaths) {
+        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
+        for (DeterminationEvent determinationEvent : listDeterminationEvents(specimen, limit, start, orderHints, propertyPaths)) {
+            if(determinationEvent.getIdentifiedUnit().equals(specimen)){
+                if(determinationEvent.getTaxon()!=null){
+                    associatedTaxa.add(determinationEvent.getTaxon());
+                }
+                if(determinationEvent.getTaxonName()!=null){
+                    associatedTaxa.addAll((Collection)determinationEvent.getTaxonName().getTaxonBases());
+                }
+            }
+        }
+        return associatedTaxa;
+    }
+
+    @Override
+    public Collection<TaxonBase<?>> listTypeDesignationTaxa(DerivedUnit specimen, Integer limit, Integer start,
+            List<OrderHint> orderHints, List<String> propertyPaths) {
+        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
+        for (SpecimenTypeDesignation typeDesignation : listTypeDesignations(specimen, limit, start, orderHints, propertyPaths)) {
+            if(typeDesignation.getTypeSpecimen().equals(specimen)){
+                Set<TaxonName> typifiedNames = typeDesignation.getTypifiedNames();
+                for (TaxonName taxonName : typifiedNames) {
+                    associatedTaxa.addAll(taxonName.getTaxa());
+                }
+            }
+        }
+        return associatedTaxa;
+    }
+
+    @Override
+    public Collection<TaxonBase<?>> listIndividualsAssociationTaxa(SpecimenOrObservationBase<?> specimen, Integer limit, Integer start,
+            List<OrderHint> orderHints, List<String> propertyPaths) {
+        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
+        for (IndividualsAssociation individualsAssociation : listIndividualsAssociations(specimen, limit, start, orderHints, propertyPaths)) {
+            if(individualsAssociation.getInDescription().isInstanceOf(TaxonDescription.class)){
+                TaxonDescription taxonDescription = HibernateProxyHelper.deproxy(individualsAssociation.getInDescription(), TaxonDescription.class);
+                if(taxonDescription.getTaxon()!=null){
+                    associatedTaxa.add(taxonDescription.getTaxon());
+                }
+            }
+        }
+        return associatedTaxa;
+    }
+
+    @Override
+    public Collection<DeterminationEvent> listDeterminationEvents(SpecimenOrObservationBase<?> specimen,
+            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
+        return dao.listDeterminationEvents(specimen, limit, start, orderHints, propertyPaths);
+    }
+
+    @Override
+    public Map<DerivedUnit, Collection<SpecimenTypeDesignation>> listTypeDesignations(
+            Collection<DerivedUnit> specimens, Integer limit, Integer start,
+            List<OrderHint> orderHints, List<String> propertyPaths) {
+        Map<DerivedUnit, Collection<SpecimenTypeDesignation>> typeDesignationMap = new HashMap<>();
+        for (DerivedUnit specimen : specimens) {
+            Collection<SpecimenTypeDesignation> typeDesignations = listTypeDesignations(specimen, limit, start, orderHints, propertyPaths);
+            typeDesignationMap.put(specimen, typeDesignations);
+        }
+        return typeDesignationMap;
+    }
+
+    @Override
+    public Collection<SpecimenTypeDesignation> listTypeDesignations(DerivedUnit specimen,
+            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
+        return dao.listTypeDesignations(specimen, limit, start, orderHints, propertyPaths);
+    }
+
+    @Override
+    public Collection<DescriptionBase<?>> listDescriptionsWithDescriptionSpecimen(
+            SpecimenOrObservationBase<?> specimen, Integer limit, Integer start, List<OrderHint> orderHints,
+            List<String> propertyPaths) {
+        return dao.listDescriptionsWithDescriptionSpecimen(specimen, limit, start, orderHints, propertyPaths);
+    }
+
+    @Override
+    @Deprecated //this is not a service layer task so it may be removed in future versions
+    public Collection<DescriptionElementBase> getCharacterDataForSpecimen(SpecimenOrObservationBase<?> specimen) {
+        if (specimen != null) {
+            return specimen.characterData();
+        }else{
+            return new ArrayList<>();
+        }
+    }
+
+    @Override
+    public Collection<DescriptionElementBase> getCharacterDataForSpecimen(UUID specimenUuid) {
+        SpecimenOrObservationBase<?> specimen = load(specimenUuid);
+        if (specimen != null) {
+            return getCharacterDataForSpecimen(specimen);
+        }
+        else{
+            throw new DataRetrievalFailureException("Specimen with the given uuid not found in the data base");
+        }
+    }
+
+
+    @Override
+    public Integer countByTitle(IIdentifiableEntityServiceConfigurator<SpecimenOrObservationBase> config){
+        if (config instanceof FindOccurrencesConfigurator) {
+            FindOccurrencesConfigurator occurrenceConfig = (FindOccurrencesConfigurator) config;
+            Taxon taxon = null;
+            if(occurrenceConfig.getAssociatedTaxonUuid()!=null){
+                TaxonBase taxonBase = taxonService.load(occurrenceConfig.getAssociatedTaxonUuid());
+                if(taxonBase.isInstanceOf(Taxon.class)){
+                    taxon = HibernateProxyHelper.deproxy(taxonBase, Taxon.class);
+                }
+            }
+            TaxonName taxonName = null;
+            if(occurrenceConfig.getAssociatedTaxonNameUuid()!=null){
+                taxonName = nameService.load(occurrenceConfig.getAssociatedTaxonNameUuid());
+            }
+            return dao.countOccurrences(occurrenceConfig.getClazz(),
+                    occurrenceConfig.getTitleSearchString(), occurrenceConfig.getSignificantIdentifier(),
+                    occurrenceConfig.getSpecimenType(), taxon, taxonName, occurrenceConfig.getMatchMode(), null, null,
+                    occurrenceConfig.getOrderHints(), occurrenceConfig.getPropertyPaths());
+        }
+        else{
+            return dao.countByTitle(config.getTitleSearchString());
+        }
+
+    }
+
+    @Override
+    public Pager<SpecimenOrObservationBase> findByTitle(
+            IIdentifiableEntityServiceConfigurator<SpecimenOrObservationBase> config) {
+        if (config instanceof FindOccurrencesConfigurator) {
+            FindOccurrencesConfigurator occurrenceConfig = (FindOccurrencesConfigurator) config;
+            List<SpecimenOrObservationBase> occurrences = new ArrayList<>();
+            Taxon taxon = null;
+            if(occurrenceConfig.getAssociatedTaxonUuid()!=null){
+                TaxonBase taxonBase = taxonService.load(occurrenceConfig.getAssociatedTaxonUuid());
+                if(taxonBase.isInstanceOf(Taxon.class)){
+                    taxon = HibernateProxyHelper.deproxy(taxonBase, Taxon.class);
+                }
+            }
+            TaxonName taxonName = null;
+            if(occurrenceConfig.getAssociatedTaxonNameUuid()!=null){
+                taxonName = nameService.load(occurrenceConfig.getAssociatedTaxonNameUuid());
+            }
+            occurrences.addAll(dao.findOccurrences(occurrenceConfig.getClazz(),
+                    occurrenceConfig.getTitleSearchString(), occurrenceConfig.getSignificantIdentifier(),
+                    occurrenceConfig.getSpecimenType(), taxon, taxonName, occurrenceConfig.getMatchMode(), null, null,
+                    occurrenceConfig.getOrderHints(), occurrenceConfig.getPropertyPaths()));
+            //filter out (un-)assigned specimens
+            if(taxon==null && taxonName==null){
+                AssignmentStatus assignmentStatus = occurrenceConfig.getAssignmentStatus();
+                List<SpecimenOrObservationBase<?>> specimenWithAssociations = new ArrayList<>();
+                if(!assignmentStatus.equals(AssignmentStatus.ALL_SPECIMENS)){
+                    for (SpecimenOrObservationBase specimenOrObservationBase : occurrences) {
+                        Collection<TaxonBase<?>> associatedTaxa = listAssociatedTaxa(specimenOrObservationBase, null, null, null, null);
+                        if(!associatedTaxa.isEmpty()){
+                            specimenWithAssociations.add(specimenOrObservationBase);
+                        }
+                    }
+                }
+                if(assignmentStatus.equals(AssignmentStatus.UNASSIGNED_SPECIMENS)){
+                    occurrences.removeAll(specimenWithAssociations);
+                }
+                if(assignmentStatus.equals(AssignmentStatus.ASSIGNED_SPECIMENS)){
+                    occurrences = new ArrayList<>(specimenWithAssociations);
+                }
+            }
+            // indirectly associated specimens
+            if(occurrenceConfig.isRetrieveIndirectlyAssociatedSpecimens()){
+                List<SpecimenOrObservationBase> indirectlyAssociatedOccurrences = new ArrayList<>(occurrences);
+                for (SpecimenOrObservationBase specimen : occurrences) {
+                    List<SpecimenOrObservationBase<?>> allHierarchyDerivates = getAllHierarchyDerivatives(specimen);
+                    for (SpecimenOrObservationBase<?> specimenOrObservationBase : allHierarchyDerivates) {
+                        if(!occurrences.contains(specimenOrObservationBase)){
+                            indirectlyAssociatedOccurrences.add(specimenOrObservationBase);
+                        }
+                    }
+                }
+                occurrences = indirectlyAssociatedOccurrences;
+            }
+
+            return new DefaultPagerImpl<SpecimenOrObservationBase>(config.getPageNumber(), occurrences.size(), config.getPageSize(), occurrences);
+        }
+        return super.findByTitle(config);
+    }
+
+    @Override
+    public List<SpecimenOrObservationBase<?>> getAllHierarchyDerivatives(SpecimenOrObservationBase<?> specimen){
+        List<SpecimenOrObservationBase<?>> allHierarchyDerivatives = new ArrayList<>();
+        Collection<FieldUnit> fieldUnits = getFieldUnits(specimen.getUuid());
+        if(fieldUnits.isEmpty()){
+            allHierarchyDerivatives.add(specimen);
+            allHierarchyDerivatives.addAll(getAllChildDerivatives(specimen));
+        }
+        else{
+            for (FieldUnit fieldUnit : fieldUnits) {
+                allHierarchyDerivatives.add(fieldUnit);
+                allHierarchyDerivatives.addAll(getAllChildDerivatives(fieldUnit));
+            }
+        }
+        return allHierarchyDerivatives;
+    }
+
+    @Override
+    public List<DerivedUnit> getAllChildDerivatives(UUID specimenUuid){
+        return getAllChildDerivatives(load(specimenUuid));
+    }
+
+    @Override
+    public List<DerivedUnit> getAllChildDerivatives(SpecimenOrObservationBase<?> specimen){
+        if (specimen == null){
+            return null;
+        }
+        List<DerivedUnit> childDerivate = new ArrayList<>();
+        Set<DerivationEvent> derivationEvents = specimen.getDerivationEvents();
+        for (DerivationEvent derivationEvent : derivationEvents) {
+            Set<DerivedUnit> derivatives = derivationEvent.getDerivatives();
+            for (DerivedUnit derivedUnit : derivatives) {
+                childDerivate.add(derivedUnit);
+                childDerivate.addAll(getAllChildDerivatives(derivedUnit.getUuid()));
+            }
+        }
+        return childDerivate;
+    }
+
+    @Override
+    public int countOccurrences(IIdentifiableEntityServiceConfigurator<SpecimenOrObservationBase> config){
+        return countByTitle(config);
+    }
+
+}