Project

General

Profile

Download (74.2 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
 * Copyright (C) 2007 EDIT
3
 * European Distributed Institute of Taxonomy
4
 * http://www.e-taxonomy.eu
5
 *
6
 * The contents of this file are subject to the Mozilla Public License Version 1.1
7
 * See LICENSE.TXT at the top of this package for the full license terms.
8
 */
9
package eu.etaxonomy.cdm.api.service;
10

    
11
import java.io.IOException;
12
import java.util.ArrayList;
13
import java.util.Arrays;
14
import java.util.Collection;
15
import java.util.Collections;
16
import java.util.Comparator;
17
import java.util.EnumSet;
18
import java.util.HashMap;
19
import java.util.HashSet;
20
import java.util.Iterator;
21
import java.util.List;
22
import java.util.Map;
23
import java.util.Set;
24
import java.util.UUID;
25

    
26
import org.apache.log4j.Logger;
27
import org.apache.lucene.queryparser.classic.ParseException;
28
import org.apache.lucene.search.BooleanClause.Occur;
29
import org.apache.lucene.search.BooleanQuery.Builder;
30
import org.apache.lucene.search.SortField;
31
import org.apache.lucene.search.grouping.TopGroups;
32
import org.apache.lucene.util.BytesRef;
33
import org.hibernate.TransientObjectException;
34
import org.hibernate.search.spatial.impl.Rectangle;
35
import org.springframework.beans.factory.annotation.Autowired;
36
import org.springframework.dao.DataRetrievalFailureException;
37
import org.springframework.stereotype.Service;
38
import org.springframework.transaction.annotation.Transactional;
39

    
40
import eu.etaxonomy.cdm.api.facade.DerivedUnitFacade;
41
import eu.etaxonomy.cdm.api.facade.DerivedUnitFacadeConfigurator;
42
import eu.etaxonomy.cdm.api.facade.DerivedUnitFacadeNotSupportedException;
43
import eu.etaxonomy.cdm.api.service.UpdateResult.Status;
44
import eu.etaxonomy.cdm.api.service.config.DeleteConfiguratorBase;
45
import eu.etaxonomy.cdm.api.service.config.FindOccurrencesConfigurator;
46
import eu.etaxonomy.cdm.api.service.config.IIdentifiableEntityServiceConfigurator;
47
import eu.etaxonomy.cdm.api.service.config.SpecimenDeleteConfigurator;
48
import eu.etaxonomy.cdm.api.service.dto.DNASampleDTO;
49
import eu.etaxonomy.cdm.api.service.dto.DerivedUnitDTO;
50
import eu.etaxonomy.cdm.api.service.dto.FieldUnitDTO;
51
import eu.etaxonomy.cdm.api.service.dto.SpecimenOrObservationBaseDTO;
52
import eu.etaxonomy.cdm.api.service.dto.SpecimenOrObservationDTOFactory;
53
import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;
54
import eu.etaxonomy.cdm.api.service.molecular.ISequenceService;
55
import eu.etaxonomy.cdm.api.service.pager.Pager;
56
import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
57
import eu.etaxonomy.cdm.api.service.search.ILuceneIndexToolProvider;
58
import eu.etaxonomy.cdm.api.service.search.ISearchResultBuilder;
59
import eu.etaxonomy.cdm.api.service.search.LuceneParseException;
60
import eu.etaxonomy.cdm.api.service.search.LuceneSearch;
61
import eu.etaxonomy.cdm.api.service.search.QueryFactory;
62
import eu.etaxonomy.cdm.api.service.search.SearchResult;
63
import eu.etaxonomy.cdm.api.service.search.SearchResultBuilder;
64
import eu.etaxonomy.cdm.api.service.util.TaxonRelationshipEdge;
65
import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
66
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
67
import eu.etaxonomy.cdm.model.CdmBaseType;
68
import eu.etaxonomy.cdm.model.common.CdmBase;
69
import eu.etaxonomy.cdm.model.common.Language;
70
import eu.etaxonomy.cdm.model.description.DescriptionBase;
71
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
72
import eu.etaxonomy.cdm.model.description.IndividualsAssociation;
73
import eu.etaxonomy.cdm.model.description.SpecimenDescription;
74
import eu.etaxonomy.cdm.model.description.TaxonDescription;
75
import eu.etaxonomy.cdm.model.location.Point;
76
import eu.etaxonomy.cdm.model.media.Media;
77
import eu.etaxonomy.cdm.model.molecular.AmplificationResult;
78
import eu.etaxonomy.cdm.model.molecular.DnaSample;
79
import eu.etaxonomy.cdm.model.molecular.Sequence;
80
import eu.etaxonomy.cdm.model.molecular.SingleRead;
81
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
82
import eu.etaxonomy.cdm.model.name.TaxonName;
83
import eu.etaxonomy.cdm.model.occurrence.DerivationEvent;
84
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
85
import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
86
import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
87
import eu.etaxonomy.cdm.model.occurrence.GatheringEvent;
88
import eu.etaxonomy.cdm.model.occurrence.MediaSpecimen;
89
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
90
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;
91
import eu.etaxonomy.cdm.model.taxon.Taxon;
92
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
93
import eu.etaxonomy.cdm.persistence.dao.initializer.AbstractBeanInitializer;
94
import eu.etaxonomy.cdm.persistence.dao.occurrence.IOccurrenceDao;
95
import eu.etaxonomy.cdm.persistence.dto.SpecimenNodeWrapper;
96
import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
97
import eu.etaxonomy.cdm.persistence.query.AssignmentStatus;
98
import eu.etaxonomy.cdm.persistence.query.OrderHint;
99
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
100

    
101
/**
102
 * @author a.babadshanjan
103
 * @since 01.09.2008
104
 */
105
@Service
106
@Transactional(readOnly = true)
107
public class OccurrenceServiceImpl
108
        extends IdentifiableServiceBase<SpecimenOrObservationBase, IOccurrenceDao>
109
        implements IOccurrenceService {
110

    
111
    static private final Logger logger = Logger.getLogger(OccurrenceServiceImpl.class);
112

    
113
    @Autowired
114
    private IDescriptionService descriptionService;
115

    
116
    @Autowired
117
    private INameService nameService;
118

    
119
    @Autowired
120
    private IEventBaseService eventService;
121

    
122
    @Autowired
123
    private ITaxonService taxonService;
124

    
125
    @Autowired
126
    private ISequenceService sequenceService;
127

    
128
    @Autowired
129
    private AbstractBeanInitializer<?> beanInitializer;
130

    
131
    @Autowired
132
    private ILuceneIndexToolProvider luceneIndexToolProvider;
133

    
134
    public OccurrenceServiceImpl() {
135
        logger.debug("Load OccurrenceService Bean");
136
    }
137

    
138

    
139
    @Override
140
    @Transactional(readOnly = false)
141
    public UpdateResult updateCaches(Class<? extends SpecimenOrObservationBase> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<SpecimenOrObservationBase> cacheStrategy, IProgressMonitor monitor) {
142
        if (clazz == null) {
143
            clazz = SpecimenOrObservationBase.class;
144
        }
145
        return super.updateCachesImpl(clazz, stepSize, cacheStrategy, monitor);
146
    }
147

    
148
    @Override
149
    @Autowired
150
    protected void setDao(IOccurrenceDao dao) {
151
        this.dao = dao;
152
    }
153

    
154
    @Override
155
    public Pager<DerivationEvent> getDerivationEvents(SpecimenOrObservationBase occurence, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
156
        long numberOfResults = dao.countDerivationEvents(occurence);
157

    
158
        List<DerivationEvent> results = new ArrayList<>();
159
        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
160
            results = dao.getDerivationEvents(occurence, pageSize, pageNumber, propertyPaths);
161
        }
162

    
163
        return new DefaultPagerImpl<DerivationEvent>(pageNumber, numberOfResults, pageSize, results);
164
    }
165

    
166
    @Override
167
    public long countDeterminations(SpecimenOrObservationBase occurence, TaxonBase taxonbase) {
168
        return dao.countDeterminations(occurence, taxonbase);
169
    }
170

    
171
    @Override
172
    public Pager<DeterminationEvent> getDeterminations(SpecimenOrObservationBase occurrence, TaxonBase taxonBase,
173
            Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
174
        long numberOfResults = dao.countDeterminations(occurrence, taxonBase);
175

    
176
        List<DeterminationEvent> results = new ArrayList<>();
177
        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
178
            results = dao.getDeterminations(occurrence, taxonBase, pageSize, pageNumber, propertyPaths);
179
        }
180

    
181
        return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
182
    }
183

    
184
    @Override
185
    public Pager<Media> getMedia(SpecimenOrObservationBase occurence, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
186
        long numberOfResults = dao.countMedia(occurence);
187

    
188
        List<Media> results = new ArrayList<>();
189
        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
190
            results = dao.getMedia(occurence, pageSize, pageNumber, propertyPaths);
191
        }
192

    
193
        return new DefaultPagerImpl<Media>(pageNumber, numberOfResults, pageSize, results);
194
    }
195

    
196
    @Override
