- added functionality to filter assigned specimens in the DerivateSearchView (fixes...
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / OccurrenceServiceImpl.java
index bf0b7a1bdbf92a4a19c102ee702c4db18fde8d69..7154ca41227432c3dca79326e773eac8fb5b9bd1 100644 (file)
 \r
 package eu.etaxonomy.cdm.api.service;\r
 \r
+import java.io.IOException;\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.Set;\r
+import java.util.UUID;\r
 \r
 import org.apache.log4j.Logger;\r
-import org.hibernate.Hibernate;\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.springframework.beans.factory.annotation.Autowired;\r
 import org.springframework.stereotype.Service;\r
-import org.springframework.transaction.annotation.Propagation;\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.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.common.DefinedTermBase;\r
-import eu.etaxonomy.cdm.model.common.RelationshipBase.Direction;\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.description.TaxonDescription;\r
-import eu.etaxonomy.cdm.model.location.WaterbodyOrCountry;\r
+import eu.etaxonomy.cdm.model.location.Country;\r
 import eu.etaxonomy.cdm.model.media.Media;\r
-import eu.etaxonomy.cdm.model.name.HomotypicalGroup;\r
-import eu.etaxonomy.cdm.model.name.TaxonNameBase;\r
+import eu.etaxonomy.cdm.model.molecular.DnaSample;\r
+import eu.etaxonomy.cdm.model.molecular.Sequence;\r
 import eu.etaxonomy.cdm.model.occurrence.DerivationEvent;\r
-import eu.etaxonomy.cdm.model.occurrence.DerivedUnitBase;\r
+import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;\r
 import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;\r
-import eu.etaxonomy.cdm.model.occurrence.FieldObservation;\r
+import eu.etaxonomy.cdm.model.occurrence.FieldUnit;\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.model.taxon.TaxonRelationship;\r
-import eu.etaxonomy.cdm.persistence.dao.AbstractBeanInitializer;\r
 import eu.etaxonomy.cdm.persistence.dao.common.IDefinedTermDao;\r
-import eu.etaxonomy.cdm.persistence.dao.description.IDescriptionDao;\r
+import eu.etaxonomy.cdm.persistence.dao.initializer.AbstractBeanInitializer;\r
 import eu.etaxonomy.cdm.persistence.dao.occurrence.IOccurrenceDao;\r
 import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao;\r
 import eu.etaxonomy.cdm.persistence.query.OrderHint;\r
@@ -63,7 +80,7 @@ import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
  * @created 01.09.2008\r
  */\r
 @Service\r
-@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)\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
@@ -77,12 +94,17 @@ public class OccurrenceServiceImpl extends IdentifiableServiceBase<SpecimenOrObs
     @Autowired\r
     private ITaxonService taxonService;\r
 \r
+    @Autowired\r
+    private ISequenceService sequenceService;\r
+\r
     @Autowired\r
     private AbstractBeanInitializer beanInitializer;\r
 \r
     @Autowired\r
     private ITaxonDao taxonDao;\r
 \r
