Project

General

Profile

Download (78.6 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
import java.util.stream.Collectors;
26

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

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

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

    
113
    static private final Logger logger = Logger.getLogger(OccurrenceServiceImpl.class);
114

    
115
    @Autowired
116
    private IDescriptionService descriptionService;
117

    
118
    @Autowired
119
    private INameService nameService;
120

    
121
    @Autowired
122
    private IEventBaseService eventService;
123

    
124
    @Autowired
125
    private ITaxonService taxonService;
126

    
127
    @Autowired
128
    private ISequenceService sequenceService;
129

    
130
    @Autowired
131
    private AbstractBeanInitializer<?> beanInitializer;
132

    
133
    @Autowired
134
    private ILuceneIndexToolProvider luceneIndexToolProvider;
135

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
299
        beanInitializer.initializeAll(derivedUnitFacadeList, derivedUnitFacadeInitStrategy);
300

    
301
        return derivedUnitFacadeList;
302
    }
303

    
304

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

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

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

    
318
    @Override
319
    @Deprecated
320
    public Collection<FieldUnit> listFieldUnitsByAssociatedTaxon(Taxon associatedTaxon, List<OrderHint> orderHints, List<String> propertyPaths) {
321
        return pageRootUnitsByAssociatedTaxon(FieldUnit.class, null, associatedTaxon, null, null, null, null, propertyPaths).getRecords();
322
    }
323

    
324
    @Override
325
    public <T extends SpecimenOrObservationBase> Collection<T> listRootUnitsByAssociatedTaxon(Class<T> type, Taxon associatedTaxon, List<OrderHint> orderHints, List<String> propertyPaths) {
326
        return pageRootUnitsByAssociatedTaxon(type, null, associatedTaxon, null, null, null, null, propertyPaths).getRecords();
327
    }
328

    
329
    @Override
330
    public <T extends SpecimenOrObservationBase> Pager<T> pageRootUnitsByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,
331
            Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
332
            List<String> propertyPaths) {
333

    
334
        if (!getSession().contains(associatedTaxon)) {
335
            associatedTaxon = (Taxon) taxonService.load(associatedTaxon.getUuid());
336
        }
337

    
338
        // gather the IDs of all relevant root units
339
        Set<UUID> rootUnitUuids = new HashSet<>();
340
        List<SpecimenOrObservationBase> records = listByAssociatedTaxon(null, includeRelationships, associatedTaxon, maxDepth, null, null, orderHints, propertyPaths);
341
        for (SpecimenOrObservationBase<?> specimen : records) {
342
            for (SpecimenOrObservationBase<?> rootUnit : findRootUnits(specimen.getUuid(), null)) {
343
                if(type == null || type.isAssignableFrom(rootUnit.getClass())) {
344
                    rootUnitUuids.add(rootUnit.getUuid());
345
                }
346
            }
347
        }
348
        //dao.list() does the paging of the field units. Passing the field units directly to the Pager would not work
349
        List<SpecimenOrObservationBase> rootUnits = dao.list(rootUnitUuids, pageSize, pageNumber, orderHints, propertyPaths);
350
        List<T> castedUnits = new ArrayList<>(rootUnits.size());
351
        for(SpecimenOrObservationBase sob : rootUnits) {
352
            // this cast should be save since the uuids have been filtered by type above
353
            castedUnits.add((T)sob);
354
        }
355
        return new DefaultPagerImpl<T>(pageNumber, (long)castedUnits.size(), pageSize, castedUnits);
356
    }
357

    
358
    @Override
359
    public FieldUnitDTO assembleFieldUnitDTO(FieldUnit fieldUnit) {
360

    
361
        if (!getSession().contains(fieldUnit)) {
362
            fieldUnit = (FieldUnit) load(fieldUnit.getUuid());
363
        }
364
        // FIXME the filter for SpecimenOrObservationType.PreservedSpecimen has been preserved from the former implementation (see commit 07e3f63c7d  and older)
365
        // it is questionable if this filter makes sense for all use cases or if it is only a sensible default for the
366
        // compressed specimen table in the cdm-dataportal (see #6816, #6870)
367
        EnumSet<SpecimenOrObservationType> typeIncludeFilter = EnumSet.of(SpecimenOrObservationType.PreservedSpecimen);
368
        FieldUnitDTO fieldUnitDTO = FieldUnitDTO.fromEntity(fieldUnit, null, typeIncludeFilter);
369
        return fieldUnitDTO;
370
    }
371

    
372

    
373
    @Override
374
    @Transactional
375
    public DerivedUnitDTO assembleDerivedUnitDTO(DerivedUnit derivedUnit) {
376

    
377
        if (!getSession().contains(derivedUnit)) {
378
            derivedUnit = (DerivedUnit) load(derivedUnit.getUuid());
379
        }
380
        DerivedUnitDTO derivedUnitDTO = DerivedUnitDTO.fromEntity(derivedUnit, null, null, null);
381

    
382
        // individuals associations
383
        Collection<IndividualsAssociation> individualsAssociations = listIndividualsAssociations(derivedUnit, null, null, null, null);
384
        if(individualsAssociations != null) {
385
            for (IndividualsAssociation individualsAssociation : individualsAssociations) {
386
                if (individualsAssociation.getInDescription() != null) {
387
                    if (individualsAssociation.getInDescription().isInstanceOf(TaxonDescription.class)) {
388
                        TaxonDescription taxonDescription = HibernateProxyHelper.deproxy(individualsAssociation.getInDescription(), TaxonDescription.class);
389
                        Taxon taxon = taxonDescription.getTaxon();
390
                        if (taxon != null) {
391
                            derivedUnitDTO.addAssociatedTaxon(taxon);
392
                        }
393
                    }
394
                }
395
            }
396
        }
397

    
398
        return derivedUnitDTO;
399
    }
400

    
401
    @Override
402
    @Deprecated
403
    public String getMostSignificantIdentifier(DerivedUnit derivedUnit) {
404
        return derivedUnit.getMostSignificantIdentifier();
405
    }
406

    
407
    /**
408
     * TODO there is a very similar function in {@link SpecimenOrObservationBaseDTO#assembleDerivative}.
409
     * If possible we should avoid using this function here by the method in <code>SpecimenOrObservationBaseDTO</code>.
410
     */
411
    private Set<DerivedUnitDTO> getDerivedUnitDTOsFor(SpecimenOrObservationBaseDTO specimenDto, DerivedUnit specimen,
412
            HashMap<UUID, SpecimenOrObservationBaseDTO> alreadyCollectedSpecimen) {
413

    
414
        Set<DerivedUnitDTO> derivedUnits = new HashSet<>();
415
        for (DerivationEvent derivationEvent : specimen.getDerivationEvents()) {
416
            for (DerivedUnit derivative : derivationEvent.getDerivatives()) {
417
                if (!alreadyCollectedSpecimen.containsKey(specimenDto.getUuid())){
418
                    DerivedUnitDTO dto = (DerivedUnitDTO) SpecimenOrObservationDTOFactory.fromEntity(derivative, 0);
419
                    alreadyCollectedSpecimen.put(dto.getUuid(), dto);
420
                    dto.addAllDerivatives(getDerivedUnitDTOsFor(dto, derivative, alreadyCollectedSpecimen));
421
                    derivedUnits.add(dto);
422
                } else {
423
                    if(alreadyCollectedSpecimen.get(specimenDto.getUuid()).getDerivatives().isEmpty() && !derivative.getDerivationEvents().isEmpty()) {
424
                        // we need to add the missing derivatives!
425
                        SpecimenOrObservationBaseDTO dto = alreadyCollectedSpecimen.get(specimenDto.getUuid());
426
                        alreadyCollectedSpecimen.get(specimenDto.getUuid()).addAllDerivatives(getDerivedUnitDTOsFor(dto, derivative, alreadyCollectedSpecimen));
427
                    }
428
                }
429
            }
430
        }
431
        return derivedUnits;
432
    }
433

    
434
//    private Set<DerivateDTO> getDerivedUnitDTOsFor(DerivateDTO specimenDto, DerivedUnit specimen, HashMap<UUID, DerivateDTO> alreadyCollectedSpecimen) {
435
//        Set<DerivateDTO> derivedUnits = new HashSet<>();
436
////        load
437
//        for (DerivationEvent derivationEvent : specimen.getDerivationEvents()) {
438
//            for (DerivedUnit derivative : derivationEvent.getDerivatives()) {
439
//                if (!alreadyCollectedSpecimen.containsKey(specimenDto.getUuid())){
440
//                    PreservedSpecimenDTO dto;
441
//                    if (derivative instanceof DnaSample){
442
//                        dto = DNASampleDTO.newInstance(derivative);
443
//                    }else{
444
//                        dto = PreservedSpecimenDTO.newInstance(derivative);
445
//                    }
446
//                    alreadyCollectedSpecimen.put(dto.getUuid(), dto);
447
//                    dto.addAllDerivates(getDerivedUnitDTOsFor(dto, derivative, alreadyCollectedSpecimen));
448
//                    derivedUnits.add(dto);
449
//                }
450
//            }
451
//        }
452
//        return derivedUnits;
453
//    }
454

    
455

    
456
    @SuppressWarnings("unchecked")