197
    public Pager<Media> getMediainHierarchy(SpecimenOrObservationBase rootOccurence, Integer pageSize,
198
            Integer pageNumber, List<String> propertyPaths) {
199
        List<Media> media = new ArrayList<>();
200
        //media specimens
201
        if(rootOccurence.isInstanceOf(MediaSpecimen.class)){
202
            MediaSpecimen mediaSpecimen = HibernateProxyHelper.deproxy(rootOccurence, MediaSpecimen.class);
203
            media.add(mediaSpecimen.getMediaSpecimen());
204
        }
205
        // pherograms & gelPhotos
206
        if (rootOccurence.isInstanceOf(DnaSample.class)) {
207
            DnaSample dnaSample = CdmBase.deproxy(rootOccurence, DnaSample.class);
208
            Set<Sequence> sequences = dnaSample.getSequences();
209
            //we do show only those gelPhotos which lead to a consensus sequence
210
            for (Sequence sequence : sequences) {
211
                Set<Media> dnaRelatedMedia = new HashSet<>();
212
                for (SingleRead singleRead : sequence.getSingleReads()){
213
                    AmplificationResult amplification = singleRead.getAmplificationResult();
214
                    dnaRelatedMedia.add(amplification.getGelPhoto());
215
                    dnaRelatedMedia.add(singleRead.getPherogram());
216
                    dnaRelatedMedia.remove(null);
217
                }
218
                media.addAll(dnaRelatedMedia);
219
            }
220
        }
221
        if(rootOccurence.isInstanceOf(DerivedUnit.class)){
222
            DerivedUnit derivedUnit = HibernateProxyHelper.deproxy(rootOccurence, DerivedUnit.class);
223
            for (DerivationEvent derivationEvent : derivedUnit.getDerivationEvents()) {
224
                for (DerivedUnit childDerivative : derivationEvent.getDerivatives()) {
225
                    media.addAll(getMediainHierarchy(childDerivative, pageSize, pageNumber, propertyPaths).getRecords());
226
                }
227
            }
228
        }
229
        return new DefaultPagerImpl<Media>(pageNumber, Long.valueOf(media.size()), pageSize, media);
230
    }
231

    
232
    @Override
233
    public Pager<SpecimenOrObservationBase> list(Class<? extends SpecimenOrObservationBase> type, TaxonName determinedAs,
234
            Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
235
        long numberOfResults = dao.count(type, determinedAs);
236
        @SuppressWarnings("rawtypes")
237
        List<SpecimenOrObservationBase> results = new ArrayList<>();
238
        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
239
            results = dao.list(type, determinedAs, pageSize, pageNumber, orderHints, propertyPaths);
240
        }
241
        return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
242
    }
243

    
244
    @Override
245
    public Pager<SpecimenOrObservationBase> list(Class<? extends SpecimenOrObservationBase> type, TaxonBase determinedAs,
246
            Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
247
        long numberOfResults = dao.count(type, determinedAs);
248
        @SuppressWarnings("rawtypes")
249
        List<SpecimenOrObservationBase> results = new ArrayList<>();
250
        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
251
            results = dao.list(type, determinedAs, pageSize, pageNumber, orderHints, propertyPaths);
252
        }
253
        return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
254
    }
255

    
256
    @Override
257
    public List<UuidAndTitleCache<DerivedUnit>> getDerivedUnitUuidAndTitleCache(Integer limit, String pattern) {
258
        return dao.getDerivedUnitUuidAndTitleCache(limit, pattern);
259
    }
260

    
261
    @Override
262
    public List<UuidAndTitleCache<FieldUnit>> getFieldUnitUuidAndTitleCache() {
263
        return dao.getFieldUnitUuidAndTitleCache();
264
    }
265

    
266
    @Override
267
    public DerivedUnitFacade getDerivedUnitFacade(DerivedUnit derivedUnit, List<String> derivedUnitFacadeInitStrategy) throws DerivedUnitFacadeNotSupportedException {
268
        derivedUnit = (DerivedUnit) dao.load(derivedUnit.getUuid(), null);
269
        DerivedUnitFacadeConfigurator config = DerivedUnitFacadeConfigurator.NewInstance();
270
        config.setThrowExceptionForNonSpecimenPreservationMethodRequest(false);
271
        DerivedUnitFacade derivedUnitFacade = DerivedUnitFacade.NewInstance(derivedUnit, config);
272
        beanInitializer.initialize(derivedUnitFacade, derivedUnitFacadeInitStrategy);
273
        return derivedUnitFacade;
274
    }
275

    
276
    @Override
277
    public List<DerivedUnitFacade> listDerivedUnitFacades(
278
            DescriptionBase description, List<String> derivedUnitFacadeInitStrategy) {
279

    
280
        List<DerivedUnitFacade> derivedUnitFacadeList = new ArrayList<>();
281
        IndividualsAssociation tempIndividualsAssociation;
282
        SpecimenOrObservationBase tempSpecimenOrObservationBase;
283
        List<IndividualsAssociation> elements = descriptionService.listDescriptionElements(description, null, IndividualsAssociation.class, null, 0, Arrays.asList(new String []{"associatedSpecimenOrObservation"}));
284
        for (IndividualsAssociation element : elements) {
285
            tempIndividualsAssociation = HibernateProxyHelper.deproxy(element, IndividualsAssociation.class);
286
            if (tempIndividualsAssociation.getAssociatedSpecimenOrObservation() != null) {
287
                tempSpecimenOrObservationBase = HibernateProxyHelper.deproxy(tempIndividualsAssociation.getAssociatedSpecimenOrObservation(), SpecimenOrObservationBase.class);
288
                if (tempSpecimenOrObservationBase.isInstanceOf(DerivedUnit.class)) {
289
                    try {
290
                        derivedUnitFacadeList.add(DerivedUnitFacade.NewInstance(HibernateProxyHelper.deproxy(tempSpecimenOrObservationBase, DerivedUnit.class)));
291
                    } catch (DerivedUnitFacadeNotSupportedException e) {
292
                        logger.warn(tempIndividualsAssociation.getAssociatedSpecimenOrObservation().getTitleCache() + " : " + e.getMessage());
293
                    }
294
                }
295
            }
296
        }
297

    
298
        beanInitializer.initializeAll(derivedUnitFacadeList, derivedUnitFacadeInitStrategy);
299

    
300
        return derivedUnitFacadeList;
301
    }
302

    
303

    
304
    @Override
305
    public <T extends SpecimenOrObservationBase> List<T> listByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,
306
            Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
307

    
308
        return pageByAssociatedTaxon(type, includeRelationships, associatedTaxon, maxDepth, pageSize, pageNumber, orderHints, propertyPaths).getRecords();
309
    }
310

    
311
    @Override
312
    public Collection<SpecimenNodeWrapper> listUuidAndTitleCacheByAssociatedTaxon(List<UUID> taxonNodeUuids,
313
            Integer limit, Integer start) {
314
        return dao.listUuidAndTitleCacheByAssociatedTaxon(taxonNodeUuids, limit, start);
315
        }
316

    
317
    @Override
318
    public Collection<SpecimenOrObservationBase> listFieldUnitsByAssociatedTaxon(Taxon associatedTaxon, List<OrderHint> orderHints, List<String> propertyPaths) {
319
        return pageFieldUnitsByAssociatedTaxon(null, associatedTaxon, null, null, null, null, propertyPaths).getRecords();
320
    }
321

    
322
    @Override
323
    public Pager<SpecimenOrObservationBase> pageFieldUnitsByAssociatedTaxon(Set<TaxonRelationshipEdge> includeRelationships,
324
            Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
325
            List<String> propertyPaths) {
326

    
327
        if (!getSession().contains(associatedTaxon)) {
328
            associatedTaxon = (Taxon) taxonService.load(associatedTaxon.getUuid());
329
        }
330

    
331
        // gather the IDs of all relevant field units
332
        Set<UUID> fieldUnitUuids = new HashSet<>();
333
        List<SpecimenOrObservationBase> records = listByAssociatedTaxon(null, includeRelationships, associatedTaxon, maxDepth, null, null, orderHints, propertyPaths);
334
        for (SpecimenOrObservationBase<?> specimen : records) {
335
            for (FieldUnit fieldUnit : findFieldUnits(specimen.getUuid(), null)) {
336
                fieldUnitUuids.add(fieldUnit.getUuid());
337
            }
338
        }
339
        //dao.list() does the paging of the field units. Passing the field units directly to the Pager would not work
340
        List<SpecimenOrObservationBase> fieldUnits = dao.list(fieldUnitUuids, pageSize, pageNumber, orderHints, propertyPaths);
341
        return new DefaultPagerImpl<>(pageNumber, fieldUnitUuids.size(), pageSize, fieldUnits);
342
    }
343

    
344
    @Override
345
    public FieldUnitDTO assembleFieldUnitDTO(FieldUnit fieldUnit) {
346

    
347
        if (!getSession().contains(fieldUnit)) {
348
            fieldUnit = (FieldUnit) load(fieldUnit.getUuid());
349
        }
350
        // FIXME the filter for SpecimenOrObservationType.PreservedSpecimen has been preserved from the former implementation (see commit 07e3f63c7d  and older)
351
        // it is questionable if this filter makes sense for all use cases or if it is only a sensible default for the
352
        // compressed specimen table in the cdm-dataportal (see #6816, #6870)
353
        FieldUnitDTO fieldUnitDTO = FieldUnitDTO.fromEntity(fieldUnit, EnumSet.of(SpecimenOrObservationType.PreservedSpecimen));
354
        return fieldUnitDTO;
355
    }
356

    
357

    
358
    @Override
359
    @Deprecated
360
    public String getMostSignificantIdentifier(DerivedUnit derivedUnit) {
361
        return derivedUnit.getMostSignificantIdentifier();
362
    }
363

    
364
    @Override
365
    @Transactional
366
    public DerivedUnitDTO assembleDerivedUnitDTO(DerivedUnit derivedUnit) {
367

    
368
        if (!getSession().contains(derivedUnit)) {
369
            derivedUnit = (DerivedUnit) load(derivedUnit.getUuid());
370
        }
371
        Collection<IndividualsAssociation> individualsAssociations = listIndividualsAssociations(derivedUnit, null, null, null, null);
372
        DerivedUnitDTO derivedUnitDTO = DerivedUnitDTO.fromEntity(derivedUnit, individualsAssociations);
373
        return derivedUnitDTO;
374
    }
