- added functionality to filter assigned specimens in the DerivateSearchView (fixes...
[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 /* (non-Javadoc)
171 * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#countDeterminations(eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase, eu.etaxonomy.cdm.model.taxon.TaxonBase)
172 */
173 @Override
174 public int countDeterminations(SpecimenOrObservationBase occurence, TaxonBase taxonbase) {
175 return dao.countDeterminations(occurence, taxonbase);
176 }
177
178 @Override
179 public Pager<DeterminationEvent> getDeterminations(SpecimenOrObservationBase occurrence, TaxonBase taxonBase, Integer pageSize,Integer pageNumber, List<String> propertyPaths) {
180 Integer numberOfResults = dao.countDeterminations(occurrence, taxonBase);
181
182 List<DeterminationEvent> results = new ArrayList<DeterminationEvent>();
183 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
184 results = dao.getDeterminations(occurrence,taxonBase, pageSize, pageNumber, propertyPaths);
185 }
186
187 return new DefaultPagerImpl<DeterminationEvent>(pageNumber, numberOfResults, pageSize, results);
188 }
189
190 @Override
191 public Pager<Media> getMedia(SpecimenOrObservationBase occurence,Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
192 Integer numberOfResults = dao.countMedia(occurence);
193
194 List<Media> results = new ArrayList<Media>();
195 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
196 results = dao.getMedia(occurence, pageSize, pageNumber, propertyPaths);
197 }
198
199 return new DefaultPagerImpl<Media>(pageNumber, numberOfResults, pageSize, results);
200 }
201
202 /* (non-Javadoc)
203 * @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)
204 */
205 @Override
206 public Pager<SpecimenOrObservationBase> list(Class<? extends SpecimenOrObservationBase> type, TaxonBase determinedAs, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
207 Integer numberOfResults = dao.count(type,determinedAs);
208 List<SpecimenOrObservationBase> results = new ArrayList<SpecimenOrObservationBase>();
209 pageNumber = pageNumber == null ? 0 : pageNumber;
210 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
211 Integer start = pageSize == null ? 0 : pageSize * pageNumber;
212 results = dao.list(type,determinedAs, pageSize, start, orderHints,propertyPaths);
213 }
214 return new DefaultPagerImpl<SpecimenOrObservationBase>(pageNumber, numberOfResults, pageSize, results);
215 }
216
217 @Override
218 public List<UuidAndTitleCache<DerivedUnit>> getDerivedUnitUuidAndTitleCache() {
219 return dao.getDerivedUnitUuidAndTitleCache();
220 }
221
222 @Override
223 public List<UuidAndTitleCache<FieldUnit>> getFieldUnitUuidAndTitleCache() {
224 return dao.getFieldUnitUuidAndTitleCache();
225 }
226
227 /* (non-Javadoc)
228 * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#getDerivedUnitFacade(eu.etaxonomy.cdm.model.occurrence.DerivedUnit)
229 */
230 @Override
231 public DerivedUnitFacade getDerivedUnitFacade(DerivedUnit derivedUnit, List<String> propertyPaths) throws DerivedUnitFacadeNotSupportedException {
232 derivedUnit = (DerivedUnit)dao.load(derivedUnit.getUuid(), null);
233 DerivedUnitFacadeConfigurator config = DerivedUnitFacadeConfigurator.NewInstance();
234 config.setThrowExceptionForNonSpecimenPreservationMethodRequest(false);
235 DerivedUnitFacade derivedUnitFacade = DerivedUnitFacade.NewInstance(derivedUnit, config);
236 beanInitializer.initialize(derivedUnitFacade, propertyPaths);
237 return derivedUnitFacade;
238 }
239
240 /* (non-Javadoc)
241 * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#listDerivedUnitFacades(eu.etaxonomy.cdm.model.description.DescriptionBase, java.util.List)
242 */
243 @Override
244 public List<DerivedUnitFacade> listDerivedUnitFacades(
245 DescriptionBase description, List<String> propertyPaths) {
246
247 List<DerivedUnitFacade> derivedUnitFacadeList = new ArrayList<DerivedUnitFacade>();
248 IndividualsAssociation tempIndividualsAssociation;
249 SpecimenOrObservationBase tempSpecimenOrObservationBase;
250 List<DescriptionElementBase> elements = descriptionService.listDescriptionElements(description, null, IndividualsAssociation.class, null, 0, Arrays.asList(new String []{"associatedSpecimenOrObservation"}));
251 for(DescriptionElementBase element : elements){
252 if(element instanceof IndividualsAssociation){
253 tempIndividualsAssociation = (IndividualsAssociation)element;
254 if(tempIndividualsAssociation.getAssociatedSpecimenOrObservation() != null){
255 tempSpecimenOrObservationBase = HibernateProxyHelper.deproxy(tempIndividualsAssociation.getAssociatedSpecimenOrObservation(), SpecimenOrObservationBase.class);
256 if(tempSpecimenOrObservationBase instanceof DerivedUnit){
257 try {
258 derivedUnitFacadeList.add(DerivedUnitFacade.NewInstance((DerivedUnit)tempSpecimenOrObservationBase));
259 } catch (DerivedUnitFacadeNotSupportedException e) {
260 logger.warn(tempIndividualsAssociation.getAssociatedSpecimenOrObservation().getTitleCache() + " : " +e.getMessage());
261 }
262 }
263 }
264
265 }
266 }
267
268 beanInitializer.initializeAll(derivedUnitFacadeList, propertyPaths);
269
270 return derivedUnitFacadeList;
271 }
272
273
274 /* (non-Javadoc)
275 * @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)
276 */
277 @Override
278 public <T extends SpecimenOrObservationBase> List<T> listByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,
279 Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
280
281 return pageByAssociatedTaxon(type, includeRelationships, associatedTaxon, maxDepth, pageSize, pageNumber, orderHints, propertyPaths).getRecords();
282 }
283
284
285 /* (non-Javadoc)
286 * @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)
287 */
288 @SuppressWarnings("unchecked")
289 @Override
290 public <T extends SpecimenOrObservationBase> Pager<T> pageByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,
291 Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
292
293 Set<Taxon> taxa = new HashSet<Taxon>();
294 Set<Integer> occurrenceIds = new HashSet<Integer>();
295 List<T> occurrences = new ArrayList<T>();
296
297 // Integer limit = PagerUtils.limitFor(pageSize);
298 // Integer start = PagerUtils.startFor(pageSize, pageNumber);
299
300 associatedTaxon = (Taxon) taxonDao.load(associatedTaxon.getUuid());
301
302 if(includeRelationships != null) {
303 taxa = taxonService.listRelatedTaxa(associatedTaxon, includeRelationships, maxDepth, null, null, propertyPaths);
304 }
305
306 taxa.add(associatedTaxon);
307
308 for (Taxon taxon : taxa) {
309 List<T> perTaxonOccurrences = dao.listByAssociatedTaxon(type, taxon, null, null, orderHints, propertyPaths);
310 for (SpecimenOrObservationBase o : perTaxonOccurrences) {
311 occurrenceIds.add(o.getId());
312 }
313 }
314 occurrences = (List<T>) dao.listByIds(occurrenceIds, pageSize, pageNumber, orderHints, propertyPaths);
315
316 return new DefaultPagerImpl<T>(pageNumber, occurrenceIds.size(), pageSize, occurrences);
317
318 }
319
320 /* (non-Javadoc)
321 * @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)
322 */
323 @SuppressWarnings("unchecked")
324 @Override
325 public <T extends SpecimenOrObservationBase> Pager<T> pageByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,
326 String taxonUUID, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
327
328 UUID uuid = UUID.fromString(taxonUUID);
329 Taxon tax = (Taxon) taxonDao.load(uuid);
330 //TODO REMOVE NULL STATEMENT
331 type=null;
332 return pageByAssociatedTaxon( type,includeRelationships,tax, maxDepth, pageSize, pageNumber, orderHints, propertyPaths );
333
334 }
335
336
337 @Override
338 public Pager<SearchResult<SpecimenOrObservationBase>> findByFullText(
339 Class<? extends SpecimenOrObservationBase> clazz, String queryString, Rectangle boundingBox, List<Language> languages,
340 boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
341 List<String> propertyPaths) throws CorruptIndexException, IOException, ParseException {
342
343 LuceneSearch luceneSearch = prepareByFullTextSearch(clazz, queryString, boundingBox, languages, highlightFragments);
344
345 // --- execute search
346 TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
347
348 Map<CdmBaseType, String> idFieldMap = new HashMap<CdmBaseType, String>();
349 idFieldMap.put(CdmBaseType.SPECIMEN_OR_OBSERVATIONBASE, "id");
350
351 // --- initialize taxa, highlight matches ....
352 ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
353 @SuppressWarnings("rawtypes")
354 List<SearchResult<SpecimenOrObservationBase>> searchResults = searchResultBuilder.createResultSet(
355 topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
356
357 int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0;
358
359 return new DefaultPagerImpl<SearchResult<SpecimenOrObservationBase>>(pageNumber, totalHits, pageSize,
360 searchResults);
361
362 }
363
364
365 /**
366 * @param clazz
367 * @param queryString
368 * @param languages
369 * @param highlightFragments
370 * @return
371 */
372 private LuceneSearch prepareByFullTextSearch(Class<? extends SpecimenOrObservationBase> clazz, String queryString, Rectangle bbox,
373 List<Language> languages, boolean highlightFragments) {
374
375 BooleanQuery finalQuery = new BooleanQuery();
376 BooleanQuery textQuery = new BooleanQuery();
377
378 LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, FieldUnit.class);
379 QueryFactory queryFactory = luceneIndexToolProvider.newQueryFactoryFor(FieldUnit.class);
380
381 // --- criteria
382 luceneSearch.setCdmTypRestriction(clazz);
383 if(queryString != null){
384 textQuery.add(queryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
385 finalQuery.add(textQuery, Occur.MUST);
386 }
387
388 // --- spacial query
389 if(bbox != null){
390 finalQuery.add(QueryFactory.buildSpatialQueryByRange(bbox, "gatheringEvent.exactLocation.point"), Occur.MUST);
391 }
392
393 luceneSearch.setQuery(finalQuery);
394
395 // --- sorting
396 SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)};
397 luceneSearch.setSortFields(sortFields);
398
399 if(highlightFragments){
400 luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray());
401 }
402 return luceneSearch;
403 }
404
405
406 /* (non-Javadoc)
407 * @see eu.etaxonomy.cdm.api.service.IOccurrenceService#getFieldUnits(eu.etaxonomy.cdm.model.occurrence.DerivedUnit)
408 */
409 @Override
410 public Collection<FieldUnit> getFieldUnits(UUID derivedUnitUuid) {
411 //It will search recursively over all {@link DerivationEvent}s and get the "originals" ({@link SpecimenOrObservationBase})
412 //from which this DerivedUnit was derived until all FieldUnits are found.
413
414 //FIXME: use HQL queries to increase performance
415 Collection<FieldUnit> fieldUnits = new ArrayList<FieldUnit>();
416 SpecimenOrObservationBase derivedUnit = load(derivedUnitUuid);
417 if(derivedUnit instanceof DerivedUnit){
418 getFieldUnits((DerivedUnit) derivedUnit, fieldUnits);
419 }
420 return fieldUnits;
421 }
422
423
424 /**
425 * @param original
426 * @param fieldUnits
427 */
428 private void getFieldUnits(DerivedUnit derivedUnit, Collection<FieldUnit> fieldUnits) {
429 Set<SpecimenOrObservationBase> originals = derivedUnit.getOriginals();
430 if(originals!=null && !originals.isEmpty()){
431 for(SpecimenOrObservationBase<?> original:originals){
432 if(original instanceof FieldUnit){
433 fieldUnits.add((FieldUnit) original);
434 }
435 else if(original instanceof DerivedUnit){
436 getFieldUnits((DerivedUnit) original, fieldUnits);
437 }
438 }
439 }
440 }
441
442 /* (non-Javadoc)
443 * @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)
444 */
445 @Override
446 public boolean moveSequence(DnaSample from, DnaSample to, Sequence sequence) {
447 //reload specimens to avoid session conflicts
448 from = (DnaSample) load(from.getUuid());
449 to = (DnaSample) load(to.getUuid());
450 sequence = sequenceService.load(sequence.getUuid());
451
452 if(from==null || to==null || sequence==null){
453 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" +
454 "Operation was move "+sequence+ " from "+from+" to "+to);
455 }
456 from.removeSequence(sequence);
457 saveOrUpdate(from);
458 to.addSequence(sequence);
459 saveOrUpdate(to);
460 return true;
461 }
462
463 /* (non-Javadoc)
464 * @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)
465 */
466 @Override
467 public boolean moveDerivate(SpecimenOrObservationBase<?> from, SpecimenOrObservationBase<?> to, DerivedUnit derivate) {
468 //reload specimens to avoid session conflicts
469 from = load(from.getUuid());
470 to = load(to.getUuid());
471 derivate = (DerivedUnit) load(derivate.getUuid());
472
473 if(from==null || to==null || derivate==null){
474 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" +
475 "Operation was move "+derivate+ " from "+from+" to "+to);
476 }
477
478 SpecimenOrObservationType derivateType = derivate.getRecordBasis();
479 SpecimenOrObservationType toType = to.getRecordBasis();
480 //check if type is a sub derivate type
481 if(toType==SpecimenOrObservationType.FieldUnit //moving to FieldUnit always works
482 || (derivateType.isKindOf(toType) && toType!=derivateType)){ //moving only to parent derivate type
483 //remove derivation event from parent specimen of dragged object
484 DerivationEvent eventToRemove = null;
485 for(DerivationEvent event:from.getDerivationEvents()){
486 if(event.getDerivatives().contains(derivate)){
487 eventToRemove = event;
488 break;
489 }
490 }
491 from.removeDerivationEvent(eventToRemove);
492 saveOrUpdate(from);
493 //add new derivation event to target
494 to.addDerivationEvent(DerivationEvent.NewSimpleInstance(to, derivate, eventToRemove==null?null:eventToRemove.getType()));
495 saveOrUpdate(to);
496 return true;
497 }
498 return false;
499 }
500
501 }