457
    @Override
458
    public <T extends SpecimenOrObservationBase> Pager<T> pageByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includedRelationships,
459
            Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
460

    
461
        Set<Taxon> taxa = new HashSet<>();
462
        Set<Integer> occurrenceIds = new HashSet<>();
463
        List<T> occurrences = new ArrayList<>();
464
        boolean includeUnpublished = INCLUDE_UNPUBLISHED;
465

    
466
        // Integer limit = PagerUtils.limitFor(pageSize);
467
        // Integer start = PagerUtils.startFor(pageSize, pageNumber);
468

    
469
        if (!getSession().contains(associatedTaxon)) {
470
            associatedTaxon = (Taxon) taxonService.load(associatedTaxon.getUuid());
471
        }
472

    
473
        if (includedRelationships != null) {
474
            taxa = taxonService.listRelatedTaxa(associatedTaxon, includedRelationships, maxDepth, includeUnpublished, null, null, propertyPaths);
475
        }
476

    
477
        taxa.add(associatedTaxon);
478

    
479
        for (Taxon taxon : taxa) {
480
            List<T> perTaxonOccurrences = dao.listByAssociatedTaxon(type, taxon, null, null, orderHints, propertyPaths);
481
            for (SpecimenOrObservationBase<?> o : perTaxonOccurrences) {
482
                occurrenceIds.add(o.getId());
483
            }
484
        }
485
        occurrences = (List<T>) dao.loadList(occurrenceIds, null, propertyPaths);
486

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

    
489
    }
490

    
491
    @Override
492
    public <T extends SpecimenOrObservationBase> Pager<T> pageByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,
493
            String taxonUUID, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
494

    
495
        UUID uuid = UUID.fromString(taxonUUID);
496
        Taxon taxon = (Taxon) taxonService.load(uuid);
497
        return pageByAssociatedTaxon(type, includeRelationships, taxon, maxDepth, pageSize, pageNumber, orderHints, propertyPaths);
498

    
499
    }
500

    
501
    @Override
502
    @Transactional
503
    public List<SpecimenOrObservationBaseDTO> listRootUnitDTOsByAssociatedTaxon(Set<TaxonRelationshipEdge> includedRelationships,
504
            UUID associatedTaxonUuid, List<String> propertyPaths) {
505

    
506
        Set<Taxon> taxa = new HashSet<>();
507
        Set<SpecimenOrObservationBaseDTO> rootUnitDTOs = new HashSet<>();
508
        boolean includeUnpublished = INCLUDE_UNPUBLISHED;
509

    
510
        Taxon associatedTaxon = (Taxon) taxonService.load(associatedTaxonUuid);
511
        if (includedRelationships != null) {
512
            taxa = taxonService.listRelatedTaxa(associatedTaxon, includedRelationships, null, includeUnpublished, null, null, null);
513
        }
514
        taxa.add(associatedTaxon);
515

    
516
        HashMap<UUID, SpecimenOrObservationBaseDTO> alreadyCollectedUnits = new HashMap<>();
517
        for (Taxon taxon : taxa) {
518
            // TODO there might be a good potential to speed up the whole processing by collecting all entities first
519
            // and to create the DTOs in a second step
520
            Set<SpecimenOrObservationBase> perTaxonOccurrences = dao.listByAssociatedTaxon(null, taxon, null, null, null, propertyPaths)
521
                    .stream()
522
                    .map(u -> HibernateProxyHelper.deproxy(u, SpecimenOrObservationBase.class))
523
                    .collect(Collectors.toSet());
524
            for (SpecimenOrObservationBase<?> unit : perTaxonOccurrences) {
525
                unit = HibernateProxyHelper.deproxy(unit);
526
                if (unit instanceof DerivedUnit){
527
                    DerivedUnitDTO derivativeDTO;
528
                    if (!alreadyCollectedUnits.containsKey(unit.getUuid())){
529
                        DerivedUnit derivedUnit = (DerivedUnit)unit;
530
                        derivativeDTO = (DerivedUnitDTO) SpecimenOrObservationDTOFactory.fromEntity(derivedUnit, null);
531
                        if (unit instanceof DnaSample) {
532
                            derivativeDTO = DNASampleDTO.fromEntity((DnaSample)unit);
533
                        } else {
534
                            derivativeDTO = DerivedUnitDTO.fromEntity(derivedUnit, null, null, null);
535
                        }
536
                        alreadyCollectedUnits.put(derivativeDTO.getUuid(), derivativeDTO);
537
                        derivativeDTO.addAllDerivatives(getDerivedUnitDTOsFor(derivativeDTO, derivedUnit, alreadyCollectedUnits));
538
                    }
539
                    derivativeDTO = (DerivedUnitDTO) alreadyCollectedUnits.get(unit.getUuid());
540
                    rootUnitDTOs.addAll(findRootUnitDTO(derivativeDTO, alreadyCollectedUnits));
541
                } else {
542
                    // only other option is FieldUnit
543
                    rootUnitDTOs.add(FieldUnitDTO.fromEntity((FieldUnit)unit, 0, null));
544
                }
545
            }
546
        }
547

    
548
        List<SpecimenOrObservationBaseDTO> orderdDTOs = new ArrayList<>(rootUnitDTOs);
549
        // TODO order dtos by date can only be done by string comparison
550
        // the FieldUnitDTO.date needs to be a Partial object for sensible ordering
551
        Collections.sort(orderdDTOs, new Comparator<SpecimenOrObservationBaseDTO>() {
552

    
553
            @Override
554
            public int compare(SpecimenOrObservationBaseDTO o1, SpecimenOrObservationBaseDTO o2) {
555
                if(o1 instanceof FieldUnitDTO && o2 instanceof FieldUnitDTO) {
556
                    FieldUnitDTO fu1 = (FieldUnitDTO)o1;
557
                    FieldUnitDTO fu2 = (FieldUnitDTO)o2;
558
                    if(fu1.getDate() == null && fu2.getDate() == null) {
559
                        return 0;
560
                    }
561
                    if(fu1.getDate() != null && fu2.getDate() == null) {
562
                        return 1;
563
                    }
564
                    if(fu1.getDate() == null && fu2.getDate() != null) {
565
                        return -1;
566
                    }
567
                    return fu1.getDate().compareTo(fu2.getDate());
568
                }
569
                if(o1 instanceof DerivedUnitDTO && o2 instanceof DerivedUnitDTO) {
570
                    DerivedUnitDTO du1 = (DerivedUnitDTO)o1;
571
                    DerivedUnitDTO du2 = (DerivedUnitDTO)o2;
572
                    return StringUtils.compare(du1.getLabel(), du2.getLabel());
573
                 }
574
                if(o1 instanceof FieldUnitDTO && o2 instanceof DerivedUnitDTO) {
575
                    return -1;
576
                } else {
577
                    return 1;
578
                }
579
            }
580
        });
581

    
582
        return orderdDTOs;
583
    }
584

    
585
    @Override
586
    @Transactional
587
    @Deprecated
588
    public  SpecimenOrObservationBaseDTO findByAccessionNumber(String accessionNumberString, List<OrderHint> orderHints)  {
589
        return findByAccessionNumber(accessionNumberString, orderHints);
590
    }
591

    
592
    @Override
593
    @Transactional
594
    public  SpecimenOrObservationBaseDTO findByGeneticAccessionNumber(String accessionNumberString, List<OrderHint> orderHints)  {
595

    
596
        DnaSample dnaSample = dao.findByGeneticAccessionNumber(accessionNumberString, null);
597
        DerivedUnitDTO dnaSampleDTO;
598
        if (dnaSample != null){
599
            dnaSampleDTO = new DNASampleDTO(dnaSample);
600
            Collection<SpecimenOrObservationBaseDTO> fieldUnitDTOs = this.findRootUnitDTO(dnaSampleDTO, new HashMap<>());
601
            // FIXME change return type to Collection<FieldUnitDTO>
602
            if(fieldUnitDTOs.isEmpty()) {
603
                return null;
604
            } else {
605
               return fieldUnitDTOs.iterator().next();
606
            }
607
        }
608
        return null;
609
    }