375

    
376
    private Set<DerivedUnitDTO> getDerivedUnitDTOsFor(SpecimenOrObservationBaseDTO specimenDto, DerivedUnit specimen, HashMap<UUID, SpecimenOrObservationBaseDTO> alreadyCollectedSpecimen) {
377
        Set<DerivedUnitDTO> derivedUnits = new HashSet<>();
378

    
379
        for (DerivationEvent derivationEvent : specimen.getDerivationEvents()) {
380
            for (DerivedUnit derivative : derivationEvent.getDerivatives()) {
381
                if (!alreadyCollectedSpecimen.containsKey(specimenDto.getUuid())){
382
                    DerivedUnitDTO dto;
383
                    if (derivative instanceof DnaSample) {
384
                        dto = new DNASampleDTO(derivative);
385
                    } else {
386
                        dto = new DerivedUnitDTO(derivative);
387
                    }
388
                    alreadyCollectedSpecimen.put(dto.getUuid(), dto);
389
                    dto.addAllDerivatives(getDerivedUnitDTOsFor(dto, derivative, alreadyCollectedSpecimen));
390
                    derivedUnits.add(dto);
391
                }
392
            }
393
        }
394
        return derivedUnits;
395
    }
396

    
397
//    private Set<DerivateDTO> getDerivedUnitDTOsFor(DerivateDTO specimenDto, DerivedUnit specimen, HashMap<UUID, DerivateDTO> alreadyCollectedSpecimen) {
398
//        Set<DerivateDTO> derivedUnits = new HashSet<>();
399
////        load
400
//        for (DerivationEvent derivationEvent : specimen.getDerivationEvents()) {
401
//            for (DerivedUnit derivative : derivationEvent.getDerivatives()) {
402
//                if (!alreadyCollectedSpecimen.containsKey(specimenDto.getUuid())){
403
//                    PreservedSpecimenDTO dto;
404
//                    if (derivative instanceof DnaSample){
405
//                        dto = DNASampleDTO.newInstance(derivative);
406
//                    }else{
407
//                        dto = PreservedSpecimenDTO.newInstance(derivative);
408
//                    }
409
//                    alreadyCollectedSpecimen.put(dto.getUuid(), dto);
410
//                    dto.addAllDerivates(getDerivedUnitDTOsFor(dto, derivative, alreadyCollectedSpecimen));
411
//                    derivedUnits.add(dto);
412
//                }
413
//            }
414
//        }
415
//        return derivedUnits;
416
//    }
417

    
418

    
419
    @SuppressWarnings("unchecked")
420
    @Override
421
    public <T extends SpecimenOrObservationBase> Pager<T> pageByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includedRelationships,
422
            Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
423

    
424
        Set<Taxon> taxa = new HashSet<>();
425
        Set<Integer> occurrenceIds = new HashSet<>();
426
        List<T> occurrences = new ArrayList<>();
427
        boolean includeUnpublished = INCLUDE_UNPUBLISHED;
428

    
429
        // Integer limit = PagerUtils.limitFor(pageSize);
430
        // Integer start = PagerUtils.startFor(pageSize, pageNumber);
431

    
432
        if (!getSession().contains(associatedTaxon)) {
433
            associatedTaxon = (Taxon) taxonService.load(associatedTaxon.getUuid());
434
        }
435

    
436
        if (includedRelationships != null) {
437
            taxa = taxonService.listRelatedTaxa(associatedTaxon, includedRelationships, maxDepth, includeUnpublished, null, null, propertyPaths);
438
        }
439

    
440
        taxa.add(associatedTaxon);
441

    
442
        for (Taxon taxon : taxa) {
443
            List<T> perTaxonOccurrences = dao.listByAssociatedTaxon(type, taxon, null, null, orderHints, propertyPaths);
444
            for (SpecimenOrObservationBase<?> o : perTaxonOccurrences) {
445
                occurrenceIds.add(o.getId());
446
            }
447
        }
448
        occurrences = (List<T>) dao.loadList(occurrenceIds, null, propertyPaths);
449

    
450
        return new DefaultPagerImpl<T>(pageNumber, Long.valueOf(occurrences.size()), pageSize, occurrences);
451

    
452
    }
453

    
454
    @Override
455
    public <T extends SpecimenOrObservationBase> Pager<T> pageByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,
456
            String taxonUUID, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
457

    
458
        UUID uuid = UUID.fromString(taxonUUID);
459
        Taxon tax = (Taxon) taxonService.load(uuid);
460
        // TODO REMOVE NULL STATEMENT
461
        type = null;
462
        return pageByAssociatedTaxon(type, includeRelationships, tax, maxDepth, pageSize, pageNumber, orderHints, propertyPaths);
463

    
464
    }
465

    
466
    @Override
467
    @Transactional
468
    public List<FieldUnitDTO> findFieldUnitDTOByAssociatedTaxon(Set<TaxonRelationshipEdge> includedRelationships,
469
            UUID associatedTaxonUuid, List<String> propertyPaths) {
470

    
471
        Set<Taxon> taxa = new HashSet<>();
472
        Set<Integer> occurrenceIds = new HashSet<>();
473
        Set<FieldUnitDTO> fieldUnitDTOs = new HashSet<>();
474
        HashMap<UUID, SpecimenOrObservationBaseDTO> alreadyCollectedSpecimen = new HashMap<>();
475
        List<SpecimenOrObservationBase> occurrences = new ArrayList<>();
476
        boolean includeUnpublished = INCLUDE_UNPUBLISHED;
477

    
478
        // Integer limit = PagerUtils.limitFor(pageSize);
479
        // Integer start = PagerUtils.startFor(pageSize, pageNumber);
480

    
481
        Taxon associatedTaxon = (Taxon) taxonService.load(associatedTaxonUuid);
482

    
483
        if (includedRelationships != null) {
484
            taxa = taxonService.listRelatedTaxa(associatedTaxon, includedRelationships, null, includeUnpublished, null, null, null);
485
        }
486

    
487
        taxa.add(associatedTaxon);
488

    
489
        for (Taxon taxon : taxa) {
490
            // TODO there might be a good potential to speed up the whole processing by collecting all entities first
491
            // and to create the DTOs in a second step
492
            List<SpecimenOrObservationBase> perTaxonOccurrences = dao.listByAssociatedTaxon(null,taxon, null, null, null, propertyPaths);
493
            for (SpecimenOrObservationBase<?> o : perTaxonOccurrences) {
494
                if (o.isInstanceOf(DerivedUnit.class)){
495
                    DerivedUnit derivedUnit;
496
                    DerivedUnitDTO derivedUnitDTO = (DerivedUnitDTO) SpecimenOrObservationDTOFactory.fromEntity(o);
497
                    if (o.isInstanceOf(DnaSample.class)) {
498
                         derivedUnit = HibernateProxyHelper.deproxy(o, DnaSample.class);
499
                        derivedUnitDTO = new DNASampleDTO(derivedUnit);
500
                    } else {
501
                        derivedUnit = HibernateProxyHelper.deproxy(o, DerivedUnit.class);
502
                        derivedUnitDTO = new DerivedUnitDTO(derivedUnit);
503
                    }
504
                    if (alreadyCollectedSpecimen.get(derivedUnitDTO.getUuid()) == null){
505
                        alreadyCollectedSpecimen.put(derivedUnitDTO.getUuid(), derivedUnitDTO);
506
                    }
507
                    derivedUnitDTO.addAllDerivatives(getDerivedUnitDTOsFor(derivedUnitDTO, derivedUnit, alreadyCollectedSpecimen));
508
                    fieldUnitDTOs.addAll(findFieldUnitDTO(derivedUnitDTO, alreadyCollectedSpecimen));
509
                } else {
510
                    // FIXME FieldUnits are not yet handled here !!!
511
                }
512
            }
513
        }
514

    
515
        List<FieldUnitDTO> orderdDTOs = new ArrayList<>(fieldUnitDTOs);
516
        // TODO order dtos by date can only be done by string comparison
517
        // the FieldUnitDTO.date needs to be a Partial object for sensible ordering
518
        Collections.sort(orderdDTOs, new Comparator<FieldUnitDTO>() {
519

    
520
            @Override
521
            public int compare(FieldUnitDTO o1, FieldUnitDTO o2) {
522
                if(o1.getDate() == null && o2.getDate() == null) {
523
                    return 0;
524
                }
525
                if(o1.getDate() != null && o2.getDate() == null) {
526
                    return 1;
527
                }
528
                if(o1.getDate() == null && o2.getDate() != null) {
529
                    return -1;
530
                }
531
                return o1.getDate().compareTo(o2.getDate());
532
            }
533
        });
534

    
535
        return orderdDTOs;
536

    
537
    }
538

    
539

    
540

    
541
    @Override
542
    @Transactional
543
    public  FieldUnitDTO findByAccessionNumber(String accessionNumberString, List<OrderHint> orderHints)  {
544

    
545
        DnaSample dnaSample = dao.findByGeneticAccessionNumber(accessionNumberString, null);
546
        DerivedUnitDTO derivedUnitDTO;
547
        HashMap<UUID, SpecimenOrObservationBaseDTO> alreadyCollectedSpecimen = new HashMap<>();
548

    
549
        if (dnaSample != null){
550
            derivedUnitDTO = new DNASampleDTO(dnaSample);
551
            alreadyCollectedSpecimen.put(derivedUnitDTO.getUuid(), derivedUnitDTO);
552
            derivedUnitDTO.addAllDerivatives(getDerivedUnitDTOsFor(derivedUnitDTO, dnaSample, alreadyCollectedSpecimen));
553
            Collection<FieldUnitDTO> fieldUnitDTOs = this.findFieldUnitDTO(derivedUnitDTO, alreadyCollectedSpecimen);
554
            // FIXME change return type to Collection<FieldUnitDTO>
555
            if(fieldUnitDTOs.isEmpty()) {
556
                return null;
557
            } else {
558
               return fieldUnitDTOs.iterator().next();
559
            }
560
        }
561
        return null;
562

    
563
    }
564

    
565
    @Override
