- added service method to term service that retrieves terms by TermType
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / OccurrenceServiceImpl.java
1 // $Id$
2 /**
3 * Copyright (C) 2007 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
6 *
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
9 */
10
11 package eu.etaxonomy.cdm.api.service;
12
13 import java.io.IOException;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.Collection;
17 import java.util.HashMap;
18 import java.util.HashSet;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.UUID;
23
24 import org.apache.log4j.Logger;
25 import org.apache.lucene.index.CorruptIndexException;
26 import org.apache.lucene.queryParser.ParseException;
27 import org.apache.lucene.search.BooleanClause.Occur;
28 import org.apache.lucene.search.BooleanQuery;
29 import org.apache.lucene.search.SortField;
30 import org.hibernate.TransientObjectException;
31 import org.hibernate.search.spatial.impl.Rectangle;
32 import org.springframework.beans.factory.annotation.Autowired;
33 import org.springframework.stereotype.Service;
34 import org.springframework.transaction.annotation.Transactional;
35
36 import eu.etaxonomy.cdm.api.facade.DerivedUnitFacade;
37 import eu.etaxonomy.cdm.api.facade.DerivedUnitFacadeConfigurator;
38 import eu.etaxonomy.cdm.api.facade.DerivedUnitFacadeNotSupportedException;
39 import eu.etaxonomy.cdm.api.service.molecular.ISequenceService;
40 import eu.etaxonomy.cdm.api.service.pager.Pager;
41 import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
42 import eu.etaxonomy.cdm.api.service.search.ILuceneIndexToolProvider;
43 import eu.etaxonomy.cdm.api.service.search.ISearchResultBuilder;
44 import eu.etaxonomy.cdm.api.service.search.LuceneSearch;
45 import eu.etaxonomy.cdm.api.service.search.LuceneSearch.TopGroupsWithMaxScore;
46 import eu.etaxonomy.cdm.api.service.search.QueryFactory;
47 import eu.etaxonomy.cdm.api.service.search.SearchResult;
48 import eu.etaxonomy.cdm.api.service.search.SearchResultBuilder;
49 import eu.etaxonomy.cdm.api.service.util.TaxonRelationshipEdge;
50 import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
51 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
52 import eu.etaxonomy.cdm.model.CdmBaseType;
53 import eu.etaxonomy.cdm.model.common.DefinedTermBase;
54 import eu.etaxonomy.cdm.model.common.Language;
55 import eu.etaxonomy.cdm.model.common.UuidAndTitleCache;
56 import eu.etaxonomy.cdm.model.description.DescriptionBase;
57 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
58 import eu.etaxonomy.cdm.model.description.IndividualsAssociation;
59 import eu.etaxonomy.cdm.model.location.Country;
60 import eu.etaxonomy.cdm.model.media.Media;
61 import eu.etaxonomy.cdm.model.molecular.DnaSample;
62 import eu.etaxonomy.cdm.model.molecular.Sequence;
63 import eu.etaxonomy.cdm.model.occurrence.DerivationEvent;
64 import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
65 import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
66 import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
67 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
68 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;
69 import eu.etaxonomy.cdm.model.taxon.Taxon;
70 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
71 import eu.etaxonomy.cdm.persistence.dao.common.IDefinedTermDao;
72 import eu.etaxonomy.cdm.persistence.dao.initializer.AbstractBeanInitializer;
73 import eu.etaxonomy.cdm.persistence.dao.occurrence.IOccurrenceDao;
74 import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao;
75 import eu.etaxonomy.cdm.persistence.query.OrderHint;
76 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
77
78 /**
79 * @author a.babadshanjan
80 * @created 01.09.2008
81 */
82 @Service
83 @Transactional(readOnly = true)
84 public class OccurrenceServiceImpl extends IdentifiableServiceBase<SpecimenOrObservationBase,IOccurrenceDao> implements IOccurrenceService {
85
86 static private final Logger logger = Logger.getLogger(OccurrenceServiceImpl.class);
87
88 @Autowired
89 private IDefinedTermDao definedTermDao;
90
91 @Autowired
92 private IDescriptionService descriptionService;
93
94 @Autowired
95 private ITaxonService taxonService;
96
97 @Autowired
98 private ISequenceService sequenceService;
99
100 @Autowired
101 private AbstractBeanInitializer beanInitializer;
102
103 @Autowired
104 private ITaxonDao taxonDao;
105
106 @Autowired
107 private ILuceneIndexToolProvider luceneIndexToolProvider;
108
109
110 public OccurrenceServiceImpl() {
111 logger.debug("Load OccurrenceService Bean");
112 }
113
114
115 /* (non-Javadoc)
116 * @see eu.etaxonomy.cdm.api.service.IIdentifiableEntityService#updateTitleCache(java.lang.Integer, eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy)
117 */
118 @Override
119 @Transactional(readOnly = false)
120 public void updateTitleCache(Class<? extends SpecimenOrObservationBase> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<SpecimenOrObservationBase> cacheStrategy, IProgressMonitor monitor) {
121 if (clazz == null){
122 clazz = SpecimenOrObservationBase.class;
123 }
124 super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);
125 }
126
127
128 /**
129 * FIXME Candidate for harmonization
130 * move to termService
131 */
132 @Override
133 public Country getCountryByIso(String iso639) {
134 return this.definedTermDao.getCountryByIso(iso639);
135
136 }
137
138 /**
139 * FIXME Candidate for harmonization
140 * move to termService
141 */
142 @Override
143 public List<Country> getCountryByName(String name) {
144 List<? extends DefinedTermBase> terms = this.definedTermDao.findByTitle(Country.class, name, null, null, null, null, null, null) ;
145 List<Country> countries = new ArrayList<Country>();
146 for (int i=0;i<terms.size();i++){
147 countries.add((Country)terms.get(i));
148 }
149 return countries;
150 }
151
152 @Override
153 @Autowired
154 protected void setDao(IOccurrenceDao dao) {
155 this.dao = dao;
156 }
157
158 @Override
159 public Pager<DerivationEvent> getDerivationEvents(SpecimenOrObservationBase occurence, Integer pageSize,Integer pageNumber, List<String> propertyPaths) {
160 Integer numberOfResults = dao.countDerivationEvents(occurence);
161
162 List<DerivationEvent> results = new ArrayList<DerivationEvent>();
163 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
164 results = dao.getDerivationEvents(occurence, pageSize, pageNumber,propertyPaths);
165 }
166
167 return new DefaultPagerImpl<DerivationEvent>(pageNumber, numberOfResults, pageSize, results);
168 }
169
170 @Override
171 public Pager<DeterminationEvent> getDeterminations(SpecimenOrObservationBase occurrence, TaxonBase taxonBase, Integer pageSize,Integer pageNumber, List<String> propertyPaths) {
172 Integer numberOfResults = dao.countDeterminations(occurrence, taxonBase);
173
174 List<DeterminationEvent> results = new ArrayList<DeterminationEvent>();
175 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
176 results = dao.getDeterminations(occurrence,taxonBase, pageSize, pageNumber, propertyPaths);
177 }
178
179 return new DefaultPagerImpl<DeterminationEvent>(pageNumber, numberOfResults, pageSize, results);
180 }
181
182 @Override
183 public Pager<Media> getMedia(SpecimenOrObservationBase occurence,Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
184 Integer numberOfResults = dao.countMedia(occurence);
185
186 List<Media> results = new ArrayList<Media>();
187 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
188 results = dao.getMedia(occurence, pageSize, pageNumber, propertyPaths);
189 }
190
191 return new DefaultPagerImpl<Media>(pageNumber, numberOfResults, pageSize, results);
192 }
193
194 /* (non-Javadoc)
195 * @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)
196 */
197 @Override
198 public Pager<SpecimenOrObservationBase> list(Class<? extends SpecimenOrObservationBase> type, TaxonBase determinedAs, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
199 Integer numberOfResults = dao.count(type,determinedAs);
200 List<SpecimenOrObservationBase> results = new ArrayList<SpecimenOrObservationBase>();
201 pageNumber = pageNumber == null ? 0 : pageNumber;
202 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
203 Integer start = pageSize == null ? 0 : pageSize * pageNumber;
204 results = dao.list(type,determinedAs, pageSize, start, orderHints,propertyPaths);
205 }
206 return new DefaultPagerImpl<SpecimenOrObservationBase>(pageNumber, numberOfResults, pageSize, results);
207 }
208
209 @Override
210 public List<UuidAndTitleCache<DerivedUnit>> getDerivedUnitUuidAndTitleCache() {
211 return dao.getDerivedUnitUuidAndTitleCache();
212 }
213
214 @Override
215 public List<UuidAndTitleCache<FieldUnit>> getFieldUnitUuidAndTitleCache() {
216 return dao.getFieldUnitUuidAndTitleCache();
217 }
218
219 /* (non-Javadoc)
220 * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#getDerivedUnitFacade(eu.etaxonomy.cdm.model.occurrence.DerivedUnit)
221 */
222 @Override
223 public DerivedUnitFacade getDerivedUnitFacade(DerivedUnit derivedUnit, List<String> propertyPaths) throws DerivedUnitFacadeNotSupportedException {
224 derivedUnit = (DerivedUnit)dao.load(derivedUnit.getUuid(), null);
225 DerivedUnitFacadeConfigurator config = DerivedUnitFacadeConfigurator.NewInstance();
226 config.setThrowExceptionForNonSpecimenPreservationMethodRequest(false);
227 DerivedUnitFacade derivedUnitFacade = DerivedUnitFacade.NewInstance(derivedUnit, config);
228 beanInitializer.initialize(derivedUnitFacade, propertyPaths);
229 return derivedUnitFacade;
230 }
231
232 /* (non-Javadoc)
233 * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#listDerivedUnitFacades(eu.etaxonomy.cdm.model.description.DescriptionBase, java.util.List)
234 */
235 @Override
236 public List<DerivedUnitFacade> listDerivedUnitFacades(
237 DescriptionBase description, List<String> propertyPaths) {
238
239 List<DerivedUnitFacade> derivedUnitFacadeList = new ArrayList<DerivedUnitFacade>();
240 IndividualsAssociation tempIndividualsAssociation;
241 SpecimenOrObservationBase tempSpecimenOrObservationBase;
242 List<DescriptionElementBase> elements = descriptionService.listDescriptionElements(description, null, IndividualsAssociation.class, null, 0, Arrays.asList(new String []{"associatedSpecimenOrObservation"}));
243 for(DescriptionElementBase element : elements){
244 if(element instanceof IndividualsAssociation){
245 tempIndividualsAssociation = (IndividualsAssociation)element;
246 if(tempIndividualsAssociation.getAssociatedSpecimenOrObservation() != null){
247 tempSpecimenOrObservationBase = HibernateProxyHelper.deproxy(tempIndividualsAssociation.getAssociatedSpecimenOrObservation(), SpecimenOrObservationBase.class);
248 if(tempSpecimenOrObservationBase instanceof DerivedUnit){
249 try {
250 derivedUnitFacadeList.add(DerivedUnitFacade.NewInstance((DerivedUnit)tempSpecimenOrObservationBase));
251 } catch (DerivedUnitFacadeNotSupportedException e) {
252 logger.warn(tempIndividualsAssociation.getAssociatedSpecimenOrObservation().getTitleCache() + " : " +e.getMessage());
253 }
254 }
255 }
256
257 }
258 }
259
260 beanInitializer.initializeAll(derivedUnitFacadeList, propertyPaths);
261
262 return derivedUnitFacadeList;
263 }
264
265
266 /* (non-Javadoc)
267 * @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)
268 */
269 @Override
270 public <T extends SpecimenOrObservationBase> List<T> listByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,
271 Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
272
273 return pageByAssociatedTaxon(type, includeRelationships, associatedTaxon, maxDepth, pageSize, pageNumber, orderHints, propertyPaths).getRecords();
274 }
275
276
277 /* (non-Javadoc)
278 * @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)
279 */
280 @SuppressWarnings("unchecked")
281 @Override
282 public <T extends SpecimenOrObservationBase> Pager<T> pageByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,
283 Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
284
285 Set<Taxon> taxa = new HashSet<Taxon>();
286 Set<Integer> occurrenceIds = new HashSet<Integer>();
287 List<T> occurrences = new ArrayList<T>();
288
289 // Integer limit = PagerUtils.limitFor(pageSize);
290 // Integer start = PagerUtils.startFor(pageSize, pageNumber);
291
292 associatedTaxon = (Taxon) taxonDao.load(associatedTaxon.getUuid());
293
294 if(includeRelationships != null) {
295 taxa = taxonService.listRelatedTaxa(associatedTaxon, includeRelationships, maxDepth, null, null, propertyPaths);
296 }
297
298 taxa.add(associatedTaxon);
299
300 for (Taxon taxon : taxa) {
301 List<T> perTaxonOccurrences = dao.listByAssociatedTaxon(type, taxon, null, null, orderHints, propertyPaths);
302 for (SpecimenOrObservationBase o : perTaxonOccurrences) {
303 occurrenceIds.add(o.getId());
304 }
305 }
306 occurrences = (List<T>) dao.listByIds(occurrenceIds, pageSize, pageNumber, orderHints, propertyPaths);
307
308 return new DefaultPagerImpl<T>(pageNumber, occurrenceIds.size(), pageSize, occurrences);
309
310 }
311
312 /* (non-Javadoc)
313 * @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)
314 */
315 @SuppressWarnings("unchecked")
316 @Override
317 public <T extends SpecimenOrObservationBase> Pager<T> pageByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,
318 String taxonUUID, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
319
320 UUID uuid = UUID.fromString(taxonUUID);
321 Taxon tax = (Taxon) taxonDao.load(uuid);
322 //TODO REMOVE NULL STATEMENT
323 type=null;
324 return pageByAssociatedTaxon( type,includeRelationships,tax, maxDepth, pageSize, pageNumber, orderHints, propertyPaths );
325
326 }
327
328
329 @Override
330 public Pager<SearchResult<SpecimenOrObservationBase>> findByFullText(
331 Class<? extends SpecimenOrObservationBase> clazz, String queryString, Rectangle boundingBox, List<Language> languages,
332 boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
333 List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
334
335 LuceneSearch luceneSearch = prepareByFullTextSearch(clazz, queryString, boundingBox, languages, highlightFragments);
336
337 // --- execute search
338 TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
339
340 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
341 idFieldMap.put(CdmBaseType.SPECIMEN_OR_OBSERVATIONBASE, "id");
342
343 // --- initialize taxa, highlight matches ....
344 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
345 @SuppressWarnings("rawtypes")
346 List<SearchResult<SpecimenOrObservationBase>> searchResults = searchResultBuilder.createResultSet(
347 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
348
349 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
350
351 return new DefaultPagerImpl<SearchResult<SpecimenOrObservationBase>>(pageNumber, totalHits, pageSize,
352 searchResults);
353
354 }
355
356
357 /**
358 * @param clazz
359 * @param queryString
360 * @param languages
361 * @param highlightFragments
362 * @return
363 */
364 private LuceneSearch prepareByFullTextSearch(Class<? extends SpecimenOrObservationBase> clazz, String queryString, Rectangle bbox,
365 List<Language> languages, boolean highlightFragments) {
366
367 BooleanQuery finalQuery = new BooleanQuery();
368 BooleanQuery textQuery = new BooleanQuery();
369
370 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, FieldUnit.class);
371 QueryFactory queryFactory = luceneIndexToolProvider.newQueryFactoryFor(FieldUnit.class);
372
373 // --- criteria
374 luceneSearch.setCdmTypRestriction(clazz);
375 if(queryString != null){
376 textQuery.add(queryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
377 finalQuery.add(textQuery, Occur.MUST);
378 }
379
380 // --- spacial query
381 if(bbox != null){
382 finalQuery.add(QueryFactory.buildSpatialQueryByRange(bbox, "gatheringEvent.exactLocation.point"), Occur.MUST);
383 }
384
385 luceneSearch.setQuery(finalQuery);
386
387 // --- sorting
388 SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
389 luceneSearch.setSortFields(sortFields);
390
391 if(highlightFragments){
392 luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray());
393 }
394 return luceneSearch;
395 }
396
397
398 /* (non-Javadoc)
399 * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#getFieldUnits(eu.etaxonomy.cdm.model.occurrence.DerivedUnit)
400 */
401 @Override
402 public Collection<FieldUnit> getFieldUnits(UUID derivedUnitUuid) {
403 //It will search recursively over all {@link DerivationEvent}s and get the "originals" ({@link SpecimenOrObservationBase})
404 //from which this DerivedUnit was derived until all FieldUnits are found.
405
406 //FIXME: use HQL queries to increase performance
407 Collection<FieldUnit> fieldUnits = new ArrayList<FieldUnit>();
408 SpecimenOrObservationBase derivedUnit = load(derivedUnitUuid);
409 if(derivedUnit instanceof DerivedUnit){
410 getFieldUnits((DerivedUnit) derivedUnit, fieldUnits);
411 }
412 return fieldUnits;
413 }
414
415
416 /**
417 * @param original
418 * @param fieldUnits
419 */
420 private void getFieldUnits(DerivedUnit derivedUnit, Collection<FieldUnit> fieldUnits) {
421 Set<SpecimenOrObservationBase> originals = derivedUnit.getOriginals();
422 if(originals!=null && !originals.isEmpty()){
423 for(SpecimenOrObservationBase<?> original:originals){
424 if(original instanceof FieldUnit){
425 fieldUnits.add((FieldUnit) original);
426 }
427 else if(original instanceof DerivedUnit){
428 getFieldUnits((DerivedUnit) original, fieldUnits);
429 }
430 }
431 }
432 }
433
434 /* (non-Javadoc)
435 * @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)
436 */
437 @Override
438 public boolean moveSequence(DnaSample from, DnaSample to, Sequence sequence) {
439 //reload specimens to avoid session conflicts
440 from = (DnaSample) load(from.getUuid());
441 to = (DnaSample) load(to.getUuid());
442 sequence = sequenceService.load(sequence.getUuid());
443
444 if(from==null || to==null || sequence==null){
445 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" +
446 "Operation was move "+sequence+ " from "+from+" to "+to);
447 }
448 from.removeSequence(sequence);
449 saveOrUpdate(from);
450 to.addSequence(sequence);
451 saveOrUpdate(to);
452 return true;
453 }
454
455 /* (non-Javadoc)
456 * @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)
457 */
458 @Override
459 public boolean moveDerivate(SpecimenOrObservationBase<?> from, SpecimenOrObservationBase<?> to, DerivedUnit derivate) {
460 //reload specimens to avoid session conflicts
461 from = load(from.getUuid());
462 to = load(to.getUuid());
463 derivate = (DerivedUnit) load(derivate.getUuid());
464
465 if(from==null || to==null || derivate==null){
466 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" +
467 "Operation was move "+derivate+ " from "+from+" to "+to);
468 }
469
470 SpecimenOrObservationType derivateType = derivate.getRecordBasis();
471 SpecimenOrObservationType toType = to.getRecordBasis();
472 //check if type is a sub derivate type
473 if(toType==SpecimenOrObservationType.FieldUnit //moving to FieldUnit always works
474 || (derivateType.isKindOf(toType) && toType!=derivateType)){ //moving only to parent derivate type
475 //remove derivation event from parent specimen of dragged object
476 DerivationEvent eventToRemove = null;
477 for(DerivationEvent event:from.getDerivationEvents()){
478 if(event.getDerivatives().contains(derivate)){
479 eventToRemove = event;
480 break;
481 }
482 }
483 from.removeDerivationEvent(eventToRemove);
484 saveOrUpdate(from);
485 //add new derivation event to target
486 to.addDerivationEvent(DerivationEvent.NewSimpleInstance(to, derivate, eventToRemove==null?null:eventToRemove.getType()));
487 saveOrUpdate(to);
488 return true;
489 }
490 return false;
491 }
492
493 }