610

    
611
    @Override
612
    public Pager<SearchResult<SpecimenOrObservationBase>> findByFullText(
613
            Class<? extends SpecimenOrObservationBase> clazz, String queryString, Rectangle boundingBox, List<Language> languages,
614
            boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
615
            List<String> propertyPaths) throws IOException, LuceneParseException {
616

    
617
        LuceneSearch luceneSearch = prepareByFullTextSearch(clazz, queryString, boundingBox, languages, highlightFragments);
618

    
619
        // --- execute search
620
        TopGroups<BytesRef> topDocsResultSet;
621
        try {
622
            topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
623
        } catch (ParseException e) {
624
            LuceneParseException parseException = new LuceneParseException(e.getMessage());
625
            parseException.setStackTrace(e.getStackTrace());
626
            throw parseException;
627
        }
628

    
629
        Map<CdmBaseType, String> idFieldMap = new HashMap<>();
630
        idFieldMap.put(CdmBaseType.SPECIMEN_OR_OBSERVATIONBASE, "id");
631

    
632
        // --- initialize taxa, highlight matches ....
633
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
634
        @SuppressWarnings("rawtypes")
635
        List<SearchResult<SpecimenOrObservationBase>> searchResults = searchResultBuilder.createResultSet(
636
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
637

    
638
        int totalHits = topDocsResultSet != null ? topDocsResultSet.totalGroupCount : 0;
639

    
640
        return new DefaultPagerImpl<>(pageNumber, Long.valueOf(totalHits), pageSize, searchResults);
641
    }
642

    
643
    private LuceneSearch prepareByFullTextSearch(Class<? extends SpecimenOrObservationBase> clazz, String queryString, Rectangle bbox,
644
            List<Language> languages, boolean highlightFragments) {
645

    
646
        Builder finalQueryBuilder = new Builder();
647
        Builder textQueryBuilder = new Builder();
648

    
649
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, FieldUnit.class);
650
        QueryFactory queryFactory = luceneIndexToolProvider.newQueryFactoryFor(FieldUnit.class);
651

    
652
        // --- criteria
653
        luceneSearch.setCdmTypRestriction(clazz);
654
        if (queryString != null) {
655
            textQueryBuilder.add(queryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
656
            finalQueryBuilder.add(textQueryBuilder.build(), Occur.MUST);
657
        }
658

    
659
        // --- spacial query
660
        if (bbox != null) {
661
            finalQueryBuilder.add(QueryFactory.buildSpatialQueryByRange(bbox, "gatheringEvent.exactLocation.point"), Occur.MUST);
662
        }
663

    
664
        luceneSearch.setQuery(finalQueryBuilder.build());
665

    
666
        // --- sorting
667
        SortField[] sortFields = new SortField[] { SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.Type.STRING, false) };
668
        luceneSearch.setSortFields(sortFields);
669

    
670
        if (highlightFragments) {
671
            luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray());
672
        }
673
        return luceneSearch;
674
    }
675

    
676

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

    
683
        // FIXME: use HQL queries to avoid entity instantiation and to increase performance
684

    
685
        SpecimenOrObservationBase<?> specimen = load(derivedUnitUuid);
686
        Collection<FieldUnit> fieldUnits = new ArrayList<>();
687
        if (specimen == null){
688
            return null;
689
        }
690
        if (specimen.isInstanceOf(FieldUnit.class)) {
691
            fieldUnits.add(HibernateProxyHelper.deproxy(specimen, FieldUnit.class));
692
        }
693
        else if(specimen.isInstanceOf(DerivedUnit.class)){
694
            fieldUnits.addAll(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class).collectRootUnits(FieldUnit.class));
695
        }
696

    
697
        fieldUnits = beanInitializer.initializeAll(fieldUnits, propertyPaths);
698
        return fieldUnits;
699
    }
700

    
701
    @Override
702
    @Transactional(readOnly=true)
703
    public Collection<SpecimenOrObservationBase> findRootUnits(UUID derivedUnitUuid, List<String> propertyPaths) {
704

    
705
        // FIXME: use HQL queries to avoid entity instantiation and to increase performance
706

    
707
        SpecimenOrObservationBase<?> specimen = load(derivedUnitUuid);
708
        Collection<SpecimenOrObservationBase> rootUnits = new ArrayList<>();
709
        if (specimen == null){
710
            return null;
711
        }
712
        if (specimen.isInstanceOf(FieldUnit.class)) {
713
            rootUnits.add(HibernateProxyHelper.deproxy(specimen, FieldUnit.class));
714
        }
715
        else if(specimen.isInstanceOf(DerivedUnit.class)){
716
            rootUnits.addAll(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class).collectRootUnits(SpecimenOrObservationBase.class));
717
        }
718

    
719
        rootUnits = beanInitializer.initializeAll(rootUnits, propertyPaths);
720
        return rootUnits;
721
    }
722

    
723
    /**
724
     * Recursively searches all {@link DerivationEvent}s to find all "originals" ({@link SpecimenOrObservationBase})
725
     * from which this DerivedUnit was derived until all FieldUnits are found.
726
     * <p>
727
     * <b>NOTE:</b> The recursive search still is a bit incomplete and may miss originals in the rare case where a
728
     * derivative has more than one original. (see https://dev.e-taxonomy.eu/redmine/issues/9253)
729
     *
730
     * @param derivedUnitDTO
731
     *  The DerivedUnitDTO to start the search from.
732
     * @param alreadyCollectedSpecimen
733
     *  A map to hold all originals that have been sees during the recursive walk.
734
     * @return
735
     *  The collection of all Field Units that are accessible from the derivative from where the search was started.
736
     */
737
    public Collection<SpecimenOrObservationBaseDTO> findRootUnitDTO(DerivedUnitDTO derivedUnitDTO,
738
            HashMap<UUID, SpecimenOrObservationBaseDTO> alreadyCollectedSpecimen) {
739

    
740
        HashMap<UUID, SpecimenOrObservationBaseDTO> rootUnitDTOs = new HashMap<>();
741
        _findRootUnitDTO(derivedUnitDTO, rootUnitDTOs, alreadyCollectedSpecimen);
742
        return rootUnitDTOs.values();
743

    
744
    }
745

    
746
    /**
747
     * Method for recursive calls, must only be used by {@link #findRootUnitDTO(DerivedUnitDTO, HashMap)}
748
     * <p>
749
     * It will search recursively over all {@link DerivationEvent}s and get the "originals" ({@link SpecimenOrObservationBase})
750
     * from which this DerivedUnit was derived until all FieldUnits are found.
751
     */
752
    private void _findRootUnitDTO(DerivedUnitDTO derivedUnitDTO, Map<UUID, SpecimenOrObservationBaseDTO> rootUnitDTOs,
753
                HashMap<UUID, SpecimenOrObservationBaseDTO> alreadyCollectedSpecimen) {
754

    
755
        List<String> propertyPaths = new ArrayList<>();
756

    
757
        // add the supplied DTO the the alreadyCollectedSpecimen if not yet there
758
        if(!alreadyCollectedSpecimen.containsKey(derivedUnitDTO.getUuid())) {
759
            alreadyCollectedSpecimen.put(derivedUnitDTO.getUuid(), derivedUnitDTO);
760
        }
761

    
762
        List<SpecimenOrObservationBase> originals = dao.findOriginalsForDerivedUnit(derivedUnitDTO.getUuid(), propertyPaths);
763
        if (originals.size() > 0){
764
            if (originals.size() > 1){
765
                logger.warn("The derived unit with uuid " + derivedUnitDTO.getUuid() + "has more than one orginal");
766
            }
767
            // FIXME allow handling multiple originals
768
            SpecimenOrObservationBase<?> original = originals.get(0);
769
            original = HibernateProxyHelper.deproxy(original);
770

    
771
            if (alreadyCollectedSpecimen.containsKey(original.getUuid())){
772
                alreadyCollectedSpecimen.get(original.getUuid()).addDerivative(derivedUnitDTO);
773
    //            if ( alreadyCollectedSpecimen.get(specimen.getUuid()) instanceof FieldUnitDTO){
774
    //                ((FieldUnitDTO)alreadyCollectedSpecimen.get(specimen.getUuid())).getTaxonRelatedDerivedUnits().add(derivedUnitDTO.getUuid());
775
    //            }
776
            }else{
777
                if(!rootUnitDTOs.containsKey(original.getUuid())){
778
                    // the direct derivatives of the field unit are added in the factory method, so it is guaranteed that
779
                    // the derivedUnitDTO is already contained.
780
                    // ----
781
                    // Don't assemble derivatives for the field unit, since we have them collected already
782
                    // when ascending to the originals, we only want to collect those derivatives which are on the path up to the root
783
                    final Integer maxDepth = 0;
784
                    SpecimenOrObservationBaseDTO originalDTO = SpecimenOrObservationDTOFactory.fromEntity(original, maxDepth);
785
                    originalDTO.addDerivative(derivedUnitDTO);
786
                    alreadyCollectedSpecimen.put(originalDTO.getUuid(), originalDTO);
787
                    if (original instanceof FieldUnit){
788
                        rootUnitDTOs.put(originalDTO.getUuid(), originalDTO);
789
                    }else{
790
                        _findRootUnitDTO((DerivedUnitDTO) originalDTO, rootUnitDTOs, alreadyCollectedSpecimen);
791
                    }
792
                } else {
793
                    SpecimenOrObservationBaseDTO previouslyFoundRootUnit = rootUnitDTOs.get(original.getUuid());
794
                    if(!previouslyFoundRootUnit.getDerivatives().stream().anyMatch(uDTO -> uDTO.getUuid().equals(derivedUnitDTO.getUuid()))) {
795
                        previouslyFoundRootUnit.addDerivative(derivedUnitDTO);
796
                    }
797
                }
798
            }
799
        } else {
800
            rootUnitDTOs.put(derivedUnitDTO.getUuid(), derivedUnitDTO);
801
        }
802

    
803
    }