+    @Autowired\r
+    private ILuceneIndexToolProvider luceneIndexToolProvider;\r
 \r
 \r
     public OccurrenceServiceImpl() {\r
@@ -107,7 +129,8 @@ public class OccurrenceServiceImpl extends IdentifiableServiceBase<SpecimenOrObs
      * FIXME Candidate for harmonization\r
      * move to termService\r
      */\r
-    public WaterbodyOrCountry getCountryByIso(String iso639) {\r
+    @Override\r
+    public Country getCountryByIso(String iso639) {\r
         return this.definedTermDao.getCountryByIso(iso639);\r
 \r
     }\r
@@ -116,20 +139,23 @@ public class OccurrenceServiceImpl extends IdentifiableServiceBase<SpecimenOrObs
      * FIXME Candidate for harmonization\r
      * move to termService\r
      */\r
-    public List<WaterbodyOrCountry> getWaterbodyOrCountryByName(String name) {\r
-        List<? extends DefinedTermBase> terms = this.definedTermDao.findByTitle(WaterbodyOrCountry.class, name, null, null, null, null, null, null) ;\r
-        List<WaterbodyOrCountry> countries = new ArrayList<WaterbodyOrCountry>();\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((WaterbodyOrCountry)terms.get(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
@@ -141,6 +167,15 @@ public class OccurrenceServiceImpl extends IdentifiableServiceBase<SpecimenOrObs
         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
@@ -152,6 +187,7 @@ public class OccurrenceServiceImpl extends IdentifiableServiceBase<SpecimenOrObs
         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
@@ -166,6 +202,7 @@ public class OccurrenceServiceImpl extends IdentifiableServiceBase<SpecimenOrObs
     /* (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
@@ -178,21 +215,21 @@ public class OccurrenceServiceImpl extends IdentifiableServiceBase<SpecimenOrObs
     }\r
 \r
     @Override\r
-    public List<UuidAndTitleCache<DerivedUnitBase>> getDerivedUnitBaseUuidAndTitleCache() {\r
-        return dao.getDerivedUnitBaseUuidAndTitleCache();\r
+    public List<UuidAndTitleCache<DerivedUnit>> getDerivedUnitUuidAndTitleCache() {\r
+        return dao.getDerivedUnitUuidAndTitleCache();\r
     }\r
 \r
     @Override\r
-    public List<UuidAndTitleCache<FieldObservation>> getFieldObservationUuidAndTitleCache() {\r
-        return dao.getFieldObservationUuidAndTitleCache();\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.DerivedUnitBase)\r
+     * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#getDerivedUnitFacade(eu.etaxonomy.cdm.model.occurrence.DerivedUnit)\r
      */\r
     @Override\r
-    public DerivedUnitFacade getDerivedUnitFacade(DerivedUnitBase derivedUnit, List<String> propertyPaths) throws DerivedUnitFacadeNotSupportedException {\r
-        derivedUnit = (DerivedUnitBase<?>)dao.load(derivedUnit.getUuid(), null);\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
@@ -216,9 +253,9 @@ public class OccurrenceServiceImpl extends IdentifiableServiceBase<SpecimenOrObs
                 tempIndividualsAssociation = (IndividualsAssociation)element;\r
                 if(tempIndividualsAssociation.getAssociatedSpecimenOrObservation() != null){\r
                     tempSpecimenOrObservationBase = HibernateProxyHelper.deproxy(tempIndividualsAssociation.getAssociatedSpecimenOrObservation(), SpecimenOrObservationBase.class);\r
-                    if(tempSpecimenOrObservationBase instanceof DerivedUnitBase){\r
+                    if(tempSpecimenOrObservationBase instanceof DerivedUnit){\r
                         try {\r
-                            derivedUnitFacadeList.add(DerivedUnitFacade.NewInstance((DerivedUnitBase)tempSpecimenOrObservationBase));\r
+                            derivedUnitFacadeList.add(DerivedUnitFacade.NewInstance((DerivedUnit)tempSpecimenOrObservationBase));\r
                         } catch (DerivedUnitFacadeNotSupportedException e) {\r
                             logger.warn(tempIndividualsAssociation.getAssociatedSpecimenOrObservation().getTitleCache() + " : " +e.getMessage());\r
                         }\r
@@ -241,39 +278,224 @@ public class OccurrenceServiceImpl extends IdentifiableServiceBase<SpecimenOrObs
     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
-        List<Taxon> taxa = new ArrayList<Taxon>();\r
-        List<Integer> occurrenceIds = new ArrayList<Integer>();\r
+        return pageByAssociatedTaxon(type, includeRelationships, associatedTaxon, maxDepth, pageSize, pageNumber, orderHints, propertyPaths).getRecords();\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
+        associatedTaxon = (Taxon) taxonDao.load(associatedTaxon.getUuid());\r
 \r
         if(includeRelationships != null) {\r
-            taxonService.listRelatedTaxa(associatedTaxon, includeRelationships, maxDepth, pageSize, pageSize, propertyPaths);\r
+            taxa = taxonService.listRelatedTaxa(associatedTaxon, includeRelationships, maxDepth, null, null, propertyPaths);\r
         }\r
 \r
-        taxa.add((Taxon) taxonDao.load(associatedTaxon.getUuid()));\r
+        taxa.add(associatedTaxon);\r
 \r
         for (Taxon taxon : taxa) {\r
-            List<T> perTaxonOccurrences = dao.listByAssociatedTaxon(type, associatedTaxon, pageSize, pageSize, orderHints, propertyPaths);\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
-        dao.listByIds(occurrenceIds, null, null, orderHints, propertyPaths);\r
+        return new DefaultPagerImpl<T>(pageNumber, occurrenceIds.size(), pageSize, occurrences);\r
 \r
-        return 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
-            Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {\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) taxonDao.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
-        return null;\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
+        Collection<FieldUnit> fieldUnits = new ArrayList<FieldUnit>();\r
+        SpecimenOrObservationBase derivedUnit = load(derivedUnitUuid);\r
+        if(derivedUnit instanceof DerivedUnit){\r
+            getFieldUnits((DerivedUnit) derivedUnit, 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.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
 }\r