566
    public Pager<SearchResult<SpecimenOrObservationBase>> findByFullText(
567
            Class<? extends SpecimenOrObservationBase> clazz, String queryString, Rectangle boundingBox, List<Language> languages,
568
            boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
569
            List<String> propertyPaths) throws IOException, LuceneParseException {
570

    
571
        LuceneSearch luceneSearch = prepareByFullTextSearch(clazz, queryString, boundingBox, languages, highlightFragments);
572

    
573
        // --- execute search
574
        TopGroups<BytesRef> topDocsResultSet;
575
        try {
576
            topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
577
        } catch (ParseException e) {
578
            LuceneParseException parseException = new LuceneParseException(e.getMessage());
579
            parseException.setStackTrace(e.getStackTrace());
580
            throw parseException;
581
        }
582

    
583
        Map<CdmBaseType, String> idFieldMap = new HashMap<>();
584
        idFieldMap.put(CdmBaseType.SPECIMEN_OR_OBSERVATIONBASE, "id");
585

    
586
        // --- initialize taxa, highlight matches ....
587
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
588
        @SuppressWarnings("rawtypes")
589
        List<SearchResult<SpecimenOrObservationBase>> searchResults = searchResultBuilder.createResultSet(
590
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
591

    
592
        int totalHits = topDocsResultSet != null ? topDocsResultSet.totalGroupCount : 0;
593

    
594
        return new DefaultPagerImpl<>(pageNumber, Long.valueOf(totalHits), pageSize, searchResults);
595

    
596
    }
597

    
598
    private LuceneSearch prepareByFullTextSearch(Class<? extends SpecimenOrObservationBase> clazz, String queryString, Rectangle bbox,
599
            List<Language> languages, boolean highlightFragments) {
600

    
601
        Builder finalQueryBuilder = new Builder();
602
        Builder textQueryBuilder = new Builder();
603

    
604
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, FieldUnit.class);
605
        QueryFactory queryFactory = luceneIndexToolProvider.newQueryFactoryFor(FieldUnit.class);
606

    
607
        // --- criteria
608
        luceneSearch.setCdmTypRestriction(clazz);
609
        if (queryString != null) {
610
            textQueryBuilder.add(queryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
611
            finalQueryBuilder.add(textQueryBuilder.build(), Occur.MUST);
612
        }
613

    
614
        // --- spacial query
615
        if (bbox != null) {
616
            finalQueryBuilder.add(QueryFactory.buildSpatialQueryByRange(bbox, "gatheringEvent.exactLocation.point"), Occur.MUST);
617
        }
618

    
619
        luceneSearch.setQuery(finalQueryBuilder.build());
620

    
621
        // --- sorting
622
        SortField[] sortFields = new SortField[] { SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.Type.STRING, false) };
623
        luceneSearch.setSortFields(sortFields);
624

    
625
        if (highlightFragments) {
626
            luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray());
627
        }
628
        return luceneSearch;
629
    }
630

    
631

    
632
    @Override
633
    @Transactional(readOnly=true)
634
    public Collection<FieldUnit> findFieldUnits(UUID derivedUnitUuid, List<String> propertyPaths) {
635
        //It will search recursively over all {@link DerivationEvent}s and get the "originals" ({@link SpecimenOrObservationBase})
636
        //from which this DerivedUnit was derived until all FieldUnits are found.
637

    
638
        // FIXME: use HQL queries to increase performance
639

    
640
        SpecimenOrObservationBase<?> specimen = load(derivedUnitUuid, propertyPaths);
641
//        specimen = HibernateProxyHelper.deproxy(specimen, SpecimenOrObservationBase.class);
642
        Collection<FieldUnit> fieldUnits = new ArrayList<>();
643
        if (specimen == null){
644
            return null;
645
        }
646
        if (specimen.isInstanceOf(FieldUnit.class)) {
647
            fieldUnits.add(HibernateProxyHelper.deproxy(specimen, FieldUnit.class));
648
        }
649
        else if(specimen.isInstanceOf(DerivedUnit.class)){
650
            fieldUnits.addAll(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class).collectFieldUnits());
651
        }
652

    
653
        fieldUnits = beanInitializer.initializeAll(fieldUnits, propertyPaths);
654
        return fieldUnits;
655
    }
656

    
657

    
658
    /**
659
     * Recursively searches all {@link DerivationEvent}s to find all "originals" ({@link SpecimenOrObservationBase})
660
     * from which this DerivedUnit was derived until all FieldUnits are found.
661
     * <p>
662
     * <b>NOTE:</b> The recursive search still is a bit incomplete and may miss originals in the rare case where a
663
     * derivative has more than one original. (see https://dev.e-taxonomy.eu/redmine/issues/9253)
664
     *
665
     * @param derivedUnitDTO
666
     *  The DerivedUnitDTO to start the search from.
667
     * @param alreadyCollectedSpecimen
668
     *  A map to hold all originals that have been sees during the recursive walk.
669
     * @return
670
     *  The collection of all Field Units that are accessible from the derivative from where the search was started.
671
     */
672
    public Collection<FieldUnitDTO> findFieldUnitDTO(DerivedUnitDTO derivedUnitDTO, HashMap<UUID, SpecimenOrObservationBaseDTO> alreadyCollectedSpecimen) {
673
        HashMap<UUID, FieldUnitDTO> fieldUnitDTOs = new HashMap<>();
674
        _findFieldUnitDTO(derivedUnitDTO, fieldUnitDTOs, alreadyCollectedSpecimen);
675
        return fieldUnitDTOs.values();
676

    
677
    }
678

    
679
    /**
680
     * Method for recursive calls, must only be used by {@link #findFieldUnitDTO(DerivedUnitDTO, HashMap)}
681
     * <p>
682
     * It will search recursively over all {@link DerivationEvent}s and get the "originals" ({@link SpecimenOrObservationBase})
683
     * from which this DerivedUnit was derived until all FieldUnits are found.
684
     */
685
    private void _findFieldUnitDTO(DerivedUnitDTO derivedUnitDTO, Map<UUID, FieldUnitDTO> fieldUnits,
686
                HashMap<UUID, SpecimenOrObservationBaseDTO> alreadyCollectedSpecimen) {
687

    
688
        List<String> propertyPaths = new ArrayList<>();
689

    
690
        // add the supplied DTO the the alreadyCollectedSpecimen if not yet there
691
        if(!alreadyCollectedSpecimen.containsKey(derivedUnitDTO.getUuid())) {
692
            alreadyCollectedSpecimen.put(derivedUnitDTO.getUuid(), derivedUnitDTO);
693
        }
694

    
695
        // FIXME defining propertyPaths inside of transactional service method makes no sense. see also #9253
696
        propertyPaths.add("descriptions.elements.media.title");
697
        propertyPaths.add("kindOfUnit");
698
        propertyPaths.add("derivedFrom");
699

    
700
        List<SpecimenOrObservationBase> originals = dao.findOriginalsForDerivedUnit(derivedUnitDTO.getUuid(), propertyPaths);
701
        if (originals.size() > 0){
702
            if (originals.size() > 1){
703
                logger.warn("The derived unit with uuid " + derivedUnitDTO.getUuid() + "has more than one orginal");
704
            }
705
            // FIXME allow handling multiple originals
706
            SpecimenOrObservationBase<?> original = originals.get(0);
707

    
708
            if (alreadyCollectedSpecimen.containsKey(original.getUuid())){
709
                alreadyCollectedSpecimen.get(original.getUuid()).addDerivate(derivedUnitDTO);
710
    //            if ( alreadyCollectedSpecimen.get(specimen.getUuid()) instanceof FieldUnitDTO){
711
    //                ((FieldUnitDTO)alreadyCollectedSpecimen.get(specimen.getUuid())).getTaxonRelatedDerivedUnits().add(derivedUnitDTO.getUuid());
712
    //            }
713
            }else{
714
                if(!fieldUnits.containsKey(original.getUuid())){
715
                    // the direct derivatives of the field unit are added in the factory method, so it is guaranteed that
716
                    // the derivedUnitDTO is already contained.
717
                    SpecimenOrObservationBaseDTO originalDTO = SpecimenOrObservationDTOFactory.fromEntity(original);
718
                    if (originalDTO instanceof FieldUnitDTO){
719
                        fieldUnits.put(originalDTO.getUuid(), (FieldUnitDTO) originalDTO);
720
                    }else{
721
                        // must be a DerivedUnitDTO then
722
                        originalDTO.addDerivate(derivedUnitDTO);
723
                        _findFieldUnitDTO((DerivedUnitDTO) originalDTO, fieldUnits, alreadyCollectedSpecimen);
724
                    }
725
                }
726
            }
727
          //  }
728
    //        if (fieldUnitDto != null){
729
    //            fieldUnitDto.addTaxonRelatedDerivedUnits(derivedUnitDTO);
730
    //        }
731
        }
732

    
733
    }
734

    
735
    @Override
736
    @Transactional(readOnly=true)