804

    
805
    @Override
806
    @Transactional(readOnly=true)
807
    public FieldUnitDTO loadFieldUnitDTO(UUID derivedUnitUuid) {
808

    
809
        FieldUnitDTO fieldUnitDTO = null;
810
        DerivedUnitDTO derivedUnitDTO = null;
811

    
812
        Map<UUID, SpecimenOrObservationBaseDTO> cycleDetectionMap = new HashMap<>();
813
        SpecimenOrObservationBase<?> derivative = dao.load(derivedUnitUuid);
814
        if(derivative != null){
815
            if (derivative instanceof FieldUnit){
816
                fieldUnitDTO = FieldUnitDTO.fromEntity((FieldUnit)derivative);
817
                return fieldUnitDTO;
818
            } else {
819
                // must be a DerivedUnit otherwise
820
                derivedUnitDTO = DerivedUnitDTO.fromEntity((DerivedUnit)derivative);
821
                while(true){
822

    
823
                    Set<SpecimenOrObservationBaseDTO> originals = originalDTOs(derivedUnitDTO.getUuid());
824

    
825
                    if(originals.isEmpty()){
826
                        break;
827
                    }
828
                    if (originals.size() > 1){
829
                        logger.debug("The derived unit with uuid " + derivedUnitUuid + "has more than one orginal, ignoring all but the first one.");
830
                    }
831

    
832
                    SpecimenOrObservationBaseDTO originalDTO = originals.iterator().next();
833

    
834
                    // cycle detection and handling
835
                    if(cycleDetectionMap.containsKey(originalDTO.getUuid())){
836
                        // cycle detected!!!
837
                        try {
838
                            throw new Exception();
839
                        } catch(Exception e){
840
                            logger.error("Cycle in derivate graph detected at DerivedUnit with uuid=" + originalDTO.getUuid() , e);
841
                        }
842
                        // to solve the situation for the output we remove the derivate from the more distant graph node
843
                        // by removing it from the derivatives of its original
844
                        // but let the derivate to be added to the original which is closer to the FieldUnit (below at originalDTO.addDerivate(derivedUnitDTO);)
845
                        for(SpecimenOrObservationBaseDTO seenOriginal: cycleDetectionMap.values()){
846
                            for(SpecimenOrObservationBaseDTO derivateDTO : seenOriginal.getDerivatives()){
847
                                if(derivateDTO.equals(originalDTO)){
848
                                    seenOriginal.getDerivatives().remove(originalDTO);
849
                                }
850
                            }
851
                        }
852
                    } else {
853
                        cycleDetectionMap.put(originalDTO.getUuid(), originalDTO);
854
                    }
855

    
856
                    if (originalDTO instanceof FieldUnitDTO){
857
                        fieldUnitDTO = (FieldUnitDTO)originalDTO;
858
                        if(derivedUnitDTO != null){
859
                            fieldUnitDTO.addDerivative(derivedUnitDTO);
860
                        }
861
                        break;
862
                    }else{
863
                        // So this must be a DerivedUnitDTO
864
                        if (derivedUnitDTO == null){
865
                            derivedUnitDTO = (DerivedUnitDTO)originalDTO;
866
                        } else {
867
                            originalDTO.addDerivative(derivedUnitDTO);
868
                            derivedUnitDTO = (DerivedUnitDTO)originalDTO;
869
                        }
870
                    }
871
                }
872
            }
873
        }
874
        return fieldUnitDTO;
875

    
876
    }
877

    
878
    /**
879
     * @param originalDTO
880
     * @return
881
     */
882
    private Set<SpecimenOrObservationBaseDTO> originalDTOs(UUID derivativeUuid) {
883

    
884
        Set<SpecimenOrObservationBaseDTO> dtos = new HashSet<>();
885
        List<SpecimenOrObservationBase> specimens = dao.findOriginalsForDerivedUnit(derivativeUuid, null);
886
        for(SpecimenOrObservationBase sob : specimens){
887
            if(sob instanceof FieldUnit) {
888
                dtos.add(FieldUnitDTO.fromEntity((FieldUnit)sob));
889
            } else {
890
                dtos.add(DerivedUnitDTO.fromEntity((DerivedUnit)sob));
891
            }
892
        }
893
        return dtos;
894
    }
895

    
896

    
897
    @Override
898
    @Transactional(readOnly = false)
899
    public UpdateResult moveSequence(DnaSample from, DnaSample to, Sequence sequence) {
900
        return moveSequence(from.getUuid(), to.getUuid(), sequence.getUuid());
901
    }
902

    
903
    @Override
904
    @Transactional(readOnly = false)
905
    public UpdateResult moveSequence(UUID fromUuid, UUID toUuid, UUID sequenceUuid) {
906
        // reload specimens to avoid session conflicts
907
        DnaSample from = (DnaSample) load(fromUuid);
908
        DnaSample to = (DnaSample) load(toUuid);
909
        Sequence sequence = sequenceService.load(sequenceUuid);
910

    
911
        if (from == null || to == null || sequence == null) {
912
            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" +
913
                    "Operation was move "+sequence+ " from "+from+" to "+to);
914
        }
915
        UpdateResult result = new UpdateResult();
916
        from.removeSequence(sequence);
917
        saveOrUpdate(from);
918
        to.addSequence(sequence);
919
        saveOrUpdate(to);
920
        result.setStatus(Status.OK);
921
        result.addUpdatedObject(from);
922
        result.addUpdatedObject(to);
923
        return result;
924
    }
925

    
926
    @Override
927
    @Transactional(readOnly = false)
928
    public boolean moveDerivate(SpecimenOrObservationBase<?> from, SpecimenOrObservationBase<?> to, DerivedUnit derivate) {
929
        return moveDerivate(from!=null?from.getUuid():null, to.getUuid(), derivate.getUuid()).isOk();
930
    }
931

    
932
    @Override
933
    @Transactional(readOnly = false)
934
    public UpdateResult moveDerivate(UUID specimenFromUuid, UUID specimenToUuid, UUID derivateUuid) {
935
        // reload specimens to avoid session conflicts
936
        SpecimenOrObservationBase<?> from = null;
937
        if(specimenFromUuid!=null){
938
            from = load(specimenFromUuid);
939
        }
940
        SpecimenOrObservationBase<?> to = load(specimenToUuid);
941
        DerivedUnit derivate = (DerivedUnit) load(derivateUuid);
942

    
943
        if ((specimenFromUuid!=null && from == null) || to == null || derivate == null) {
944
            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" +
945
                    "Operation was move "+derivate+ " from "+from+" to "+to);
946
        }
947
        UpdateResult result = new UpdateResult();
948
        SpecimenOrObservationType derivateType = derivate.getRecordBasis();
949
        SpecimenOrObservationType toType = to.getRecordBasis();
950
        // check if type is a sub derivate type
951
        if(toType==SpecimenOrObservationType.FieldUnit //moving to FieldUnit always works
952
                || derivateType==SpecimenOrObservationType.Media //moving media always works
953
                || (derivateType.isKindOf(toType) && toType!=derivateType)){ //moving only to parent derivate type
954
            if(from!=null){
955
                // remove derivation event from parent specimen of dragged object
956
                DerivationEvent eventToRemove = null;
957
                for (DerivationEvent event : from.getDerivationEvents()) {
958
                    if (event.getDerivatives().contains(derivate)) {
959
                        eventToRemove = event;
960
                        break;
961
                    }
962
                }
963
                from.removeDerivationEvent(eventToRemove);
964
                if(eventToRemove!=null){
965
                    // add new derivation event to target and copy the event parameters of the old one
966
                    DerivationEvent derivedFromNewOriginalEvent = DerivationEvent.NewSimpleInstance(to, derivate, null);
967
                    derivedFromNewOriginalEvent.setActor(eventToRemove.getActor());
968
                    derivedFromNewOriginalEvent.setDescription(eventToRemove.getDescription());
969
                    derivedFromNewOriginalEvent.setInstitution(eventToRemove.getInstitution());
970
                    derivedFromNewOriginalEvent.setTimeperiod(eventToRemove.getTimeperiod());
971
                    derivedFromNewOriginalEvent.setType(eventToRemove.getType());
972
                    to.addDerivationEvent(derivedFromNewOriginalEvent);
973
                    derivate.setDerivedFrom(derivedFromNewOriginalEvent);
974
                }
975
            }
976
            else{
977
                //derivative had no parent before so we use empty derivation event
978
                DerivationEvent derivedFromNewOriginalEvent = DerivationEvent.NewSimpleInstance(to, derivate, null);
979
                to.addDerivationEvent(derivedFromNewOriginalEvent);
980
                derivate.setDerivedFrom(derivedFromNewOriginalEvent);
981
            }
982

    
983
            if(from!=null){
984
                saveOrUpdate(from);
985
            }
986
            saveOrUpdate(to);
987
            result.setStatus(Status.OK);
988
            result.addUpdatedObject(from);
989
            result.addUpdatedObject(to);
990
        } else {
991
            result.setStatus(Status.ERROR);
992
        }
993
        return result;
994
    }
995

    
996
    @Override
997
    public DeleteResult isDeletable(UUID specimenUuid, DeleteConfiguratorBase config) {
998
        DeleteResult deleteResult = new DeleteResult();
999
        SpecimenOrObservationBase specimen = this.load(specimenUuid);
1000
        SpecimenDeleteConfigurator specimenDeleteConfigurator = (SpecimenDeleteConfigurator) config;
1001

    
1002
        // check elements found by super method
1003
        Set<CdmBase> relatedObjects = super.isDeletable(specimenUuid, config).getRelatedObjects();
1004
        for (CdmBase cdmBase : relatedObjects) {
1005
            // check for type designation
1006
            if (cdmBase.isInstanceOf(SpecimenTypeDesignation.class) && !specimenDeleteConfigurator.isDeleteFromTypeDesignation()) {
1007
                deleteResult.setAbort();
1008
                deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation is a type specimen."));
1009
                deleteResult.addRelatedObject(cdmBase);
1010
                break;
1011
            }
1012
            // check for IndividualsAssociations
1013
            else if (cdmBase.isInstanceOf(IndividualsAssociation.class) && !specimenDeleteConfigurator.isDeleteFromIndividualsAssociation()) {
1014
                deleteResult.setAbort();
1015
                deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation is still associated via IndividualsAssociations"));
1016
                deleteResult.addRelatedObject(cdmBase);
1017
                break;
1018
            }
1019
            // check for taxon description
1020
            else if(cdmBase.isInstanceOf(TaxonDescription.class)
1021
                    && HibernateProxyHelper.deproxy(cdmBase, TaxonDescription.class).getDescribedSpecimenOrObservation().equals(specimen)
1022
                    && !specimenDeleteConfigurator.isDeleteFromDescription()){
1023
                deleteResult.setAbort();
1024
                deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation is still used as \"Described Specimen\" in a taxon description."));
1025
                deleteResult.addRelatedObject(cdmBase);
1026
                break;
1027
            }
1028
            // check for children and parents (derivation events)
1029
            else if (cdmBase.isInstanceOf(DerivationEvent.class)) {
1030
                DerivationEvent derivationEvent = HibernateProxyHelper.deproxy(cdmBase, DerivationEvent.class);
1031
                // check if derivation event is empty
1032
                if (!derivationEvent.getDerivatives().isEmpty() && derivationEvent.getOriginals().contains(specimen)) {
1033
                    // if derivationEvent is the childEvent and contains derivations
1034
//                    if (derivationEvent.getDerivatives().contains(specimen)) {
1035
//                        //if it is the parent event the specimen is still deletable
1036
//                        continue;
1037
//                    }
1038
                    if(!specimenDeleteConfigurator.isDeleteChildren()){
1039
                        //if children should not be deleted then it is undeletable
1040
                        deleteResult.setAbort();
1041
                        deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation still has child derivatives."));
1042
                        deleteResult.addRelatedObject(cdmBase);
1043
                        break;
1044
                    }
1045
                    else{
1046
                        // check all children if they can be deleted
1047
                        Set<DerivedUnit> derivatives = derivationEvent.getDerivatives();
1048
                        DeleteResult childResult = new DeleteResult();
1049
                        for (DerivedUnit derivedUnit : derivatives) {
1050
                            childResult.includeResult(isDeletable(derivedUnit.getUuid(), specimenDeleteConfigurator));
1051
                        }
1052
                        if (!childResult.isOk()) {
1053
                            deleteResult.setAbort();
1054
                            deleteResult.includeResult(childResult);
1055
                            deleteResult.addRelatedObject(cdmBase);
1056
                            break;
1057
                        }
1058
                    }
1059
                }
1060
            }
1061
            // check for amplification
1062
            else if (cdmBase.isInstanceOf(AmplificationResult.class)
1063
                    && !specimenDeleteConfigurator.isDeleteMolecularData()
1064
                    && !specimenDeleteConfigurator.isDeleteChildren()) {
1065
                deleteResult.setAbort();
1066
                deleteResult.addException(new ReferencedObjectUndeletableException("DnaSample is used in amplification results."));
1067
                deleteResult.addRelatedObject(cdmBase);
1068
                break;
1069
            }
1070
            // check for sequence
1071
            else if (cdmBase.isInstanceOf(Sequence.class)
1072
                    && !specimenDeleteConfigurator.isDeleteMolecularData()
1073
                    && !specimenDeleteConfigurator.isDeleteChildren()) {
1074
                deleteResult.setAbort();
1075
                deleteResult.addException(new ReferencedObjectUndeletableException("DnaSample is used in sequences."));
1076
                deleteResult.addRelatedObject(cdmBase);
1077
                break;
1078
            }
1079
        }
1080
        if (deleteResult.isOk()) {
1081
            //add all related object if deletion is OK so they can be handled by the delete() method
1082
            deleteResult.addRelatedObjects(relatedObjects);
1083
        }
1084
        return deleteResult;
1085
    }
1086

    
1087
    /**
1088
     * {@inheritDoc}
1089
     */
1090
    @Transactional(readOnly = false)
1091
    @Override
1092
    public DeleteResult delete(UUID specimenUuid, SpecimenDeleteConfigurator config) {
1093
        return delete(load(specimenUuid), config);
1094
    }
1095

    
1096

    
1097
    @Transactional(readOnly = false)
1098
    @Override