737
    public FieldUnitDTO loadFieldUnitDTO(UUID derivedUnitUuid) {
738

    
739
        FieldUnitDTO fieldUnitDTO = null;
740
        DerivedUnitDTO derivedUnitDTO = null;
741

    
742
        Map<UUID, SpecimenOrObservationBaseDTO> cycleDetectionMap = new HashMap<>();
743
        SpecimenOrObservationBase derivative = dao.load(derivedUnitUuid);
744
        if(derivative != null){
745
            if (derivative instanceof FieldUnit){
746
                fieldUnitDTO = FieldUnitDTO.fromEntity((FieldUnit)derivative);
747
                return fieldUnitDTO;
748
            } else {
749
                // must be a DerivedUnit otherwise
750
                derivedUnitDTO = DerivedUnitDTO.fromEntity((DerivedUnit)derivative, null);
751
                while(true){
752

    
753
                    Set<SpecimenOrObservationBaseDTO> originals = originalDTOs(derivedUnitDTO.getUuid());
754

    
755
                    if(originals.isEmpty()){
756
                        break;
757
                    }
758
                    if (originals.size() > 1){
759
                        logger.debug("The derived unit with uuid " + derivedUnitUuid + "has more than one orginal, ignoring all but the first one.");
760
                    }
761

    
762
                    SpecimenOrObservationBaseDTO originalDTO = originals.iterator().next();
763

    
764
                    // cycle detection and handling
765
                    if(cycleDetectionMap.containsKey(originalDTO.getUuid())){
766
                        // cycle detected!!!
767
                        try {
768
                            throw new Exception();
769
                        } catch(Exception e){
770
                            logger.error("Cycle in derivate graph detected at DerivedUnit with uuid=" + originalDTO.getUuid() , e);
771
                        }
772
                        // to solve the situation for the output we remove the derivate from the more distant graph node
773
                        // by removing it from the derivatives of its original
774
                        // but let the derivate to be added to the original which is closer to the FieldUnit (below at originalDTO.addDerivate(derivedUnitDTO);)
775
                        for(SpecimenOrObservationBaseDTO seenOriginal: cycleDetectionMap.values()){
776
                            for(SpecimenOrObservationBaseDTO derivateDTO : seenOriginal.getDerivatives()){
777
                                if(derivateDTO.equals(originalDTO)){
778
                                    seenOriginal.getDerivatives().remove(originalDTO);
779
                                }
780
                            }
781
                        }
782

    
783
                    } else {
784
                        cycleDetectionMap.put(originalDTO.getUuid(), originalDTO);
785
                    }
786

    
787

    
788
                    if (originalDTO instanceof FieldUnitDTO){
789
                        fieldUnitDTO = (FieldUnitDTO)originalDTO;
790
                        if(derivedUnitDTO != null){
791
                            fieldUnitDTO.addDerivate(derivedUnitDTO);
792
                        }
793
                        break;
794
                    }else{
795
                        // So this must be a DerivedUnitDTO
796
                        if (derivedUnitDTO == null){
797
                            derivedUnitDTO = (DerivedUnitDTO)originalDTO;
798
                        } else {
799
                            originalDTO.addDerivate(derivedUnitDTO);
800
                            derivedUnitDTO = (DerivedUnitDTO)originalDTO;
801
                        }
802
                    }
803
                }
804
            }
805
        }
806
        return fieldUnitDTO;
807

    
808
    }
809

    
810
    /**
811
     * @param originalDTO
812
     * @return
813
     */
814
    private Set<SpecimenOrObservationBaseDTO> originalDTOs(UUID derivativeUuid) {
815

    
816
        Set<SpecimenOrObservationBaseDTO> dtos = new HashSet<>();
817
        List<SpecimenOrObservationBase> specimens = dao.findOriginalsForDerivedUnit(derivativeUuid, null);
818
        for(SpecimenOrObservationBase sob : specimens){
819
            if(sob instanceof FieldUnit) {
820
                dtos.add(FieldUnitDTO.fromEntity((FieldUnit)sob));
821
            } else {
822
                dtos.add(DerivedUnitDTO.fromEntity((DerivedUnit)sob, null));
823
            }
824
        }
825
        return dtos;
826
    }
827

    
828

    
829
    @Override
830
    @Transactional(readOnly = false)
831
    public UpdateResult moveSequence(DnaSample from, DnaSample to, Sequence sequence) {
832
        return moveSequence(from.getUuid(), to.getUuid(), sequence.getUuid());
833
    }
834

    
835
    @Override
836
    @Transactional(readOnly = false)
837
    public UpdateResult moveSequence(UUID fromUuid, UUID toUuid, UUID sequenceUuid) {
838
        // reload specimens to avoid session conflicts
839
        DnaSample from = (DnaSample) load(fromUuid);
840
        DnaSample to = (DnaSample) load(toUuid);
841
        Sequence sequence = sequenceService.load(sequenceUuid);
842

    
843
        if (from == null || to == null || sequence == null) {
844
            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" +
845
                    "Operation was move "+sequence+ " from "+from+" to "+to);
846
        }
847
        UpdateResult result = new UpdateResult();
848
        from.removeSequence(sequence);
849
        saveOrUpdate(from);
850
        to.addSequence(sequence);
851
        saveOrUpdate(to);
852
        result.setStatus(Status.OK);
853
        result.addUpdatedObject(from);
854
        result.addUpdatedObject(to);
855
        return result;
856
    }
857

    
858
    @Override
859
    @Transactional(readOnly = false)
860
    public boolean moveDerivate(SpecimenOrObservationBase<?> from, SpecimenOrObservationBase<?> to, DerivedUnit derivate) {
861
        return moveDerivate(from!=null?from.getUuid():null, to.getUuid(), derivate.getUuid()).isOk();
862
    }
863

    
864
    @Override
865
    @Transactional(readOnly = false)
866
    public UpdateResult moveDerivate(UUID specimenFromUuid, UUID specimenToUuid, UUID derivateUuid) {
867
        // reload specimens to avoid session conflicts
868
        SpecimenOrObservationBase<?> from = null;
869
        if(specimenFromUuid!=null){
870
            from = load(specimenFromUuid);
871
        }
872
        SpecimenOrObservationBase<?> to = load(specimenToUuid);
873
        DerivedUnit derivate = (DerivedUnit) load(derivateUuid);
874

    
875
        if ((specimenFromUuid!=null && from == null) || to == null || derivate == null) {
876
            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" +
877
                    "Operation was move "+derivate+ " from "+from+" to "+to);
878
        }
879
        UpdateResult result = new UpdateResult();
880
        SpecimenOrObservationType derivateType = derivate.getRecordBasis();
881
        SpecimenOrObservationType toType = to.getRecordBasis();
882
        // check if type is a sub derivate type
883
        if(toType==SpecimenOrObservationType.FieldUnit //moving to FieldUnit always works
884
                || derivateType==SpecimenOrObservationType.Media //moving media always works
885
                || (derivateType.isKindOf(toType) && toType!=derivateType)){ //moving only to parent derivate type
886
            if(from!=null){
887
                // remove derivation event from parent specimen of dragged object
888
                DerivationEvent eventToRemove = null;
889
                for (DerivationEvent event : from.getDerivationEvents()) {
890
                    if (event.getDerivatives().contains(derivate)) {
891
                        eventToRemove = event;
892
                        break;
893
                    }
894
                }
895
                from.removeDerivationEvent(eventToRemove);
896
                if(eventToRemove!=null){
897
                    // add new derivation event to target and copy the event parameters of the old one
898
                    DerivationEvent derivedFromNewOriginalEvent = DerivationEvent.NewSimpleInstance(to, derivate, null);
899
                    derivedFromNewOriginalEvent.setActor(eventToRemove.getActor());
900
                    derivedFromNewOriginalEvent.setDescription(eventToRemove.getDescription());
901
                    derivedFromNewOriginalEvent.setInstitution(eventToRemove.getInstitution());
902
                    derivedFromNewOriginalEvent.setTimeperiod(eventToRemove.getTimeperiod());
903
                    derivedFromNewOriginalEvent.setType(eventToRemove.getType());
904
                    to.addDerivationEvent(derivedFromNewOriginalEvent);
905
                    derivate.setDerivedFrom(derivedFromNewOriginalEvent);
906
                }
907
            }
908
            else{
909
                //derivative had no parent before so we use empty derivation event
910
                DerivationEvent derivedFromNewOriginalEvent = DerivationEvent.NewSimpleInstance(to, derivate, null);
911
                to.addDerivationEvent(derivedFromNewOriginalEvent);
912
                derivate.setDerivedFrom(derivedFromNewOriginalEvent);
913
            }
914

    
915
            if(from!=null){
916
                saveOrUpdate(from);
917
            }
918
            saveOrUpdate(to);
919
            result.setStatus(Status.OK);
920
            result.addUpdatedObject(from);
921
            result.addUpdatedObject(to);
922
        } else {
923
            result.setStatus(Status.ERROR);
924
        }
925
        return result;
926
    }
927

    
928
    @Override
929
    public DeleteResult isDeletable(UUID specimenUuid, DeleteConfiguratorBase config) {
930
        DeleteResult deleteResult = new DeleteResult();
931
        SpecimenOrObservationBase specimen = this.load(specimenUuid);
932
        SpecimenDeleteConfigurator specimenDeleteConfigurator = (SpecimenDeleteConfigurator) config;
933

    
934
        // check elements found by super method
935
        Set<CdmBase> relatedObjects = super.isDeletable(specimenUuid, config).getRelatedObjects();
936
        for (CdmBase cdmBase : relatedObjects) {
937
            // check for type designation
938
            if (cdmBase.isInstanceOf(SpecimenTypeDesignation.class) && !specimenDeleteConfigurator.isDeleteFromTypeDesignation()) {
939
                deleteResult.setAbort();
940
                deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation is a type specimen."));
941
                deleteResult.addRelatedObject(cdmBase);
942
                break;
943
            }
944
            // check for IndividualsAssociations
945
            else if (cdmBase.isInstanceOf(IndividualsAssociation.class) && !specimenDeleteConfigurator.isDeleteFromIndividualsAssociation()) {
946
                deleteResult.setAbort();
947
                deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation is still associated via IndividualsAssociations"));
948
                deleteResult.addRelatedObject(cdmBase);
949
                break;
950
            }
951
            // check for taxon description
952
            else if(cdmBase.isInstanceOf(TaxonDescription.class)
953
                    && HibernateProxyHelper.deproxy(cdmBase, TaxonDescription.class).getDescribedSpecimenOrObservation().equals(specimen)
954
                    && !specimenDeleteConfigurator.isDeleteFromDescription()){
955
                deleteResult.setAbort();
956
                deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation is still used as \"Described Specimen\" in a taxon description."));
957
                deleteResult.addRelatedObject(cdmBase);
958
                break;
959
            }
960
            // check for children and parents (derivation events)