1099
    public DeleteResult delete(SpecimenOrObservationBase<?> specimen, SpecimenDeleteConfigurator config) {
1100
        specimen = HibernateProxyHelper.deproxy(specimen, SpecimenOrObservationBase.class);
1101

    
1102
        DeleteResult deleteResult = isDeletable(specimen.getUuid(), config);
1103
        if (!deleteResult.isOk()) {
1104
            return deleteResult;
1105
        }
1106

    
1107
        if (config.isDeleteChildren()) {
1108
            Set<DerivationEvent> derivationEvents = specimen.getDerivationEvents();
1109
            //clone to avoid concurrent modification
1110
            //can happen if the child is deleted and deleted its own derivedFrom event
1111
            Set<DerivationEvent> derivationEventsClone = new HashSet<>(derivationEvents);
1112
            for (DerivationEvent derivationEvent : derivationEventsClone) {
1113
                Set<DerivedUnit> derivatives = derivationEvent.getDerivatives();
1114
                Iterator<DerivedUnit> it = derivatives.iterator();
1115
                Set<DerivedUnit> derivativesToDelete = new HashSet<>();
1116
                while (it.hasNext()) {
1117
                    DerivedUnit unit = it.next();
1118
                    derivativesToDelete.add(unit);
1119
                }
1120
                for (DerivedUnit unit:derivativesToDelete){
1121
                    deleteResult.includeResult(delete(unit, config));
1122
                }
1123
            }
1124
        }
1125

    
1126

    
1127

    
1128

    
1129
        // check related objects
1130
        Set<CdmBase> relatedObjects = deleteResult.getRelatedObjects();
1131

    
1132
        for (CdmBase relatedObject : relatedObjects) {
1133
            // check for TypeDesignations
1134
            if (relatedObject.isInstanceOf(SpecimenTypeDesignation.class)) {
1135
                SpecimenTypeDesignation designation = HibernateProxyHelper.deproxy(relatedObject, SpecimenTypeDesignation.class);
1136
                designation.setTypeSpecimen(null);
1137
                List<TaxonName> typifiedNames = new ArrayList<>();
1138
                typifiedNames.addAll(designation.getTypifiedNames());
1139
                for (TaxonName taxonName : typifiedNames) {
1140
                    taxonName.removeTypeDesignation(designation);
1141
                }
1142
            }
1143
            // delete IndividualsAssociation
1144
            if (relatedObject.isInstanceOf(IndividualsAssociation.class)) {
1145
                IndividualsAssociation association = HibernateProxyHelper.deproxy(relatedObject, IndividualsAssociation.class);
1146
                association.setAssociatedSpecimenOrObservation(null);
1147
                association.getInDescription().removeElement(association);
1148
            }
1149
            // check for "described specimen" (deprecated)
1150
            if (relatedObject.isInstanceOf(TaxonDescription.class)) {
1151
                TaxonDescription description = HibernateProxyHelper.deproxy(relatedObject, TaxonDescription.class);
1152
                description.setDescribedSpecimenOrObservation(null);
1153
            }
1154
            // check for specimen description
1155
            if (relatedObject.isInstanceOf(SpecimenDescription.class)) {
1156
                SpecimenDescription specimenDescription = HibernateProxyHelper.deproxy(relatedObject, SpecimenDescription.class);
1157
                specimenDescription.setDescribedSpecimenOrObservation(null);
1158
                // check if description is a description of the given specimen
1159
                if (specimen.getDescriptions().contains(specimenDescription)) {
1160
                    specimen.removeDescription(specimenDescription);
1161
                }
1162
                DeleteResult descriptionDelete = descriptionService.isDeletable(specimenDescription.getUuid(), null);
1163
                if (descriptionDelete.isOk()){
1164
                    deleteResult.includeResult(descriptionService.delete(specimenDescription));
1165
                }
1166
            }
1167
            // check for amplification
1168
            if (relatedObject.isInstanceOf(AmplificationResult.class)) {
1169
                AmplificationResult amplificationResult = HibernateProxyHelper.deproxy(relatedObject, AmplificationResult.class);
1170
                amplificationResult.getDnaSample().removeAmplificationResult(amplificationResult);
1171
            }
1172
            // check for sequence
1173
            if (relatedObject.isInstanceOf(Sequence.class)) {
1174
                Sequence sequence = HibernateProxyHelper.deproxy(relatedObject, Sequence.class);
1175
                sequence.getDnaSample().removeSequence(sequence);
1176
            }
1177
            // check for children and parents (derivation events)
1178
            if (relatedObject.isInstanceOf(DerivationEvent.class)) {
1179
                DerivationEvent derivationEvent = HibernateProxyHelper.deproxy(relatedObject, DerivationEvent.class);
1180
                // parent derivation event (derivedFrom)
1181
                if (derivationEvent.getDerivatives().contains(specimen) && specimen.isInstanceOf(DerivedUnit.class)) {
1182
                    derivationEvent.removeDerivative(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class));
1183
                    if (derivationEvent.getDerivatives().isEmpty()) {
1184
                        Set<SpecimenOrObservationBase> originals = derivationEvent.getOriginals();
1185
                        for (SpecimenOrObservationBase specimenOrObservationBase : originals) {
1186
                            specimenOrObservationBase.removeDerivationEvent(derivationEvent);
1187
                            deleteResult.addUpdatedObject(specimenOrObservationBase);
1188
                        }
1189
                        // if derivationEvent has no derivates anymore, delete it
1190
                        deleteResult.includeResult(eventService.delete(derivationEvent));
1191
                    }
1192
                }
1193
                else{
1194
                    //child derivation events should not occur since we delete the hierarchy from bottom to top
1195
                }
1196
            }
1197
        }
1198
        if (specimen instanceof FieldUnit){
1199
            FieldUnit fieldUnit = HibernateProxyHelper.deproxy(specimen, FieldUnit.class);
1200
            GatheringEvent event = fieldUnit.getGatheringEvent();
1201
            fieldUnit.setGatheringEvent(null);
1202
            if (event != null){
1203
                DeleteResult result = eventService.isDeletable(event.getUuid(), null);
1204
                if (result.isOk()){
1205
                    deleteResult.includeResult( eventService.delete(event));
1206
                }
1207
            }
1208

    
1209
        }
1210
        deleteResult.includeResult(delete(specimen));
1211

    
1212
        return deleteResult;
1213
    }
1214

    
1215
    @Override
1216
    public Collection<IndividualsAssociation> listIndividualsAssociations(SpecimenOrObservationBase<?> specimen, Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1217
        return dao.listIndividualsAssociations(specimen, limit, start, orderHints, propertyPaths);
1218
    }
1219

    
1220
    /**
1221
     * {@inheritDoc}
1222
     */
1223
    @Override
1224
    public Collection<TaxonBase<?>> listAssociatedTaxa(SpecimenOrObservationBase<?> specimen, Integer limit,
1225
            Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1226
        return listAssociatedTaxa(specimen, INCLUDE_UNPUBLISHED, limit, start, orderHints, propertyPaths);
1227
    }
1228
    @Override
1229
    public Collection<TaxonBase<?>> listAssociatedTaxa(SpecimenOrObservationBase<?> specimen, boolean includeUnpublished,
1230
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1231
        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
1232

    
1233
        //individuals associations
1234
        associatedTaxa.addAll(listIndividualsAssociationTaxa(specimen, includeUnpublished, limit, start, orderHints, propertyPaths));
1235
        //type designation
1236
        if(specimen.isInstanceOf(DerivedUnit.class)){
1237
            associatedTaxa.addAll(listTypeDesignationTaxa(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class),
1238
                  includeUnpublished, limit, start, orderHints, propertyPaths));
1239
        }
1240
        //determinations
1241
        associatedTaxa.addAll(listDeterminedTaxa(specimen, includeUnpublished, limit, start, orderHints, propertyPaths));
1242

    
1243
        return associatedTaxa;
1244
    }
1245

    
1246

    
1247

    
1248
    /**
1249
     * {@inheritDoc}
1250
     */
1251
    @Override
1252
    public Collection<TaxonBase<?>> listDeterminedTaxa(SpecimenOrObservationBase<?> specimen, Integer limit,
1253
            Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1254
        return listDeterminedTaxa(specimen, INCLUDE_UNPUBLISHED, limit, start, orderHints, propertyPaths);
1255
    }
1256
    @Override
1257
    public Collection<TaxonBase<?>> listDeterminedTaxa(SpecimenOrObservationBase<?> specimen, boolean includeUnpublished, Integer limit, Integer start,
1258
            List<OrderHint> orderHints, List<String> propertyPaths) {
1259
        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
1260
        for (DeterminationEvent determinationEvent : listDeterminationEvents(specimen, limit, start, orderHints, propertyPaths)) {
1261
            if(determinationEvent.getIdentifiedUnit().equals(specimen)){
1262
                if(determinationEvent.getTaxon()!=null){
1263
                    associatedTaxa.add(taxonService.load(determinationEvent.getTaxon().getUuid(), includeUnpublished, propertyPaths));
1264
                }
1265
                if(determinationEvent.getTaxonName()!=null){
1266
                    Collection<TaxonBase> taxonBases = determinationEvent.getTaxonName().getTaxonBases();
1267
                    for (TaxonBase taxonBase : taxonBases) {
1268
                        associatedTaxa.add(taxonService.load(taxonBase.getUuid(), includeUnpublished, propertyPaths));
1269
                    }
1270
                }
1271
            }
1272
        }
1273
        return associatedTaxa;
1274
    }
1275

    
1276
    /**
1277
     * {@inheritDoc}
1278
     */
1279
    @Override
1280
    public Collection<TaxonBase<?>> listTypeDesignationTaxa(DerivedUnit specimen, Integer limit, Integer start,
1281
            List<OrderHint> orderHints, List<String> propertyPaths) {
1282
        return listTypeDesignationTaxa(specimen, INCLUDE_UNPUBLISHED, limit, start, orderHints, propertyPaths);
1283
    }
1284
    @Override