961
            else if (cdmBase.isInstanceOf(DerivationEvent.class)) {
962
                DerivationEvent derivationEvent = HibernateProxyHelper.deproxy(cdmBase, DerivationEvent.class);
963
                // check if derivation event is empty
964
                if (!derivationEvent.getDerivatives().isEmpty() && derivationEvent.getOriginals().contains(specimen)) {
965
                    // if derivationEvent is the childEvent and contains derivations
966
//                    if (derivationEvent.getDerivatives().contains(specimen)) {
967
//                        //if it is the parent event the specimen is still deletable
968
//                        continue;
969
//                    }
970
                    if(!specimenDeleteConfigurator.isDeleteChildren()){
971
                        //if children should not be deleted then it is undeletable
972
                        deleteResult.setAbort();
973
                        deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation still has child derivatives."));
974
                        deleteResult.addRelatedObject(cdmBase);
975
                        break;
976
                    }
977
                    else{
978
                        // check all children if they can be deleted
979
                        Set<DerivedUnit> derivatives = derivationEvent.getDerivatives();
980
                        DeleteResult childResult = new DeleteResult();
981
                        for (DerivedUnit derivedUnit : derivatives) {
982
                            childResult.includeResult(isDeletable(derivedUnit.getUuid(), specimenDeleteConfigurator));
983
                        }
984
                        if (!childResult.isOk()) {
985
                            deleteResult.setAbort();
986
                            deleteResult.includeResult(childResult);
987
                            deleteResult.addRelatedObject(cdmBase);
988
                            break;
989
                        }
990
                    }
991
                }
992
            }
993
            // check for amplification
994
            else if (cdmBase.isInstanceOf(AmplificationResult.class)
995
                    && !specimenDeleteConfigurator.isDeleteMolecularData()
996
                    && !specimenDeleteConfigurator.isDeleteChildren()) {
997
                deleteResult.setAbort();
998
                deleteResult.addException(new ReferencedObjectUndeletableException("DnaSample is used in amplification results."));
999
                deleteResult.addRelatedObject(cdmBase);
1000
                break;
1001
            }
1002
            // check for sequence
1003
            else if (cdmBase.isInstanceOf(Sequence.class)
1004
                    && !specimenDeleteConfigurator.isDeleteMolecularData()
1005
                    && !specimenDeleteConfigurator.isDeleteChildren()) {
1006
                deleteResult.setAbort();
1007
                deleteResult.addException(new ReferencedObjectUndeletableException("DnaSample is used in sequences."));
1008
                deleteResult.addRelatedObject(cdmBase);
1009
                break;
1010
            }
1011
        }
1012
        if (deleteResult.isOk()) {
1013
            //add all related object if deletion is OK so they can be handled by the delete() method
1014
            deleteResult.addRelatedObjects(relatedObjects);
1015
        }
1016
        return deleteResult;
1017
    }
1018

    
1019
    /**
1020
     * {@inheritDoc}
1021
     */
1022
    @Transactional(readOnly = false)
1023
    @Override
1024
    public DeleteResult delete(UUID specimenUuid, SpecimenDeleteConfigurator config) {
1025
        return delete(load(specimenUuid), config);
1026
    }
1027

    
1028

    
1029
    @Transactional(readOnly = false)
1030
    @Override
1031
    public DeleteResult delete(SpecimenOrObservationBase<?> specimen, SpecimenDeleteConfigurator config) {
1032
        specimen = HibernateProxyHelper.deproxy(specimen, SpecimenOrObservationBase.class);
1033

    
1034
        DeleteResult deleteResult = isDeletable(specimen.getUuid(), config);
1035
        if (!deleteResult.isOk()) {
1036
            return deleteResult;
1037
        }
1038

    
1039
        if (config.isDeleteChildren()) {
1040
            Set<DerivationEvent> derivationEvents = specimen.getDerivationEvents();
1041
            //clone to avoid concurrent modification
1042
            //can happen if the child is deleted and deleted its own derivedFrom event
1043
            Set<DerivationEvent> derivationEventsClone = new HashSet<>(derivationEvents);
1044
            for (DerivationEvent derivationEvent : derivationEventsClone) {
1045
                Set<DerivedUnit> derivatives = derivationEvent.getDerivatives();
1046
                Iterator<DerivedUnit> it = derivatives.iterator();
1047
                Set<DerivedUnit> derivativesToDelete = new HashSet<>();
1048
                while (it.hasNext()) {
1049
                    DerivedUnit unit = it.next();
1050
                    derivativesToDelete.add(unit);
1051
                }
1052
                for (DerivedUnit unit:derivativesToDelete){
1053
                    deleteResult.includeResult(delete(unit, config));
1054
                }
1055
            }
1056
        }
1057

    
1058

    
1059

    
1060

    
1061
        // check related objects
1062
        Set<CdmBase> relatedObjects = deleteResult.getRelatedObjects();
1063

    
1064
        for (CdmBase relatedObject : relatedObjects) {
1065
            // check for TypeDesignations
1066
            if (relatedObject.isInstanceOf(SpecimenTypeDesignation.class)) {
1067
                SpecimenTypeDesignation designation = HibernateProxyHelper.deproxy(relatedObject, SpecimenTypeDesignation.class);
1068
                designation.setTypeSpecimen(null);
1069
                List<TaxonName> typifiedNames = new ArrayList<>();
1070
                typifiedNames.addAll(designation.getTypifiedNames());
1071
                for (TaxonName taxonName : typifiedNames) {
1072
                    taxonName.removeTypeDesignation(designation);
1073
                }
1074
            }
1075
            // delete IndividualsAssociation
1076
            if (relatedObject.isInstanceOf(IndividualsAssociation.class)) {
1077
                IndividualsAssociation association = HibernateProxyHelper.deproxy(relatedObject, IndividualsAssociation.class);
1078
                association.setAssociatedSpecimenOrObservation(null);
1079
                association.getInDescription().removeElement(association);
1080
            }
1081
            // check for "described specimen" (deprecated)
1082
            if (relatedObject.isInstanceOf(TaxonDescription.class)) {
1083
                TaxonDescription description = HibernateProxyHelper.deproxy(relatedObject, TaxonDescription.class);
1084
                description.setDescribedSpecimenOrObservation(null);
1085
            }
1086
            // check for specimen description
1087
            if (relatedObject.isInstanceOf(SpecimenDescription.class)) {
1088
                SpecimenDescription specimenDescription = HibernateProxyHelper.deproxy(relatedObject, SpecimenDescription.class);
1089
                specimenDescription.setDescribedSpecimenOrObservation(null);
1090
                // check if description is a description of the given specimen
1091
                if (specimen.getDescriptions().contains(specimenDescription)) {
1092
                    specimen.removeDescription(specimenDescription);
1093
                }
1094
                DeleteResult descriptionDelete = descriptionService.isDeletable(specimenDescription.getUuid(), null);
1095
                if (descriptionDelete.isOk()){
1096
                    deleteResult.includeResult(descriptionService.delete(specimenDescription));
1097
                }
1098
            }
1099
            // check for amplification
1100
            if (relatedObject.isInstanceOf(AmplificationResult.class)) {
1101
                AmplificationResult amplificationResult = HibernateProxyHelper.deproxy(relatedObject, AmplificationResult.class);
1102
                amplificationResult.getDnaSample().removeAmplificationResult(amplificationResult);
1103
            }
1104
            // check for sequence
1105
            if (relatedObject.isInstanceOf(Sequence.class)) {
1106
                Sequence sequence = HibernateProxyHelper.deproxy(relatedObject, Sequence.class);
1107
                sequence.getDnaSample().removeSequence(sequence);
1108
            }
1109
            // check for children and parents (derivation events)
1110
            if (relatedObject.isInstanceOf(DerivationEvent.class)) {
1111
                DerivationEvent derivationEvent = HibernateProxyHelper.deproxy(relatedObject, DerivationEvent.class);
1112
                // parent derivation event (derivedFrom)
1113
                if (derivationEvent.getDerivatives().contains(specimen) && specimen.isInstanceOf(DerivedUnit.class)) {
1114
                    derivationEvent.removeDerivative(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class));
1115
                    if (derivationEvent.getDerivatives().isEmpty()) {
1116
                        Set<SpecimenOrObservationBase> originals = derivationEvent.getOriginals();
1117
                        for (SpecimenOrObservationBase specimenOrObservationBase : originals) {
1118
                            specimenOrObservationBase.removeDerivationEvent(derivationEvent);
1119
                            deleteResult.addUpdatedObject(specimenOrObservationBase);
1120
                        }
1121
                        // if derivationEvent has no derivates anymore, delete it
1122
                        deleteResult.includeResult(eventService.delete(derivationEvent));
1123
                    }
1124
                }
1125
                else{
1126
                    //child derivation events should not occur since we delete the hierarchy from bottom to top
1127
                }
1128
            }
1129
        }
1130
        if (specimen instanceof FieldUnit){
1131
            FieldUnit fieldUnit = HibernateProxyHelper.deproxy(specimen, FieldUnit.class);
1132
            GatheringEvent event = fieldUnit.getGatheringEvent();
1133
            fieldUnit.setGatheringEvent(null);
1134
            if (event != null){
1135
                DeleteResult result = eventService.isDeletable(event.getUuid(), null);
1136
                if (result.isOk()){
1137
                    deleteResult.includeResult( eventService.delete(event));
1138
                }
1139
            }
1140

    
1141
        }
1142
        deleteResult.includeResult(delete(specimen));
1143

    
1144
        return deleteResult;
1145
    }
1146

    
1147
    @Override
1148
    public Collection<IndividualsAssociation> listIndividualsAssociations(SpecimenOrObservationBase<?> specimen, Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1149
        return dao.listIndividualsAssociations(specimen, limit, start, orderHints, propertyPaths);
1150
    }
1151

    
1152
    /**
1153
     * {@inheritDoc}
1154
     */
1155
    @Override
1156
    public Collection<TaxonBase<?>> listAssociatedTaxa(SpecimenOrObservationBase<?> specimen, Integer limit,
1157
            Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1158
        return listAssociatedTaxa(specimen, INCLUDE_UNPUBLISHED, limit, start, orderHints, propertyPaths);
1159
    }
1160
    @Override
1161
    public Collection<TaxonBase<?>> listAssociatedTaxa(SpecimenOrObservationBase<?> specimen, boolean includeUnpublished,
1162
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1163
        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
1164

    
1165
        //individuals associations
1166
        associatedTaxa.addAll(listIndividualsAssociationTaxa(specimen, includeUnpublished, limit, start, orderHints, propertyPaths));
1167
        //type designation
1168
        if(specimen.isInstanceOf(DerivedUnit.class)){
1169
            associatedTaxa.addAll(listTypeDesignationTaxa(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class),
1170
                  includeUnpublished, limit, start, orderHints, propertyPaths));
1171
        }