1285
    public Collection<TaxonBase<?>> listTypeDesignationTaxa(DerivedUnit specimen, boolean includeUnpublished,
1286
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1287
        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
1288
        for (SpecimenTypeDesignation typeDesignation : listTypeDesignations(specimen, limit, start, orderHints, propertyPaths)) {
1289
            if(typeDesignation.getTypeSpecimen().equals(specimen)){
1290
                Set<TaxonName> typifiedNames = typeDesignation.getTypifiedNames();
1291
                for (TaxonName taxonName : typifiedNames) {
1292
                    Set<Taxon> taxa = taxonName.getTaxa();
1293
                    for (Taxon taxon : taxa) {
1294
                        associatedTaxa.add(taxonService.load(taxon.getUuid(), includeUnpublished, propertyPaths));
1295
                    }
1296
                }
1297
            }
1298
        }
1299
        return associatedTaxa;
1300
    }
1301

    
1302
    /**
1303
     * {@inheritDoc}
1304
     */
1305
    @Override
1306
    public Collection<TaxonBase<?>> listIndividualsAssociationTaxa(SpecimenOrObservationBase<?> specimen, Integer limit,
1307
            Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1308
        return listIndividualsAssociationTaxa(specimen, INCLUDE_UNPUBLISHED, limit, start, orderHints, propertyPaths);
1309
    }
1310

    
1311
    @Override
1312
    public Collection<TaxonBase<?>> listIndividualsAssociationTaxa(SpecimenOrObservationBase<?> specimen, boolean includeUnpublished,
1313
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1314
        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
1315
        for (IndividualsAssociation individualsAssociation : listIndividualsAssociations(specimen, null, null, null, null)) {
1316
            if(individualsAssociation.getInDescription().isInstanceOf(TaxonDescription.class)){
1317
                TaxonDescription taxonDescription = HibernateProxyHelper.deproxy(individualsAssociation.getInDescription(), TaxonDescription.class);
1318
                if(taxonDescription.getTaxon()!=null){
1319
                    associatedTaxa.add(taxonService.load(taxonDescription.getTaxon().getUuid(), includeUnpublished, propertyPaths));
1320
                }
1321
            }
1322
        }
1323
        return associatedTaxa;
1324
    }
1325

    
1326
    @Override
1327
    public Collection<DeterminationEvent> listDeterminationEvents(SpecimenOrObservationBase<?> specimen,
1328
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1329
        return dao.listDeterminationEvents(specimen, limit, start, orderHints, propertyPaths);
1330
    }
1331

    
1332
    @Override
1333
    public Map<DerivedUnit, Collection<SpecimenTypeDesignation>> listTypeDesignations(
1334
            Collection<DerivedUnit> specimens, Integer limit, Integer start,
1335
            List<OrderHint> orderHints, List<String> propertyPaths) {
1336
        Map<DerivedUnit, Collection<SpecimenTypeDesignation>> typeDesignationMap = new HashMap<>();
1337
        for (DerivedUnit specimen : specimens) {
1338
            Collection<SpecimenTypeDesignation> typeDesignations = listTypeDesignations(specimen, limit, start, orderHints, propertyPaths);
1339
            typeDesignationMap.put(specimen, typeDesignations);
1340
        }
1341
        return typeDesignationMap;
1342
    }
1343

    
1344
    @Override
1345
    public Collection<SpecimenTypeDesignation> listTypeDesignations(DerivedUnit specimen,
1346
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1347
        return dao.listTypeDesignations(specimen, limit, start, orderHints, propertyPaths);
1348
    }
1349

    
1350
    @Override
1351
    public Collection<DescriptionBase<?>> listDescriptionsWithDescriptionSpecimen(
1352
            SpecimenOrObservationBase<?> specimen, Integer limit, Integer start, List<OrderHint> orderHints,
1353
            List<String> propertyPaths) {
1354
        return dao.listDescriptionsWithDescriptionSpecimen(specimen, limit, start, orderHints, propertyPaths);
1355
    }
1356

    
1357
    @Override
1358
    public Collection<DescriptionElementBase> getCharacterDataForSpecimen(UUID specimenUuid) {
1359
        SpecimenOrObservationBase<?> specimen = load(specimenUuid);
1360
        if (specimen != null) {
1361
            return specimen.characterData();
1362
        }
1363
        else{
1364
            throw new DataRetrievalFailureException("Specimen with the given uuid not found in the data base");
1365
        }
1366
    }
1367

    
1368
    @Override
1369
    public long countByTitle(IIdentifiableEntityServiceConfigurator<SpecimenOrObservationBase> config){
1370
        if (config instanceof FindOccurrencesConfigurator) {
1371
            FindOccurrencesConfigurator occurrenceConfig = (FindOccurrencesConfigurator) config;
1372
            Taxon taxon = null;
1373
            if(occurrenceConfig.getAssociatedTaxonUuid()!=null){
1374
                TaxonBase<?> taxonBase = taxonService.load(occurrenceConfig.getAssociatedTaxonUuid());
1375
                if(taxonBase.isInstanceOf(Taxon.class)){
1376
                    taxon = HibernateProxyHelper.deproxy(taxonBase, Taxon.class);
1377
                }
1378
            }
1379
            TaxonName taxonName = null;
1380
            if(occurrenceConfig.getAssociatedTaxonNameUuid()!=null){
1381
                taxonName = nameService.load(occurrenceConfig.getAssociatedTaxonNameUuid());
1382
            }
1383
            /*TODO: #6484 Neither isRetrieveIndirectlyAssociatedSpecimens() nor the AssignmentStatus
1384
             * is currently reflected in the HQL query. So using these in the count method will
1385
             * significantly slow down this method as we have to retrieve the entities instead of
1386
             * just the amount.
1387
             */
1388
            if(occurrenceConfig.isRetrieveIndirectlyAssociatedSpecimens() || !occurrenceConfig.getAssignmentStatus().equals(AssignmentStatus.ALL_SPECIMENS)){
1389
                List<SpecimenOrObservationBase> occurrences = new ArrayList<>();
1390
                occurrences.addAll(dao.findOccurrences(occurrenceConfig.getClazz(),
1391
                        occurrenceConfig.getTitleSearchStringSqlized(), occurrenceConfig.getSignificantIdentifier(),
1392
                        occurrenceConfig.getSpecimenType(), taxon, taxonName, occurrenceConfig.getMatchMode(), null, null,
1393
                        occurrenceConfig.getOrderHints(), occurrenceConfig.getPropertyPaths()));
1394
                occurrences = filterOccurencesByAssignmentAndHierarchy(occurrenceConfig, occurrences, taxon, taxonName);
1395
                return occurrences.size();
1396
            }
1397

    
1398
            return dao.countOccurrences(occurrenceConfig.getClazz(),
1399
                    occurrenceConfig.getTitleSearchStringSqlized(), occurrenceConfig.getSignificantIdentifier(),
1400
                    occurrenceConfig.getSpecimenType(), taxon, taxonName, occurrenceConfig.getMatchMode(), null, null,
1401
                    occurrenceConfig.getOrderHints(), occurrenceConfig.getPropertyPaths());
1402
        }
1403
        else{
1404
            return super.countByTitle(config);
1405
        }
1406
    }
1407

    
1408
    @Override
1409
    public Pager<UuidAndTitleCache<SpecimenOrObservationBase>> findByTitleUuidAndTitleCache(
1410
            FindOccurrencesConfigurator config){
1411
        List<UuidAndTitleCache<SpecimenOrObservationBase>> occurrences = new ArrayList<>();
1412
        Taxon taxon = null;
1413
        if(config.getAssociatedTaxonUuid()!=null){
1414
            TaxonBase<?> taxonBase = taxonService.load(config.getAssociatedTaxonUuid());
1415
            if(taxonBase.isInstanceOf(Taxon.class)){
1416
                taxon = CdmBase.deproxy(taxonBase, Taxon.class);
1417
            }
1418
        }
1419
        TaxonName taxonName = null;
1420
        if(config.getAssociatedTaxonNameUuid()!=null){
1421
            taxonName = nameService.load(config.getAssociatedTaxonNameUuid());
1422
        }
1423
        occurrences.addAll(dao.findOccurrencesUuidAndTitleCache(config.getClazz(),
1424
                config.getTitleSearchString(), config.getSignificantIdentifier(),
1425
                config.getSpecimenType(), taxon, taxonName, config.getMatchMode(), null, null,
1426
                config.getOrderHints()));
1427

    
1428
        return new DefaultPagerImpl<>(config.getPageNumber(), occurrences.size(), config.getPageSize(), occurrences);
1429
    }
1430

    
1431
    @Override
1432
    public List<DerivedUnitDTO> findByTitleDerivedUnitDTO(FindOccurrencesConfigurator config) {
1433
        Taxon taxon = null;
1434
        if(config.getAssociatedTaxonUuid()!=null){
1435
            TaxonBase<?> taxonBase = taxonService.load(config.getAssociatedTaxonUuid());
1436
            if(taxonBase.isInstanceOf(Taxon.class)){
1437
                taxon = CdmBase.deproxy(taxonBase, Taxon.class);
1438
            }
1439
        }
1440
        TaxonName taxonName = null;
1441
        if(config.getAssociatedTaxonNameUuid()!=null){
1442
            taxonName = nameService.load(config.getAssociatedTaxonNameUuid());
1443
        }
1444
        List<DerivedUnit> occurrences = new ArrayList<>();
1445
        occurrences.addAll(dao.findOccurrences(DerivedUnit.class,
1446
                config.getTitleSearchString(), config.getSignificantIdentifier(),
1447
                config.getSpecimenType(), taxon, taxonName, config.getMatchMode(), null, null,
1448
                config.getOrderHints(), null));
1449

    
1450
        List<DerivedUnitDTO> dtos = new ArrayList<>();
1451
        occurrences.forEach(derivedUnit->dtos.add(assembleDerivedUnitDTO(derivedUnit)));
1452
        return dtos;
1453
    }
1454

    
1455
    @Override
1456
    public <S extends SpecimenOrObservationBase> Pager<S> findByTitle(
1457
            IIdentifiableEntityServiceConfigurator<S> config) {
1458
        if (config instanceof FindOccurrencesConfigurator) {
1459
            FindOccurrencesConfigurator occurrenceConfig = (FindOccurrencesConfigurator) config;
1460
            List<SpecimenOrObservationBase> occurrences = new ArrayList<>();
1461
            Taxon taxon = null;
1462
            if(occurrenceConfig.getAssociatedTaxonUuid()!=null){
1463
                TaxonBase<?> taxonBase = taxonService.load(occurrenceConfig.getAssociatedTaxonUuid());
1464
                if(taxonBase.isInstanceOf(Taxon.class)){
1465
                    taxon = HibernateProxyHelper.deproxy(taxonBase, Taxon.class);
1466
                }
1467
            }
1468
            TaxonName taxonName = null;
1469
            if(occurrenceConfig.getAssociatedTaxonNameUuid()!=null){
1470
                taxonName = nameService.load(occurrenceConfig.getAssociatedTaxonNameUuid());
1471
            }
1472
            List<? extends SpecimenOrObservationBase> foundOccurrences = dao.findOccurrences(occurrenceConfig.getClazz(),
1473
                    occurrenceConfig.getTitleSearchString(), occurrenceConfig.getSignificantIdentifier(),
1474
                    occurrenceConfig.getSpecimenType(), taxon, taxonName, occurrenceConfig.getMatchMode(), null, null,
1475
                    occurrenceConfig.getOrderHints(), occurrenceConfig.getPropertyPaths());
1476
            occurrences.addAll(foundOccurrences);
1477
            occurrences = filterOccurencesByAssignmentAndHierarchy(occurrenceConfig, occurrences, taxon, taxonName);
1478

    
1479
            return new DefaultPagerImpl<>(config.getPageNumber(), occurrences.size(), config.getPageSize(), (List<S>)occurrences);
1480
        }
1481
        return super.findByTitle(config);
1482
    }
1483

    
1484
    private List<SpecimenOrObservationBase> filterOccurencesByAssignmentAndHierarchy(
1485
            FindOccurrencesConfigurator occurrenceConfig, List<SpecimenOrObservationBase> occurrences, Taxon taxon,
1486
            TaxonName taxonName) {
1487
        //filter out (un-)assigned specimens
1488
        if(taxon==null && taxonName==null){
1489
            AssignmentStatus assignmentStatus = occurrenceConfig.getAssignmentStatus();
1490
            List<SpecimenOrObservationBase> specimenWithAssociations = new ArrayList<>();
1491
            if(!assignmentStatus.equals(AssignmentStatus.ALL_SPECIMENS)){
1492
                for (SpecimenOrObservationBase specimenOrObservationBase : occurrences) {
1493
                    boolean includeUnpublished = true;  //TODO not sure if this is correct, maybe we have to propagate publish flag to higher methods.
1494
                    Collection<TaxonBase<?>> associatedTaxa = listAssociatedTaxa(specimenOrObservationBase,
1495
                            includeUnpublished, null, null, null, null);
1496
                    if(!associatedTaxa.isEmpty()){
1497
                        specimenWithAssociations.add(specimenOrObservationBase);
1498
                    }
1499
                }
1500
            }
1501
            if(assignmentStatus.equals(AssignmentStatus.UNASSIGNED_SPECIMENS)){
1502
                occurrences.removeAll(specimenWithAssociations);
1503
            }
1504
            if(assignmentStatus.equals(AssignmentStatus.ASSIGNED_SPECIMENS)){
1505
                occurrences = new ArrayList<>(specimenWithAssociations);
1506
            }
1507
        }
1508
        // indirectly associated specimens
1509
        if(occurrenceConfig.isRetrieveIndirectlyAssociatedSpecimens()){
1510
            List<SpecimenOrObservationBase> indirectlyAssociatedOccurrences = new ArrayList<>(occurrences);
1511
            for (SpecimenOrObservationBase<?> specimen : occurrences) {
1512
                List<SpecimenOrObservationBase<?>> allHierarchyDerivates = getAllHierarchyDerivatives(specimen);
1513
                for (SpecimenOrObservationBase<?> specimenOrObservationBase : allHierarchyDerivates) {
1514
                    if(!occurrences.contains(specimenOrObservationBase)){
1515
                        indirectlyAssociatedOccurrences.add(specimenOrObservationBase);
1516
                    }
1517
                }
1518
            }
1519
            occurrences = indirectlyAssociatedOccurrences;
1520
        }
1521
        return occurrences;
1522
    }
1523

    
1524
    @Override
1525
    public List<SpecimenOrObservationBase<?>> getAllHierarchyDerivatives(SpecimenOrObservationBase<?> specimen){
1526
        List<SpecimenOrObservationBase<?>> allHierarchyDerivatives = new ArrayList<>();
1527
        Collection<FieldUnit> fieldUnits = findFieldUnits(specimen.getUuid(), null);
1528
        if(fieldUnits.isEmpty()){
1529
            allHierarchyDerivatives.add(specimen);
1530
            allHierarchyDerivatives.addAll(getAllChildDerivatives(specimen));
1531
        }
1532
        else{
1533
            for (FieldUnit fieldUnit : fieldUnits) {
1534
                allHierarchyDerivatives.add(fieldUnit);
1535
                allHierarchyDerivatives.addAll(getAllChildDerivatives(fieldUnit));
1536
            }
1537
        }
1538
        return allHierarchyDerivatives;
1539
    }
1540

    
1541
    @Override
1542
    public List<DerivedUnit> getAllChildDerivatives(UUID specimenUuid){
1543
        return getAllChildDerivatives(load(specimenUuid));
1544
    }
1545

    
1546
    @Override
1547
    public List<DerivedUnit> getAllChildDerivatives(SpecimenOrObservationBase<?> specimen){
1548
        if (specimen == null){
1549
            return null;
1550
        }
1551
        List<DerivedUnit> childDerivate = new ArrayList<>();
1552
        Set<DerivationEvent> derivationEvents = specimen.getDerivationEvents();
1553
        for (DerivationEvent derivationEvent : derivationEvents) {
1554
            Set<DerivedUnit> derivatives = derivationEvent.getDerivatives();
1555
            for (DerivedUnit derivedUnit : derivatives) {
1556
                childDerivate.add(derivedUnit);
1557
                childDerivate.addAll(getAllChildDerivatives(derivedUnit.getUuid()));
1558
            }
1559
        }
1560
        return childDerivate;
1561
    }
1562

    
1563
    @Override
1564
    public long countOccurrences(IIdentifiableEntityServiceConfigurator<SpecimenOrObservationBase> config){
1565
        return countByTitle(config);
1566
    }
1567

    
1568
    @Override
1569
    public List<FieldUnit> findFieldUnitsForGatheringEvent(UUID gatheringEventUuid) {
1570
        return dao.findFieldUnitsForGatheringEvent(gatheringEventUuid, null, null, null, null);
1571
    }
1572

    
1573
    @Override
1574
    public List<Point> findPointsForFieldUnitList(List<UUID> fieldUnitUuids) {
1575
        return dao.findPointsForFieldUnitList(fieldUnitUuids);
1576
    }
1577
}
(74-74/97)