1172
        //determinations
1173
        associatedTaxa.addAll(listDeterminedTaxa(specimen, includeUnpublished, limit, start, orderHints, propertyPaths));
1174

    
1175
        return associatedTaxa;
1176
    }
1177

    
1178

    
1179

    
1180
    /**
1181
     * {@inheritDoc}
1182
     */
1183
    @Override
1184
    public Collection<TaxonBase<?>> listDeterminedTaxa(SpecimenOrObservationBase<?> specimen, Integer limit,
1185
            Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1186
        return listDeterminedTaxa(specimen, INCLUDE_UNPUBLISHED, limit, start, orderHints, propertyPaths);
1187
    }
1188
    @Override
1189
    public Collection<TaxonBase<?>> listDeterminedTaxa(SpecimenOrObservationBase<?> specimen, boolean includeUnpublished, Integer limit, Integer start,
1190
            List<OrderHint> orderHints, List<String> propertyPaths) {
1191
        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
1192
        for (DeterminationEvent determinationEvent : listDeterminationEvents(specimen, limit, start, orderHints, propertyPaths)) {
1193
            if(determinationEvent.getIdentifiedUnit().equals(specimen)){
1194
                if(determinationEvent.getTaxon()!=null){
1195
                    associatedTaxa.add(taxonService.load(determinationEvent.getTaxon().getUuid(), includeUnpublished, propertyPaths));
1196
                }
1197
                if(determinationEvent.getTaxonName()!=null){
1198
                    Collection<TaxonBase> taxonBases = determinationEvent.getTaxonName().getTaxonBases();
1199
                    for (TaxonBase taxonBase : taxonBases) {
1200
                        associatedTaxa.add(taxonService.load(taxonBase.getUuid(), includeUnpublished, propertyPaths));
1201
                    }
1202
                }
1203
            }
1204
        }
1205
        return associatedTaxa;
1206
    }
1207

    
1208
    /**
1209
     * {@inheritDoc}
1210
     */
1211
    @Override
1212
    public Collection<TaxonBase<?>> listTypeDesignationTaxa(DerivedUnit specimen, Integer limit, Integer start,
1213
            List<OrderHint> orderHints, List<String> propertyPaths) {
1214
        return listTypeDesignationTaxa(specimen, INCLUDE_UNPUBLISHED, limit, start, orderHints, propertyPaths);
1215
    }
1216
    @Override
1217
    public Collection<TaxonBase<?>> listTypeDesignationTaxa(DerivedUnit specimen, boolean includeUnpublished,
1218
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1219
        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
1220
        for (SpecimenTypeDesignation typeDesignation : listTypeDesignations(specimen, limit, start, orderHints, propertyPaths)) {
1221
            if(typeDesignation.getTypeSpecimen().equals(specimen)){
1222
                Set<TaxonName> typifiedNames = typeDesignation.getTypifiedNames();
1223
                for (TaxonName taxonName : typifiedNames) {
1224
                    Set<Taxon> taxa = taxonName.getTaxa();
1225
                    for (Taxon taxon : taxa) {
1226
                        associatedTaxa.add(taxonService.load(taxon.getUuid(), includeUnpublished, propertyPaths));
1227
                    }
1228
                }
1229
            }
1230
        }
1231
        return associatedTaxa;
1232
    }
1233

    
1234
    /**
1235
     * {@inheritDoc}
1236
     */
1237
    @Override
1238
    public Collection<TaxonBase<?>> listIndividualsAssociationTaxa(SpecimenOrObservationBase<?> specimen, Integer limit,
1239
            Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1240
        return listIndividualsAssociationTaxa(specimen, INCLUDE_UNPUBLISHED, limit, start, orderHints, propertyPaths);
1241
    }
1242

    
1243
    @Override
1244
    public Collection<TaxonBase<?>> listIndividualsAssociationTaxa(SpecimenOrObservationBase<?> specimen, boolean includeUnpublished,
1245
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1246
        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
1247
        for (IndividualsAssociation individualsAssociation : listIndividualsAssociations(specimen, null, null, null, null)) {
1248
            if(individualsAssociation.getInDescription().isInstanceOf(TaxonDescription.class)){
1249
                TaxonDescription taxonDescription = HibernateProxyHelper.deproxy(individualsAssociation.getInDescription(), TaxonDescription.class);
1250
                if(taxonDescription.getTaxon()!=null){
1251
                    associatedTaxa.add(taxonService.load(taxonDescription.getTaxon().getUuid(), includeUnpublished, propertyPaths));
1252
                }
1253
            }
1254
        }
1255
        return associatedTaxa;
1256
    }
1257

    
1258
    @Override
1259
    public Collection<DeterminationEvent> listDeterminationEvents(SpecimenOrObservationBase<?> specimen,
1260
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1261
        return dao.listDeterminationEvents(specimen, limit, start, orderHints, propertyPaths);
1262
    }
1263

    
1264
    @Override
1265
    public Map<DerivedUnit, Collection<SpecimenTypeDesignation>> listTypeDesignations(
1266
            Collection<DerivedUnit> specimens, Integer limit, Integer start,
1267
            List<OrderHint> orderHints, List<String> propertyPaths) {
1268
        Map<DerivedUnit, Collection<SpecimenTypeDesignation>> typeDesignationMap = new HashMap<>();
1269
        for (DerivedUnit specimen : specimens) {
1270
            Collection<SpecimenTypeDesignation> typeDesignations = listTypeDesignations(specimen, limit, start, orderHints, propertyPaths);
1271
            typeDesignationMap.put(specimen, typeDesignations);
1272
        }
1273
        return typeDesignationMap;
1274
    }
1275

    
1276
    @Override
1277
    public Collection<SpecimenTypeDesignation> listTypeDesignations(DerivedUnit specimen,
1278
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1279
        return dao.listTypeDesignations(specimen, limit, start, orderHints, propertyPaths);
1280
    }
1281

    
1282
    @Override
1283
    public Collection<DescriptionBase<?>> listDescriptionsWithDescriptionSpecimen(
1284
            SpecimenOrObservationBase<?> specimen, Integer limit, Integer start, List<OrderHint> orderHints,
1285
            List<String> propertyPaths) {
1286
        return dao.listDescriptionsWithDescriptionSpecimen(specimen, limit, start, orderHints, propertyPaths);
1287
    }
1288

    
1289
    @Override
1290
    public Collection<DescriptionElementBase> getCharacterDataForSpecimen(UUID specimenUuid) {
1291
        SpecimenOrObservationBase<?> specimen = load(specimenUuid);
1292
        if (specimen != null) {
1293
            return specimen.characterData();
1294
        }
1295
        else{
1296
            throw new DataRetrievalFailureException("Specimen with the given uuid not found in the data base");
1297
        }
1298
    }
1299

    
1300
    @Override
1301
    public long countByTitle(IIdentifiableEntityServiceConfigurator<SpecimenOrObservationBase> config){
1302
        if (config instanceof FindOccurrencesConfigurator) {
1303
            FindOccurrencesConfigurator occurrenceConfig = (FindOccurrencesConfigurator) config;
1304
            Taxon taxon = null;
1305
            if(occurrenceConfig.getAssociatedTaxonUuid()!=null){
1306
                TaxonBase<?> taxonBase = taxonService.load(occurrenceConfig.getAssociatedTaxonUuid());
1307
                if(taxonBase.isInstanceOf(Taxon.class)){
1308
                    taxon = HibernateProxyHelper.deproxy(taxonBase, Taxon.class);
1309
                }
1310
            }
1311
            TaxonName taxonName = null;
1312
            if(occurrenceConfig.getAssociatedTaxonNameUuid()!=null){
1313
                taxonName = nameService.load(occurrenceConfig.getAssociatedTaxonNameUuid());
1314
            }
1315
            /*TODO: #6484 Neither isRetrieveIndirectlyAssociatedSpecimens() nor the AssignmentStatus
1316
             * is currently reflected in the HQL query. So using these in the count method will
1317
             * significantly slow down this method as we have to retrieve the entities instead of
1318
             * just the amount.
1319
             */
1320
            if(occurrenceConfig.isRetrieveIndirectlyAssociatedSpecimens() || !occurrenceConfig.getAssignmentStatus().equals(AssignmentStatus.ALL_SPECIMENS)){
1321
                List<SpecimenOrObservationBase> occurrences = new ArrayList<>();
1322
                occurrences.addAll(dao.findOccurrences(occurrenceConfig.getClazz(),
1323
                        occurrenceConfig.getTitleSearchStringSqlized(), occurrenceConfig.getSignificantIdentifier(),
1324
                        occurrenceConfig.getSpecimenType(), taxon, taxonName, occurrenceConfig.getMatchMode(), null, null,
1325
                        occurrenceConfig.getOrderHints(), occurrenceConfig.getPropertyPaths()));
1326
                occurrences = filterOccurencesByAssignmentAndHierarchy(occurrenceConfig, occurrences, taxon, taxonName);
1327
                return occurrences.size();
1328
            }
1329

    
1330
            return dao.countOccurrences(occurrenceConfig.getClazz(),
1331
                    occurrenceConfig.getTitleSearchStringSqlized(), occurrenceConfig.getSignificantIdentifier(),
1332
                    occurrenceConfig.getSpecimenType(), taxon, taxonName, occurrenceConfig.getMatchMode(), null, null,
1333
                    occurrenceConfig.getOrderHints(), occurrenceConfig.getPropertyPaths());
1334
        }
1335
        else{
1336
            return super.countByTitle(config);
1337
        }
1338
    }
1339

    
1340
    @Override
1341
    public Pager<UuidAndTitleCache<SpecimenOrObservationBase>> findByTitleUuidAndTitleCache(
1342
            FindOccurrencesConfigurator config){
1343
        List<UuidAndTitleCache<SpecimenOrObservationBase>> occurrences = new ArrayList<>();
1344
        Taxon taxon = null;
1345
        if(config.getAssociatedTaxonUuid()!=null){
1346
            TaxonBase<?> taxonBase = taxonService.load(config.getAssociatedTaxonUuid());
1347
            if(taxonBase.isInstanceOf(Taxon.class)){
1348
                taxon = CdmBase.deproxy(taxonBase, Taxon.class);
1349
            }
1350
        }
1351
        TaxonName taxonName = null;
1352
        if(config.getAssociatedTaxonNameUuid()!=null){
1353
            taxonName = nameService.load(config.getAssociatedTaxonNameUuid());
1354
        }
1355
        occurrences.addAll(dao.findOccurrencesUuidAndTitleCache(config.getClazz(),
1356
                config.getTitleSearchString(), config.getSignificantIdentifier(),
1357
                config.getSpecimenType(), taxon, taxonName, config.getMatchMode(), null, null,
1358
                config.getOrderHints()));
1359

    
1360
        return new DefaultPagerImpl<>(config.getPageNumber(), occurrences.size(), config.getPageSize(), occurrences);
1361
    }
1362

    
1363
    @Override
1364
    public List<DerivedUnitDTO> findByTitleDerivedUnitDTO(FindOccurrencesConfigurator config) {
1365
        Taxon taxon = null;
1366
        if(config.getAssociatedTaxonUuid()!=null){
1367
            TaxonBase<?> taxonBase = taxonService.load(config.getAssociatedTaxonUuid());
1368
            if(taxonBase.isInstanceOf(Taxon.class)){
1369
                taxon = CdmBase.deproxy(taxonBase, Taxon.class);
1370
            }
1371
        }
1372
        TaxonName taxonName = null;
1373
        if(config.getAssociatedTaxonNameUuid()!=null){
1374
            taxonName = nameService.load(config.getAssociatedTaxonNameUuid());
1375
        }
1376
        List<DerivedUnit> occurrences = new ArrayList<>();
1377
        occurrences.addAll(dao.findOccurrences(DerivedUnit.class,
1378
                config.getTitleSearchString(), config.getSignificantIdentifier(),
1379
                config.getSpecimenType(), taxon, taxonName, config.getMatchMode(), null, null,
1380
                config.getOrderHints(), null));
1381

    
1382
        List<DerivedUnitDTO> dtos = new ArrayList<>();
1383
        occurrences.forEach(derivedUnit->dtos.add(assembleDerivedUnitDTO(derivedUnit)));
1384
        return dtos;
1385
    }
1386

    
1387
    @Override
1388
    public <S extends SpecimenOrObservationBase> Pager<S> findByTitle(
1389
            IIdentifiableEntityServiceConfigurator<S> config) {
1390
        if (config instanceof FindOccurrencesConfigurator) {
1391
            FindOccurrencesConfigurator occurrenceConfig = (FindOccurrencesConfigurator) config;
1392
            List<SpecimenOrObservationBase> occurrences = new ArrayList<>();
1393
            Taxon taxon = null;
1394
            if(occurrenceConfig.getAssociatedTaxonUuid()!=null){
1395
                TaxonBase<?> taxonBase = taxonService.load(occurrenceConfig.getAssociatedTaxonUuid());
1396
                if(taxonBase.isInstanceOf(Taxon.class)){
1397
                    taxon = HibernateProxyHelper.deproxy(taxonBase, Taxon.class);
1398
                }
1399
            }
1400
            TaxonName taxonName = null;
1401
            if(occurrenceConfig.getAssociatedTaxonNameUuid()!=null){
1402
                taxonName = nameService.load(occurrenceConfig.getAssociatedTaxonNameUuid());
1403
            }
1404
            List<? extends SpecimenOrObservationBase> foundOccurrences = dao.findOccurrences(occurrenceConfig.getClazz(),
1405
                    occurrenceConfig.getTitleSearchString(), occurrenceConfig.getSignificantIdentifier(),
1406
                    occurrenceConfig.getSpecimenType(), taxon, taxonName, occurrenceConfig.getMatchMode(), null, null,
1407
                    occurrenceConfig.getOrderHints(), occurrenceConfig.getPropertyPaths());
1408
            occurrences.addAll(foundOccurrences);
1409
            occurrences = filterOccurencesByAssignmentAndHierarchy(occurrenceConfig, occurrences, taxon, taxonName);
1410

    
1411
            return new DefaultPagerImpl<>(config.getPageNumber(), occurrences.size(), config.getPageSize(), (List<S>)occurrences);
1412
        }
1413
        return super.findByTitle(config);
1414
    }
1415

    
1416
    private List<SpecimenOrObservationBase> filterOccurencesByAssignmentAndHierarchy(
1417
            FindOccurrencesConfigurator occurrenceConfig, List<SpecimenOrObservationBase> occurrences, Taxon taxon,
1418
            TaxonName taxonName) {
1419
        //filter out (un-)assigned specimens
1420
        if(taxon==null && taxonName==null){
1421
            AssignmentStatus assignmentStatus = occurrenceConfig.getAssignmentStatus();
1422
            List<SpecimenOrObservationBase> specimenWithAssociations = new ArrayList<>();
1423
            if(!assignmentStatus.equals(AssignmentStatus.ALL_SPECIMENS)){
1424
                for (SpecimenOrObservationBase specimenOrObservationBase : occurrences) {
1425
                    boolean includeUnpublished = true;  //TODO not sure if this is correct, maybe we have to propagate publish flag to higher methods.
1426
                    Collection<TaxonBase<?>> associatedTaxa = listAssociatedTaxa(specimenOrObservationBase,
1427
                            includeUnpublished, null, null, null, null);
1428
                    if(!associatedTaxa.isEmpty()){
1429
                        specimenWithAssociations.add(specimenOrObservationBase);
1430
                    }
1431
                }
1432
            }
1433
            if(assignmentStatus.equals(AssignmentStatus.UNASSIGNED_SPECIMENS)){
1434
                occurrences.removeAll(specimenWithAssociations);
1435
            }
1436
            if(assignmentStatus.equals(AssignmentStatus.ASSIGNED_SPECIMENS)){
1437
                occurrences = new ArrayList<>(specimenWithAssociations);
1438
            }
1439
        }
1440
        // indirectly associated specimens
1441
        if(occurrenceConfig.isRetrieveIndirectlyAssociatedSpecimens()){
1442
            List<SpecimenOrObservationBase> indirectlyAssociatedOccurrences = new ArrayList<>(occurrences);
1443
            for (SpecimenOrObservationBase<?> specimen : occurrences) {
1444
                List<SpecimenOrObservationBase<?>> allHierarchyDerivates = getAllHierarchyDerivatives(specimen);
1445
                for (SpecimenOrObservationBase<?> specimenOrObservationBase : allHierarchyDerivates) {
1446
                    if(!occurrences.contains(specimenOrObservationBase)){
1447
                        indirectlyAssociatedOccurrences.add(specimenOrObservationBase);
1448
                    }
1449
                }
1450
            }
1451
            occurrences = indirectlyAssociatedOccurrences;
1452
        }
1453
        return occurrences;
1454
    }
1455

    
1456
    @Override
1457
    public List<SpecimenOrObservationBase<?>> getAllHierarchyDerivatives(SpecimenOrObservationBase<?> specimen){
1458
        List<SpecimenOrObservationBase<?>> allHierarchyDerivatives = new ArrayList<>();
1459
        Collection<FieldUnit> fieldUnits = findFieldUnits(specimen.getUuid(), null);
1460
        if(fieldUnits.isEmpty()){
1461
            allHierarchyDerivatives.add(specimen);
1462
            allHierarchyDerivatives.addAll(getAllChildDerivatives(specimen));
1463
        }
1464
        else{
1465
            for (FieldUnit fieldUnit : fieldUnits) {
1466
                allHierarchyDerivatives.add(fieldUnit);
1467
                allHierarchyDerivatives.addAll(getAllChildDerivatives(fieldUnit));
1468
            }
1469
        }
1470
        return allHierarchyDerivatives;
1471
    }
1472

    
1473
    @Override
1474
    public List<DerivedUnit> getAllChildDerivatives(UUID specimenUuid){
1475
        return getAllChildDerivatives(load(specimenUuid));
1476
    }
1477

    
1478
    @Override
1479
    public List<DerivedUnit> getAllChildDerivatives(SpecimenOrObservationBase<?> specimen){
1480
        if (specimen == null){
1481
            return null;
1482
        }
1483
        List<DerivedUnit> childDerivate = new ArrayList<>();
1484
        Set<DerivationEvent> derivationEvents = specimen.getDerivationEvents();
1485
        for (DerivationEvent derivationEvent : derivationEvents) {
1486
            Set<DerivedUnit> derivatives = derivationEvent.getDerivatives();
1487
            for (DerivedUnit derivedUnit : derivatives) {
1488
                childDerivate.add(derivedUnit);
1489
                childDerivate.addAll(getAllChildDerivatives(derivedUnit.getUuid()));
1490
            }
1491
        }
1492
        return childDerivate;
1493
    }
1494

    
1495
    @Override
1496
    public long countOccurrences(IIdentifiableEntityServiceConfigurator<SpecimenOrObservationBase> config){
1497
        return countByTitle(config);
1498
    }
1499

    
1500
    /**
1501
     * {@inheritDoc}
1502
     */
1503
    @Override
1504
    public List<FieldUnit> findFieldUnitsForGatheringEvent(UUID gatheringEventUuid) {
1505
        return dao.findFieldUnitsForGatheringEvent(gatheringEventUuid, null, null, null, null);
1506
    }
1507

    
1508

    
1509
    /**
1510
     * {@inheritDoc}
1511
     */
1512
    @Override
1513
    public List<Point> findPointsForFieldUnitList(List<UUID> fieldUnitUuids) {
1514

    
1515
        return dao.findPointsForFieldUnitList(fieldUnitUuids);
1516
    }
1517
}
(76-76/100)