Project

General

Profile

Download (80.1 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.MediaDTO;
54
import eu.etaxonomy.cdm.api.service.dto.SpecimenOrObservationBaseDTO;
55
import eu.etaxonomy.cdm.api.service.dto.SpecimenOrObservationDTOFactory;
56
import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;
57
import eu.etaxonomy.cdm.api.service.molecular.ISequenceService;
58
import eu.etaxonomy.cdm.api.service.pager.Pager;
59
import eu.etaxonomy.cdm.api.service.pager.impl.AbstractPagerImpl;
60
import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
61
import eu.etaxonomy.cdm.api.service.search.ILuceneIndexToolProvider;
62
import eu.etaxonomy.cdm.api.service.search.ISearchResultBuilder;
63
import eu.etaxonomy.cdm.api.service.search.LuceneParseException;
64
import eu.etaxonomy.cdm.api.service.search.LuceneSearch;
65
import eu.etaxonomy.cdm.api.service.search.QueryFactory;
66
import eu.etaxonomy.cdm.api.service.search.SearchResult;
67
import eu.etaxonomy.cdm.api.service.search.SearchResultBuilder;
68
import eu.etaxonomy.cdm.api.util.TaxonRelationshipEdge;
69
import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
70
import eu.etaxonomy.cdm.compare.common.PartialComparator;
71
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
72
import eu.etaxonomy.cdm.model.CdmBaseType;
73
import eu.etaxonomy.cdm.model.common.CdmBase;
74
import eu.etaxonomy.cdm.model.common.Language;
75
import eu.etaxonomy.cdm.model.description.DescriptionBase;
76
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
77
import eu.etaxonomy.cdm.model.description.IndividualsAssociation;
78
import eu.etaxonomy.cdm.model.description.SpecimenDescription;
79
import eu.etaxonomy.cdm.model.description.TaxonDescription;
80
import eu.etaxonomy.cdm.model.location.Point;
81
import eu.etaxonomy.cdm.model.media.Media;
82
import eu.etaxonomy.cdm.model.molecular.AmplificationResult;
83
import eu.etaxonomy.cdm.model.molecular.DnaSample;
84
import eu.etaxonomy.cdm.model.molecular.Sequence;
85
import eu.etaxonomy.cdm.model.molecular.SingleRead;
86
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
87
import eu.etaxonomy.cdm.model.name.TaxonName;
88
import eu.etaxonomy.cdm.model.occurrence.DerivationEvent;
89
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
90
import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
91
import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
92
import eu.etaxonomy.cdm.model.occurrence.GatheringEvent;
93
import eu.etaxonomy.cdm.model.occurrence.MediaSpecimen;
94
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
95
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;
96
import eu.etaxonomy.cdm.model.taxon.Taxon;
97
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
98
import eu.etaxonomy.cdm.persistence.dao.initializer.AbstractBeanInitializer;
99
import eu.etaxonomy.cdm.persistence.dao.occurrence.IOccurrenceDao;
100
import eu.etaxonomy.cdm.persistence.dto.SpecimenNodeWrapper;
101
import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
102
import eu.etaxonomy.cdm.persistence.query.AssignmentStatus;
103
import eu.etaxonomy.cdm.persistence.query.OrderHint;
104
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
105

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

    
116
    static private final Logger logger = Logger.getLogger(OccurrenceServiceImpl.class);
117

    
118
    @Autowired
119
    private IDescriptionService descriptionService;
120

    
121
    @Autowired
122
    private INameService nameService;
123

    
124
    @Autowired
125
    private IEventBaseService eventService;
126

    
127
    @Autowired
128
    private ITaxonService taxonService;
129

    
130
    @Autowired
131
    private ISequenceService sequenceService;
132

    
133
    @Autowired
134
    private AbstractBeanInitializer<?> beanInitializer;
135

    
136
    @Autowired
137
    private ILuceneIndexToolProvider luceneIndexToolProvider;
138

    
139
    public OccurrenceServiceImpl() {
140
        logger.debug("Load OccurrenceService Bean");
141
    }
142

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

    
152
    @Override
153
    @Autowired
154
    protected void setDao(IOccurrenceDao dao) {
155
        this.dao = dao;
156
    }
157

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

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

    
167
        return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
168
    }
169

    
170
    @Override
171
    public long countDeterminations(SpecimenOrObservationBase occurence, TaxonBase taxonbase) {
172
        return dao.countDeterminations(occurence, taxonbase);
173
    }
174

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

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

    
185
        return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
186
    }
187

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

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

    
197
        return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
198
    }
199

    
200
    @Override
201
    public Pager<MediaDTO> getMediaDTOs(SpecimenOrObservationBase<?> occurence, Integer pageSize, Integer pageNumber) {
202
        long numberOfResults = dao.countMedia(occurence);
203

    
204
        List<Media> results = new ArrayList<>();
205
        if(AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)) {
206
            results = dao.getMedia(occurence, pageSize, pageNumber, null);
207
        }
208
        List<MediaDTO> mediaDTOs = results.stream()
209
                .map(m -> MediaDTO.fromEntity(m))
210
                .flatMap(dtos -> dtos.stream())
211
                .collect(Collectors.toList()
212
                );
213
        return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, mediaDTOs);
214
    }
215

    
216
    @Override
217
    public Pager<Media> getMediainHierarchy(SpecimenOrObservationBase rootOccurence, Integer pageSize,
218
            Integer pageNumber, List<String> propertyPaths) {
219
        List<Media> media = new ArrayList<>();
220
        //media specimens
221
        if(rootOccurence.isInstanceOf(MediaSpecimen.class)){
222
            MediaSpecimen mediaSpecimen = HibernateProxyHelper.deproxy(rootOccurence, MediaSpecimen.class);
223
            media.add(mediaSpecimen.getMediaSpecimen());
224
        }
225
        // pherograms & gelPhotos
226
        if (rootOccurence.isInstanceOf(DnaSample.class)) {
227
            DnaSample dnaSample = CdmBase.deproxy(rootOccurence, DnaSample.class);
228
            Set<Sequence> sequences = dnaSample.getSequences();
229
            //we do show only those gelPhotos which lead to a consensus sequence
230
            for (Sequence sequence : sequences) {
231
                Set<Media> dnaRelatedMedia = new HashSet<>();
232
                for (SingleRead singleRead : sequence.getSingleReads()){
233
                    AmplificationResult amplification = singleRead.getAmplificationResult();
234
                    dnaRelatedMedia.add(amplification.getGelPhoto());
235
                    dnaRelatedMedia.add(singleRead.getPherogram());
236
                    dnaRelatedMedia.remove(null);
237
                }
238
                media.addAll(dnaRelatedMedia);
239
            }
240
        }
241
        if(rootOccurence.isInstanceOf(DerivedUnit.class)){
242
            DerivedUnit derivedUnit = HibernateProxyHelper.deproxy(rootOccurence, DerivedUnit.class);
243
            for (DerivationEvent derivationEvent : derivedUnit.getDerivationEvents()) {
244
                for (DerivedUnit childDerivative : derivationEvent.getDerivatives()) {
245
                    media.addAll(getMediainHierarchy(childDerivative, pageSize, pageNumber, propertyPaths).getRecords());
246
                }
247
            }
248
        }
249
        return new DefaultPagerImpl<>(pageNumber, Long.valueOf(media.size()), pageSize, media);
250
    }
251

    
252
    @Override
253
    public Pager<SpecimenOrObservationBase> list(Class<? extends SpecimenOrObservationBase> type, TaxonName determinedAs,
254
            Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
255
        long numberOfResults = dao.count(type, determinedAs);
256
        @SuppressWarnings("rawtypes")
257
        List<SpecimenOrObservationBase> results = new ArrayList<>();
258
        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
259
            results = dao.list(type, determinedAs, pageSize, pageNumber, orderHints, propertyPaths);
260
        }
261
        return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
262
    }
263

    
264
    @Override
265
    public Pager<SpecimenOrObservationBase> list(Class<? extends SpecimenOrObservationBase> type, TaxonBase determinedAs,
266
            Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
267
        long numberOfResults = dao.count(type, determinedAs);
268
        @SuppressWarnings("rawtypes")
269
        List<SpecimenOrObservationBase> results = new ArrayList<>();
270
        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
271
            results = dao.list(type, determinedAs, pageSize, pageNumber, orderHints, propertyPaths);
272
        }
273
        return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
274
    }
275

    
276
    @Override
277
    public List<UuidAndTitleCache<DerivedUnit>> getDerivedUnitUuidAndTitleCache(Integer limit, String pattern) {
278
        return dao.getDerivedUnitUuidAndTitleCache(limit, pattern);
279
    }
280

    
281
    @Override
282
    public List<UuidAndTitleCache<FieldUnit>> getFieldUnitUuidAndTitleCache() {
283
        return dao.getFieldUnitUuidAndTitleCache();
284
    }
285

    
286
    @Override
287
    public DerivedUnitFacade getDerivedUnitFacade(DerivedUnit derivedUnit, List<String> derivedUnitFacadeInitStrategy) throws DerivedUnitFacadeNotSupportedException {
288
        derivedUnit = (DerivedUnit) dao.load(derivedUnit.getUuid(), null);
289
        DerivedUnitFacadeConfigurator config = DerivedUnitFacadeConfigurator.NewInstance();
290
        config.setThrowExceptionForNonSpecimenPreservationMethodRequest(false);
291
        DerivedUnitFacade derivedUnitFacade = DerivedUnitFacade.NewInstance(derivedUnit, config);
292
        beanInitializer.initialize(derivedUnitFacade, derivedUnitFacadeInitStrategy);
293
        return derivedUnitFacade;
294
    }
295

    
296
    @Override
297
    public List<DerivedUnitFacade> listDerivedUnitFacades(
298
            DescriptionBase description, List<String> derivedUnitFacadeInitStrategy) {
299

    
300
        List<DerivedUnitFacade> derivedUnitFacadeList = new ArrayList<>();
301
        IndividualsAssociation tempIndividualsAssociation;
302
        SpecimenOrObservationBase tempSpecimenOrObservationBase;
303
        List<IndividualsAssociation> elements = descriptionService.listDescriptionElements(description, null, IndividualsAssociation.class, null, 0, Arrays.asList(new String []{"associatedSpecimenOrObservation"}));
304
        for (IndividualsAssociation element : elements) {
305
            tempIndividualsAssociation = HibernateProxyHelper.deproxy(element, IndividualsAssociation.class);
306
            if (tempIndividualsAssociation.getAssociatedSpecimenOrObservation() != null) {
307
                tempSpecimenOrObservationBase = HibernateProxyHelper.deproxy(tempIndividualsAssociation.getAssociatedSpecimenOrObservation(), SpecimenOrObservationBase.class);
308
                if (tempSpecimenOrObservationBase.isInstanceOf(DerivedUnit.class)) {
309
                    try {
310
                        derivedUnitFacadeList.add(DerivedUnitFacade.NewInstance(HibernateProxyHelper.deproxy(tempSpecimenOrObservationBase, DerivedUnit.class)));
311
                    } catch (DerivedUnitFacadeNotSupportedException e) {
312
                        logger.warn(tempIndividualsAssociation.getAssociatedSpecimenOrObservation().getTitleCache() + " : " + e.getMessage());
313
                    }
314
                }
315
            }
316
        }
317

    
318
        beanInitializer.initializeAll(derivedUnitFacadeList, derivedUnitFacadeInitStrategy);
319

    
320
        return derivedUnitFacadeList;
321
    }
322

    
323

    
324
    @Override
325
    public <T extends SpecimenOrObservationBase> List<T> listByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,
326
            Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
327

    
328
        return pageByAssociatedTaxon(type, includeRelationships, associatedTaxon, maxDepth, pageSize, pageNumber, orderHints, propertyPaths).getRecords();
329
    }
330

    
331
    @Override
332
    public Collection<SpecimenNodeWrapper> listUuidAndTitleCacheByAssociatedTaxon(List<UUID> taxonNodeUuids,
333
            Integer limit, Integer start) {
334
        return dao.listUuidAndTitleCacheByAssociatedTaxon(taxonNodeUuids, limit, start);
335
        }
336

    
337
    @Override
338
    @Deprecated
339
    public Collection<FieldUnit> listFieldUnitsByAssociatedTaxon(Taxon associatedTaxon, List<OrderHint> orderHints, List<String> propertyPaths) {
340
        return pageRootUnitsByAssociatedTaxon(FieldUnit.class, null, associatedTaxon, null, null, null, null, propertyPaths).getRecords();
341
    }
342

    
343
    @Override
344
    public <T extends SpecimenOrObservationBase> Collection<T> listRootUnitsByAssociatedTaxon(Class<T> type, Taxon associatedTaxon, List<OrderHint> orderHints, List<String> propertyPaths) {
345
        return pageRootUnitsByAssociatedTaxon(type, null, associatedTaxon, null, null, null, null, propertyPaths).getRecords();
346
    }
347

    
348
    @Override
349
    public <T extends SpecimenOrObservationBase> Pager<T> pageRootUnitsByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,
350
            Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
351
            List<String> propertyPaths) {
352

    
353
        if (!getSession().contains(associatedTaxon)) {
354
            associatedTaxon = (Taxon) taxonService.load(associatedTaxon.getUuid());
355
        }
356

    
357
        // gather the IDs of all relevant root units
358
        Set<UUID> rootUnitUuids = new HashSet<>();
359
        List<SpecimenOrObservationBase> records = listByAssociatedTaxon(null, includeRelationships, associatedTaxon, maxDepth, null, null, orderHints, propertyPaths);
360
        for (SpecimenOrObservationBase<?> specimen : records) {
361
            for (SpecimenOrObservationBase<?> rootUnit : findRootUnits(specimen.getUuid(), null)) {
362
                if(type == null || type.isAssignableFrom(rootUnit.getClass())) {
363
                    rootUnitUuids.add(rootUnit.getUuid());
364
                }
365
            }
366
        }
367
        long totalCount = rootUnitUuids.size();
368
        //dao.list() does the paging of the field units. Passing the field units directly to the Pager would not work
369
        List<SpecimenOrObservationBase> rootUnits = dao.list(rootUnitUuids, pageSize, pageNumber, orderHints, propertyPaths);
370
        List<T> castedUnits = new ArrayList<>(rootUnits.size());
371
        for(SpecimenOrObservationBase<?> sob : rootUnits) {
372
            // this cast should be save since the uuids have been filtered by type above
373
            castedUnits.add((T)sob);
374
        }
375
        return new DefaultPagerImpl<>(pageNumber, totalCount, pageSize, castedUnits);
376
    }
377

    
378
    @Override
379
    public FieldUnitDTO assembleFieldUnitDTO(FieldUnit fieldUnit) {
380

    
381
        if (!getSession().contains(fieldUnit)) {
382
            fieldUnit = (FieldUnit) load(fieldUnit.getUuid());
383
        }
384
        // FIXME the filter for SpecimenOrObservationType.PreservedSpecimen has been preserved from the former implementation (see commit 07e3f63c7d  and older)
385
        // it is questionable if this filter makes sense for all use cases or if it is only a sensible default for the
386
        // compressed specimen table in the cdm-dataportal (see #6816, #6870)
387
        EnumSet<SpecimenOrObservationType> typeIncludeFilter = EnumSet.of(SpecimenOrObservationType.PreservedSpecimen);
388
        FieldUnitDTO fieldUnitDTO = FieldUnitDTO.fromEntity(fieldUnit, null, typeIncludeFilter);
389
        return fieldUnitDTO;
390
    }
391

    
392

    
393
    @Override
394
    @Transactional
395
    public DerivedUnitDTO assembleDerivedUnitDTO(DerivedUnit derivedUnit) {
396

    
397
        if (!getSession().contains(derivedUnit)) {
398
            derivedUnit = (DerivedUnit) load(derivedUnit.getUuid());
399
        }
400
        DerivedUnitDTO derivedUnitDTO = DerivedUnitDTO.fromEntity(derivedUnit, null, null);
401

    
402
        // individuals associations
403
        Collection<IndividualsAssociation> individualsAssociations = listIndividualsAssociations(derivedUnit, null, null, null, null);
404
        if(individualsAssociations != null) {
405
            for (IndividualsAssociation individualsAssociation : individualsAssociations) {
406
                if (individualsAssociation.getInDescription() != null) {
407
                    if (individualsAssociation.getInDescription().isInstanceOf(TaxonDescription.class)) {
408
                        TaxonDescription taxonDescription = HibernateProxyHelper.deproxy(individualsAssociation.getInDescription(), TaxonDescription.class);
409
                        Taxon taxon = taxonDescription.getTaxon();
410
                        if (taxon != null) {
411
                            derivedUnitDTO.addAssociatedTaxon(taxon);
412
                        }
413
                    }
414
                }
415
            }
416
        }
417

    
418
        return derivedUnitDTO;
419
    }
420

    
421
    @Override
422
    @Deprecated
423
    public String getMostSignificantIdentifier(DerivedUnit derivedUnit) {
424
        return derivedUnit.getMostSignificantIdentifier();
425
    }
426

    
427
    /**
428
     * TODO there is a very similar function in {@link SpecimenOrObservationBaseDTO#assembleDerivative}.
429
     * If possible we should avoid using this function here by the method in <code>SpecimenOrObservationBaseDTO</code>.
430
     */
431
    private Set<DerivedUnitDTO> getDerivedUnitDTOsFor(SpecimenOrObservationBaseDTO specimenDto, DerivedUnit specimen,
432
            HashMap<UUID, SpecimenOrObservationBaseDTO> alreadyCollectedSpecimen) {
433

    
434
        Set<DerivedUnitDTO> derivedUnits = new HashSet<>();
435
        for (DerivationEvent derivationEvent : specimen.getDerivationEvents()) {
436
            for (DerivedUnit derivative : derivationEvent.getDerivatives()) {
437
                if (!alreadyCollectedSpecimen.containsKey(specimenDto.getUuid())){
438
                    DerivedUnitDTO dto = (DerivedUnitDTO) SpecimenOrObservationDTOFactory.fromEntity(derivative, 0);
439
                    alreadyCollectedSpecimen.put(dto.getUuid(), dto);
440
                    dto.addAllDerivatives(getDerivedUnitDTOsFor(dto, derivative, alreadyCollectedSpecimen));
441
                    derivedUnits.add(dto);
442
                } else {
443
                    if(alreadyCollectedSpecimen.get(specimenDto.getUuid()).getDerivatives().isEmpty() && !derivative.getDerivationEvents().isEmpty()) {
444
                        // we need to add the missing derivatives!
445
                        SpecimenOrObservationBaseDTO dto = alreadyCollectedSpecimen.get(specimenDto.getUuid());
446
                        alreadyCollectedSpecimen.get(specimenDto.getUuid()).addAllDerivatives(getDerivedUnitDTOsFor(dto, derivative, alreadyCollectedSpecimen));
447
                    }
448
                }
449
            }
450
        }
451
        return derivedUnits;
452
    }
453

    
454
//    private Set<DerivateDTO> getDerivedUnitDTOsFor(DerivateDTO specimenDto, DerivedUnit specimen, HashMap<UUID, DerivateDTO> alreadyCollectedSpecimen) {
455
//        Set<DerivateDTO> derivedUnits = new HashSet<>();
456
////        load
457
//        for (DerivationEvent derivationEvent : specimen.getDerivationEvents()) {
458
//            for (DerivedUnit derivative : derivationEvent.getDerivatives()) {
459
//                if (!alreadyCollectedSpecimen.containsKey(specimenDto.getUuid())){
460
//                    PreservedSpecimenDTO dto;
461
//                    if (derivative instanceof DnaSample){
462
//                        dto = DNASampleDTO.newInstance(derivative);
463
//                    }else{
464
//                        dto = PreservedSpecimenDTO.newInstance(derivative);
465
//                    }
466
//                    alreadyCollectedSpecimen.put(dto.getUuid(), dto);
467
//                    dto.addAllDerivates(getDerivedUnitDTOsFor(dto, derivative, alreadyCollectedSpecimen));
468
//                    derivedUnits.add(dto);
469
//                }
470
//            }
471
//        }
472
//        return derivedUnits;
473
//    }
474

    
475

    
476
    @SuppressWarnings("unchecked")
477
    @Override
478
    public <T extends SpecimenOrObservationBase> Pager<T> pageByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includedRelationships,
479
            Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
480

    
481
        Set<Taxon> taxa = new HashSet<>();
482
        Set<Integer> occurrenceIds = new HashSet<>();
483
        List<T> occurrences = new ArrayList<>();
484
        boolean includeUnpublished = INCLUDE_UNPUBLISHED;
485

    
486
        // Integer limit = PagerUtils.limitFor(pageSize);
487
        // Integer start = PagerUtils.startFor(pageSize, pageNumber);
488

    
489
        if (!getSession().contains(associatedTaxon)) {
490
            associatedTaxon = (Taxon) taxonService.load(associatedTaxon.getUuid());
491
        }
492

    
493
        if (includedRelationships != null) {
494
            taxa = taxonService.listRelatedTaxa(associatedTaxon, includedRelationships, maxDepth, includeUnpublished, null, null, propertyPaths);
495
        }
496

    
497
        taxa.add(associatedTaxon);
498

    
499
        for (Taxon taxon : taxa) {
500
            List<T> perTaxonOccurrences = dao.listByAssociatedTaxon(type, taxon, null, null, orderHints, propertyPaths);
501
            for (SpecimenOrObservationBase<?> o : perTaxonOccurrences) {
502
                occurrenceIds.add(o.getId());
503
            }
504
        }
505
        occurrences = (List<T>) dao.loadList(occurrenceIds, null, propertyPaths);
506

    
507
        return new DefaultPagerImpl<>(pageNumber, Long.valueOf(occurrences.size()), pageSize, occurrences);
508

    
509
    }
510

    
511
    @Override
512
    public <T extends SpecimenOrObservationBase> Pager<T> pageByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,
513
            String taxonUUID, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
514

    
515
        UUID uuid = UUID.fromString(taxonUUID);
516
        Taxon taxon = (Taxon) taxonService.load(uuid);
517
        return pageByAssociatedTaxon(type, includeRelationships, taxon, maxDepth, pageSize, pageNumber, orderHints, propertyPaths);
518

    
519
    }
520

    
521
    @Override
522
    @Transactional
523
    public List<SpecimenOrObservationBaseDTO> listRootUnitDTOsByAssociatedTaxon(Set<TaxonRelationshipEdge> includedRelationships,
524
            UUID associatedTaxonUuid, List<String> propertyPaths) {
525

    
526
        Set<Taxon> taxa = new HashSet<>();
527
        Set<SpecimenOrObservationBaseDTO> rootUnitDTOs = new HashSet<>();
528
        boolean includeUnpublished = INCLUDE_UNPUBLISHED;
529

    
530
        Taxon associatedTaxon = (Taxon) taxonService.load(associatedTaxonUuid);
531
        if (includedRelationships != null) {
532
            taxa = taxonService.listRelatedTaxa(associatedTaxon, includedRelationships, null, includeUnpublished, null, null, null);
533
        }
534
        taxa.add(associatedTaxon);
535

    
536
        HashMap<UUID, SpecimenOrObservationBaseDTO> alreadyCollectedUnits = new HashMap<>();
537
        for (Taxon taxon : taxa) {
538
            // TODO there might be a good potential to speed up the whole processing by collecting all entities first
539
            // and to create the DTOs in a second step
540
            Set<SpecimenOrObservationBase> perTaxonOccurrences = dao.listByAssociatedTaxon(null, taxon, null, null, null, propertyPaths)
541
                    .stream()
542
                    .map(u -> HibernateProxyHelper.deproxy(u, SpecimenOrObservationBase.class))
543
                    .collect(Collectors.toSet());
544
            for (SpecimenOrObservationBase<?> unit : perTaxonOccurrences) {
545
                unit = HibernateProxyHelper.deproxy(unit);
546
                if (unit instanceof DerivedUnit){
547
                    DerivedUnitDTO derivativeDTO;
548
                    if (!alreadyCollectedUnits.containsKey(unit.getUuid())){
549
                        DerivedUnit derivedUnit = (DerivedUnit)unit;
550
                        derivativeDTO = (DerivedUnitDTO) SpecimenOrObservationDTOFactory.fromEntity(derivedUnit, null);
551
                        if (unit instanceof DnaSample) {
552
                            derivativeDTO = DNASampleDTO.fromEntity((DnaSample)unit);
553
                        } else {
554
                            derivativeDTO = DerivedUnitDTO.fromEntity(derivedUnit, null, null);
555
                        }
556
                        alreadyCollectedUnits.put(derivativeDTO.getUuid(), derivativeDTO);
557
                        derivativeDTO.addAllDerivatives(getDerivedUnitDTOsFor(derivativeDTO, derivedUnit, alreadyCollectedUnits));
558
                    }
559
                    derivativeDTO = (DerivedUnitDTO) alreadyCollectedUnits.get(unit.getUuid());
560
                    rootUnitDTOs.addAll(findRootUnitDTOs(derivativeDTO, alreadyCollectedUnits));
561
                } else {
562
                    // only other option is FieldUnit
563
                    rootUnitDTOs.add(FieldUnitDTO.fromEntity((FieldUnit)unit, 0, null));
564
                }
565
            }
566
        }
567

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

    
573
            @Override
574
            public int compare(SpecimenOrObservationBaseDTO o1, SpecimenOrObservationBaseDTO o2) {
575
                if(o1 instanceof FieldUnitDTO && o2 instanceof FieldUnitDTO) {
576
                    FieldUnitDTO fu1 = (FieldUnitDTO)o1;
577
                    FieldUnitDTO fu2 = (FieldUnitDTO)o2;
578
                    //TODO if we want null values and values with missing year as smallest we should use
579
                    //PartialComparator.INSTANCE_NULL_SMALLEST() here
580
                    return PartialComparator.INSTANCE().compare(fu1.getDate(), fu2.getDate());
581
                }
582
                if(o1 instanceof DerivedUnitDTO && o2 instanceof DerivedUnitDTO) {
583
                    SpecimenOrObservationBaseDTO du1 = o1;
584
                    SpecimenOrObservationBaseDTO du2 = o2;
585
                    return StringUtils.compare(du1.getLabel(), du2.getLabel());
586
                 }
587
                if(o1 instanceof FieldUnitDTO && o2 instanceof DerivedUnitDTO) {
588
                    return -1;
589
                } else {
590
                    return 1;
591
                }
592
            }
593

    
594
        });
595

    
596
        return orderdDTOs;
597
    }
598

    
599
    @Override
600
    @Transactional
601
    @Deprecated
602
    public  SpecimenOrObservationBaseDTO findByAccessionNumber(String accessionNumberString, List<OrderHint> orderHints)  {
603
        return findByAccessionNumber(accessionNumberString, orderHints);
604
    }
605

    
606
    @Override
607
    @Transactional
608
    public  SpecimenOrObservationBaseDTO findByGeneticAccessionNumber(String accessionNumberString, List<OrderHint> orderHints)  {
609

    
610
        DnaSample dnaSample = dao.findByGeneticAccessionNumber(accessionNumberString, null);
611
        DerivedUnitDTO dnaSampleDTO;
612
        if (dnaSample != null){
613
            dnaSampleDTO = new DNASampleDTO(dnaSample);
614
            Collection<SpecimenOrObservationBaseDTO> fieldUnitDTOs = this.findRootUnitDTOs(dnaSampleDTO, new HashMap<>());
615
            // FIXME change return type to Collection<FieldUnitDTO>
616
            if(fieldUnitDTOs.isEmpty()) {
617
                return null;
618
            } else {
619
               return fieldUnitDTOs.iterator().next();
620
            }
621
        }
622
        return null;
623
    }
624

    
625
    @Override
626
    public Pager<SearchResult<SpecimenOrObservationBase>> findByFullText(
627
            Class<? extends SpecimenOrObservationBase> clazz, String queryString, Rectangle boundingBox, List<Language> languages,
628
            boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
629
            List<String> propertyPaths) throws IOException, LuceneParseException {
630

    
631
        LuceneSearch luceneSearch = prepareByFullTextSearch(clazz, queryString, boundingBox, languages, highlightFragments);
632

    
633
        // --- execute search
634
        TopGroups<BytesRef> topDocsResultSet;
635
        try {
636
            topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
637
        } catch (ParseException e) {
638
            LuceneParseException parseException = new LuceneParseException(e.getMessage());
639
            parseException.setStackTrace(e.getStackTrace());
640
            throw parseException;
641
        }
642

    
643
        Map<CdmBaseType, String> idFieldMap = new HashMap<>();
644
        idFieldMap.put(CdmBaseType.SPECIMEN_OR_OBSERVATIONBASE, "id");
645

    
646
        // --- initialize taxa, highlight matches ....
647
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
648
        @SuppressWarnings("rawtypes")
649
        List<SearchResult<SpecimenOrObservationBase>> searchResults = searchResultBuilder.createResultSet(
650
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
651

    
652
        int totalHits = topDocsResultSet != null ? topDocsResultSet.totalGroupCount : 0;
653

    
654
        return new DefaultPagerImpl<>(pageNumber, Long.valueOf(totalHits), pageSize, searchResults);
655
    }
656

    
657
    private LuceneSearch prepareByFullTextSearch(Class<? extends SpecimenOrObservationBase> clazz, String queryString, Rectangle bbox,
658
            List<Language> languages, boolean highlightFragments) {
659

    
660
        Builder finalQueryBuilder = new Builder();
661
        Builder textQueryBuilder = new Builder();
662

    
663
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, FieldUnit.class);
664
        QueryFactory queryFactory = luceneIndexToolProvider.newQueryFactoryFor(FieldUnit.class);
665

    
666
        // --- criteria
667
        luceneSearch.setCdmTypRestriction(clazz);
668
        if (queryString != null) {
669
            textQueryBuilder.add(queryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
670
            finalQueryBuilder.add(textQueryBuilder.build(), Occur.MUST);
671
        }
672

    
673
        // --- spacial query
674
        if (bbox != null) {
675
            finalQueryBuilder.add(QueryFactory.buildSpatialQueryByRange(bbox, "gatheringEvent.exactLocation.point"), Occur.MUST);
676
        }
677

    
678
        luceneSearch.setQuery(finalQueryBuilder.build());
679

    
680
        // --- sorting
681
        SortField[] sortFields = new SortField[] { SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.Type.STRING, false) };
682
        luceneSearch.setSortFields(sortFields);
683

    
684
        if (highlightFragments) {
685
            luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray());
686
        }
687
        return luceneSearch;
688
    }
689

    
690

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

    
697
        // FIXME: use HQL queries to avoid entity instantiation and to increase performance
698

    
699
        SpecimenOrObservationBase<?> specimen = load(derivedUnitUuid);
700
        Collection<FieldUnit> fieldUnits = new ArrayList<>();
701
        if (specimen == null){
702
            return null;
703
        }
704
        if (specimen.isInstanceOf(FieldUnit.class)) {
705
            fieldUnits.add(HibernateProxyHelper.deproxy(specimen, FieldUnit.class));
706
        }
707
        else if(specimen.isInstanceOf(DerivedUnit.class)){
708
            fieldUnits.addAll(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class).collectRootUnits(FieldUnit.class));
709
        }
710

    
711
        fieldUnits = beanInitializer.initializeAll(fieldUnits, propertyPaths);
712
        return fieldUnits;
713
    }
714

    
715
    @Override
716
    @Transactional(readOnly=true)
717
    public Collection<SpecimenOrObservationBase> findRootUnits(UUID derivedUnitUuid, List<String> propertyPaths) {
718

    
719
        // FIXME: use HQL queries to avoid entity instantiation and to increase performance
720

    
721
        SpecimenOrObservationBase<?> specimen = load(derivedUnitUuid);
722
        Collection<SpecimenOrObservationBase> rootUnits = new ArrayList<>();
723
        if (specimen == null){
724
            return null;
725
        }
726
        if (specimen.isInstanceOf(FieldUnit.class)) {
727
            rootUnits.add(HibernateProxyHelper.deproxy(specimen, FieldUnit.class));
728
        }
729
        else if(specimen.isInstanceOf(DerivedUnit.class)){
730
            rootUnits.addAll(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class).collectRootUnits(SpecimenOrObservationBase.class));
731
        }
732

    
733
        rootUnits = beanInitializer.initializeAll(rootUnits, propertyPaths);
734
        return rootUnits;
735
    }
736

    
737
    @Override
738
    public Collection<SpecimenOrObservationBaseDTO> findRootUnitDTOs(UUID unitUUID) {
739

    
740

    
741
        SpecimenOrObservationBase<?> entity = load(unitUUID);
742
        SpecimenOrObservationBaseDTO derivedUnitDTO = SpecimenOrObservationDTOFactory.fromEntity(entity);
743
        Collection<SpecimenOrObservationBaseDTO> rootUnitDTOs = new ArrayList<>();
744
        if(derivedUnitDTO != null) {
745
            if(derivedUnitDTO instanceof FieldUnitDTO) {
746
                rootUnitDTOs.add(derivedUnitDTO);
747
            } else {
748
                Map<UUID, SpecimenOrObservationBaseDTO> alreadyCollectedSpecimen = new HashMap<>();
749
                rootUnitDTOs  = findRootUnitDTOs((DerivedUnitDTO)derivedUnitDTO, alreadyCollectedSpecimen);
750
            }
751
        }
752

    
753
        return rootUnitDTOs;
754

    
755
    }
756

    
757
    /**
758
     * Recursively searches all {@link DerivationEvent}s to find all "originals" ({@link SpecimenOrObservationBase})
759
     * from which this DerivedUnit was derived until all FieldUnits are found.
760
     * <p>
761
     * <b>NOTE:</b> The recursive search still is a bit incomplete and may miss originals in the rare case where a
762
     * derivative has more than one original. (see https://dev.e-taxonomy.eu/redmine/issues/9253)
763
     *
764
     * @param derivedUnitDTO
765
     *  The DerivedUnitDTO to start the search from.
766
     * @param alreadyCollectedSpecimen
767
     *  A map to hold all originals that have been sees during the recursive walk.
768
     * @return
769
     *  The collection of all Field Units that are accessible from the derivative from where the search was started.
770
     */
771
    public Collection<SpecimenOrObservationBaseDTO> findRootUnitDTOs(DerivedUnitDTO derivedUnitDTO,
772
            Map<UUID, SpecimenOrObservationBaseDTO> alreadyCollectedSpecimen) {
773

    
774
        HashMap<UUID, SpecimenOrObservationBaseDTO> rootUnitDTOs = new HashMap<>();
775
        _findRootUnitDTOs(derivedUnitDTO, rootUnitDTOs, alreadyCollectedSpecimen);
776
        return rootUnitDTOs.values();
777

    
778
    }
779

    
780
    /**
781
     * Method for recursive calls, must only be used by {@link #findRootUnitDTOs(DerivedUnitDTO, HashMap)}
782
     * <p>
783
     * It will search recursively over all {@link DerivationEvent}s and get the "originals" ({@link SpecimenOrObservationBase})
784
     * from which this DerivedUnit was derived until all FieldUnits are found.
785
     */
786
    private void _findRootUnitDTOs(DerivedUnitDTO derivedUnitDTO, Map<UUID, SpecimenOrObservationBaseDTO> rootUnitDTOs,
787
                Map<UUID, SpecimenOrObservationBaseDTO> alreadyCollectedSpecimen) {
788

    
789
        List<String> propertyPaths = new ArrayList<>();
790

    
791
        // add the supplied DTO the the alreadyCollectedSpecimen if not yet there
792
        if(!alreadyCollectedSpecimen.containsKey(derivedUnitDTO.getUuid())) {
793
            alreadyCollectedSpecimen.put(derivedUnitDTO.getUuid(), derivedUnitDTO);
794
        }
795

    
796
        List<SpecimenOrObservationBase> originals = dao.findOriginalsForDerivedUnit(derivedUnitDTO.getUuid(), propertyPaths);
797
        if (originals.size() > 0){
798
            if (originals.size() > 1){
799
                logger.warn("The derived unit with uuid " + derivedUnitDTO.getUuid() + "has more than one orginal");
800
            }
801
            // FIXME allow handling multiple originals
802
            SpecimenOrObservationBase<?> original = originals.get(0);
803
            original = HibernateProxyHelper.deproxy(original);
804

    
805
            if (alreadyCollectedSpecimen.containsKey(original.getUuid())){
806
                alreadyCollectedSpecimen.get(original.getUuid()).addDerivative(derivedUnitDTO);
807
    //            if ( alreadyCollectedSpecimen.get(specimen.getUuid()) instanceof FieldUnitDTO){
808
    //                ((FieldUnitDTO)alreadyCollectedSpecimen.get(specimen.getUuid())).getTaxonRelatedDerivedUnits().add(derivedUnitDTO.getUuid());
809
    //            }
810
            }else{
811
                if(!rootUnitDTOs.containsKey(original.getUuid())){
812
                    // the direct derivatives of the field unit are added in the factory method, so it is guaranteed that
813
                    // the derivedUnitDTO is already contained.
814
                    // ----
815
                    // Don't assemble derivatives for originals since we have them collected already
816
                    // when ascending to the originals, we only want to collect those derivatives which are on the path up to the root
817
                    final Integer maxDepth = 0;
818
                    SpecimenOrObservationBaseDTO originalDTO = SpecimenOrObservationDTOFactory.fromEntity(original, maxDepth);
819
                    originalDTO.addDerivative(derivedUnitDTO);
820
                    alreadyCollectedSpecimen.put(originalDTO.getUuid(), originalDTO);
821
                    if (original instanceof FieldUnit){
822
                        rootUnitDTOs.put(originalDTO.getUuid(), originalDTO);
823
                    }else{
824
                        _findRootUnitDTOs((DerivedUnitDTO) originalDTO, rootUnitDTOs, alreadyCollectedSpecimen);
825
                    }
826
                } else {
827
                    SpecimenOrObservationBaseDTO previouslyFoundRootUnit = rootUnitDTOs.get(original.getUuid());
828
                    if(!previouslyFoundRootUnit.getDerivatives().stream().anyMatch(uDTO -> uDTO.getUuid().equals(derivedUnitDTO.getUuid()))) {
829
                        previouslyFoundRootUnit.addDerivative(derivedUnitDTO);
830
                    }
831
                }
832
            }
833
        } else {
834
            rootUnitDTOs.put(derivedUnitDTO.getUuid(), derivedUnitDTO);
835
        }
836

    
837
    }
838

    
839
    @Override
840
    @Transactional(readOnly=true)
841
    public FieldUnitDTO loadFieldUnitDTO(UUID derivedUnitUuid) {
842

    
843
        FieldUnitDTO fieldUnitDTO = null;
844
        DerivedUnitDTO derivedUnitDTO = null;
845

    
846
        Map<UUID, SpecimenOrObservationBaseDTO> cycleDetectionMap = new HashMap<>();
847
        SpecimenOrObservationBase<?> derivative = dao.load(derivedUnitUuid);
848
        if(derivative != null){
849
            if (derivative instanceof FieldUnit){
850
                fieldUnitDTO = FieldUnitDTO.fromEntity((FieldUnit)derivative);
851
                return fieldUnitDTO;
852
            } else {
853
                // must be a DerivedUnit otherwise
854
                derivedUnitDTO = DerivedUnitDTO.fromEntity((DerivedUnit)derivative);
855
                while(true){
856

    
857
                    Set<SpecimenOrObservationBaseDTO> originals = originalDTOs(derivedUnitDTO.getUuid());
858

    
859
                    if(originals.isEmpty()){
860
                        break;
861
                    }
862
                    if (originals.size() > 1){
863
                        logger.debug("The derived unit with uuid " + derivedUnitUuid + "has more than one orginal, ignoring all but the first one.");
864
                    }
865

    
866
                    SpecimenOrObservationBaseDTO originalDTO = originals.iterator().next();
867

    
868
                    // cycle detection and handling
869
                    if(cycleDetectionMap.containsKey(originalDTO.getUuid())){
870
                        // cycle detected!!!
871
                        try {
872
                            throw new Exception();
873
                        } catch(Exception e){
874
                            logger.error("Cycle in derivate graph detected at DerivedUnit with uuid=" + originalDTO.getUuid() , e);
875
                        }
876
                        // to solve the situation for the output we remove the derivate from the more distant graph node
877
                        // by removing it from the derivatives of its original
878
                        // but let the derivate to be added to the original which is closer to the FieldUnit (below at originalDTO.addDerivate(derivedUnitDTO);)
879
                        for(SpecimenOrObservationBaseDTO seenOriginal: cycleDetectionMap.values()){
880
                            for(SpecimenOrObservationBaseDTO derivateDTO : seenOriginal.getDerivatives()){
881
                                if(derivateDTO.equals(originalDTO)){
882
                                    seenOriginal.getDerivatives().remove(originalDTO);
883
                                }
884
                            }
885
                        }
886
                    } else {
887
                        cycleDetectionMap.put(originalDTO.getUuid(), originalDTO);
888
                    }
889

    
890
                    if (originalDTO instanceof FieldUnitDTO){
891
                        fieldUnitDTO = (FieldUnitDTO)originalDTO;
892
                        break;
893
                    }else{
894
                        // So this must be a DerivedUnitDTO
895
                        if (derivedUnitDTO == null){
896
                            derivedUnitDTO = (DerivedUnitDTO)originalDTO;
897
                        } else {
898
                            derivedUnitDTO = (DerivedUnitDTO)originalDTO;
899
                        }
900
                    }
901
                }
902
            }
903
        }
904
        return fieldUnitDTO;
905

    
906
    }
907

    
908
    /**
909
     * @param originalDTO
910
     * @return
911
     */
912
    private Set<SpecimenOrObservationBaseDTO> originalDTOs(UUID derivativeUuid) {
913

    
914
        Set<SpecimenOrObservationBaseDTO> dtos = new HashSet<>();
915
        List<SpecimenOrObservationBase> specimens = dao.findOriginalsForDerivedUnit(derivativeUuid, null);
916
        for(SpecimenOrObservationBase sob : specimens){
917
            if(sob instanceof FieldUnit) {
918
                dtos.add(FieldUnitDTO.fromEntity((FieldUnit)sob));
919
            } else {
920
                dtos.add(DerivedUnitDTO.fromEntity((DerivedUnit)sob));
921
            }
922
        }
923
        return dtos;
924
    }
925

    
926

    
927
    @Override
928
    @Transactional(readOnly = false)
929
    public UpdateResult moveSequence(DnaSample from, DnaSample to, Sequence sequence) {
930
        return moveSequence(from.getUuid(), to.getUuid(), sequence.getUuid());
931
    }
932

    
933
    @Override
934
    @Transactional(readOnly = false)
935
    public UpdateResult moveSequence(UUID fromUuid, UUID toUuid, UUID sequenceUuid) {
936
        // reload specimens to avoid session conflicts
937
        DnaSample from = (DnaSample) load(fromUuid);
938
        DnaSample to = (DnaSample) load(toUuid);
939
        Sequence sequence = sequenceService.load(sequenceUuid);
940

    
941
        if (from == null || to == null || sequence == null) {
942
            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" +
943
                    "Operation was move "+sequence+ " from "+from+" to "+to);
944
        }
945
        UpdateResult result = new UpdateResult();
946
        from.removeSequence(sequence);
947
        saveOrUpdate(from);
948
        to.addSequence(sequence);
949
        saveOrUpdate(to);
950
        result.setStatus(Status.OK);
951
        result.addUpdatedObject(from);
952
        result.addUpdatedObject(to);
953
        return result;
954
    }
955

    
956
    @Override
957
    @Transactional(readOnly = false)
958
    public boolean moveDerivate(SpecimenOrObservationBase<?> from, SpecimenOrObservationBase<?> to, DerivedUnit derivate) {
959
        return moveDerivate(from!=null?from.getUuid():null, to.getUuid(), derivate.getUuid()).isOk();
960
    }
961

    
962
    @Override
963
    @Transactional(readOnly = false)
964
    public UpdateResult moveDerivate(UUID specimenFromUuid, UUID specimenToUuid, UUID derivateUuid) {
965
        // reload specimens to avoid session conflicts
966
        SpecimenOrObservationBase<?> from = null;
967
        if(specimenFromUuid!=null){
968
            from = load(specimenFromUuid);
969
        }
970
        SpecimenOrObservationBase<?> to = load(specimenToUuid);
971
        DerivedUnit derivate = (DerivedUnit) load(derivateUuid);
972

    
973
        if ((specimenFromUuid!=null && from == null) || to == null || derivate == null) {
974
            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" +
975
                    "Operation was move "+derivate+ " from "+from+" to "+to);
976
        }
977
        UpdateResult result = new UpdateResult();
978
        SpecimenOrObservationType derivateType = derivate.getRecordBasis();
979
        SpecimenOrObservationType toType = to.getRecordBasis();
980
        // check if type is a sub derivate type
981
        if(toType==SpecimenOrObservationType.FieldUnit //moving to FieldUnit always works
982
                || derivateType==SpecimenOrObservationType.Media //moving media always works
983
                || (derivateType.isKindOf(toType) && toType!=derivateType)){ //moving only to parent derivate type
984
            if(from!=null){
985
                // remove derivation event from parent specimen of dragged object
986
                DerivationEvent eventToRemove = null;
987
                for (DerivationEvent event : from.getDerivationEvents()) {
988
                    if (event.getDerivatives().contains(derivate)) {
989
                        eventToRemove = event;
990
                        break;
991
                    }
992
                }
993
                from.removeDerivationEvent(eventToRemove);
994
                if(eventToRemove!=null){
995
                    // add new derivation event to target and copy the event parameters of the old one
996
                    DerivationEvent derivedFromNewOriginalEvent = DerivationEvent.NewSimpleInstance(to, derivate, null);
997
                    derivedFromNewOriginalEvent.setActor(eventToRemove.getActor());
998
                    derivedFromNewOriginalEvent.setDescription(eventToRemove.getDescription());
999
                    derivedFromNewOriginalEvent.setInstitution(eventToRemove.getInstitution());
1000
                    derivedFromNewOriginalEvent.setTimeperiod(eventToRemove.getTimeperiod());
1001
                    derivedFromNewOriginalEvent.setType(eventToRemove.getType());
1002
                    to.addDerivationEvent(derivedFromNewOriginalEvent);
1003
                    derivate.setDerivedFrom(derivedFromNewOriginalEvent);
1004
                }
1005
            }
1006
            else{
1007
                //derivative had no parent before so we use empty derivation event
1008
                DerivationEvent derivedFromNewOriginalEvent = DerivationEvent.NewSimpleInstance(to, derivate, null);
1009
                to.addDerivationEvent(derivedFromNewOriginalEvent);
1010
                derivate.setDerivedFrom(derivedFromNewOriginalEvent);
1011
            }
1012

    
1013
            if(from!=null){
1014
                saveOrUpdate(from);
1015
            }
1016
            saveOrUpdate(to);
1017
            result.setStatus(Status.OK);
1018
            result.addUpdatedObject(from);
1019
            result.addUpdatedObject(to);
1020
        } else {
1021
            result.setStatus(Status.ERROR);
1022
        }
1023
        return result;
1024
    }
1025

    
1026
    @Override
1027
    public DeleteResult isDeletable(UUID specimenUuid, DeleteConfiguratorBase config) {
1028
        DeleteResult deleteResult = new DeleteResult();
1029
        SpecimenOrObservationBase specimen = this.load(specimenUuid);
1030
        SpecimenDeleteConfigurator specimenDeleteConfigurator = (SpecimenDeleteConfigurator) config;
1031

    
1032
        // check elements found by super method
1033
        Set<CdmBase> relatedObjects = super.isDeletable(specimenUuid, config).getRelatedObjects();
1034
        for (CdmBase cdmBase : relatedObjects) {
1035
            // check for type designation
1036
            if (cdmBase.isInstanceOf(SpecimenTypeDesignation.class) && !specimenDeleteConfigurator.isDeleteFromTypeDesignation()) {
1037
                deleteResult.setAbort();
1038
                deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation is a type specimen."));
1039
                deleteResult.addRelatedObject(cdmBase);
1040
                break;
1041
            }
1042
            // check for IndividualsAssociations
1043
            else if (cdmBase.isInstanceOf(IndividualsAssociation.class) && !specimenDeleteConfigurator.isDeleteFromIndividualsAssociation()) {
1044
                deleteResult.setAbort();
1045
                deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation is still associated via IndividualsAssociations"));
1046
                deleteResult.addRelatedObject(cdmBase);
1047
                break;
1048
            }
1049
            // check for taxon description
1050
            else if(cdmBase.isInstanceOf(TaxonDescription.class)
1051
                    && HibernateProxyHelper.deproxy(cdmBase, TaxonDescription.class).getDescribedSpecimenOrObservation().equals(specimen)
1052
                    && !specimenDeleteConfigurator.isDeleteFromDescription()){
1053
                deleteResult.setAbort();
1054
                deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation is still used as \"Described Specimen\" in a taxon description."));
1055
                deleteResult.addRelatedObject(cdmBase);
1056
                break;
1057
            }
1058
            // check for children and parents (derivation events)
1059
            else if (cdmBase.isInstanceOf(DerivationEvent.class)) {
1060
                DerivationEvent derivationEvent = HibernateProxyHelper.deproxy(cdmBase, DerivationEvent.class);
1061
                // check if derivation event is empty
1062
                if (!derivationEvent.getDerivatives().isEmpty() && derivationEvent.getOriginals().contains(specimen)) {
1063
                    // if derivationEvent is the childEvent and contains derivations
1064
//                    if (derivationEvent.getDerivatives().contains(specimen)) {
1065
//                        //if it is the parent event the specimen is still deletable
1066
//                        continue;
1067
//                    }
1068
                    if(!specimenDeleteConfigurator.isDeleteChildren()){
1069
                        //if children should not be deleted then it is undeletable
1070
                        deleteResult.setAbort();
1071
                        deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation still has child derivatives."));
1072
                        deleteResult.addRelatedObject(cdmBase);
1073
                        break;
1074
                    }
1075
                    else{
1076
                        // check all children if they can be deleted
1077
                        Set<DerivedUnit> derivatives = derivationEvent.getDerivatives();
1078
                        DeleteResult childResult = new DeleteResult();
1079
                        for (DerivedUnit derivedUnit : derivatives) {
1080
                            childResult.includeResult(isDeletable(derivedUnit.getUuid(), specimenDeleteConfigurator));
1081
                        }
1082
                        if (!childResult.isOk()) {
1083
                            deleteResult.setAbort();
1084
                            deleteResult.includeResult(childResult);
1085
                            deleteResult.addRelatedObject(cdmBase);
1086
                            break;
1087
                        }
1088
                    }
1089
                }
1090
            }
1091
            // check for amplification
1092
            else if (cdmBase.isInstanceOf(AmplificationResult.class)
1093
                    && !specimenDeleteConfigurator.isDeleteMolecularData()
1094
                    && !specimenDeleteConfigurator.isDeleteChildren()) {
1095
                deleteResult.setAbort();
1096
                deleteResult.addException(new ReferencedObjectUndeletableException("DnaSample is used in amplification results."));
1097
                deleteResult.addRelatedObject(cdmBase);
1098
                break;
1099
            }
1100
            // check for sequence
1101
            else if (cdmBase.isInstanceOf(Sequence.class)
1102
                    && !specimenDeleteConfigurator.isDeleteMolecularData()
1103
                    && !specimenDeleteConfigurator.isDeleteChildren()) {
1104
                deleteResult.setAbort();
1105
                deleteResult.addException(new ReferencedObjectUndeletableException("DnaSample is used in sequences."));
1106
                deleteResult.addRelatedObject(cdmBase);
1107
                break;
1108
            }
1109
        }
1110
        if (deleteResult.isOk()) {
1111
            //add all related object if deletion is OK so they can be handled by the delete() method
1112
            deleteResult.addRelatedObjects(relatedObjects);
1113
        }
1114
        return deleteResult;
1115
    }
1116

    
1117
    /**
1118
     * {@inheritDoc}
1119
     */
1120
    @Transactional(readOnly = false)
1121
    @Override
1122
    public DeleteResult delete(UUID specimenUuid, SpecimenDeleteConfigurator config) {
1123
        return delete(load(specimenUuid), config);
1124
    }
1125

    
1126

    
1127
    @Transactional(readOnly = false)
1128
    @Override
1129
    public DeleteResult delete(SpecimenOrObservationBase<?> specimen, SpecimenDeleteConfigurator config) {
1130
        specimen = HibernateProxyHelper.deproxy(specimen, SpecimenOrObservationBase.class);
1131

    
1132
        DeleteResult deleteResult = isDeletable(specimen.getUuid(), config);
1133
        if (!deleteResult.isOk()) {
1134
            return deleteResult;
1135
        }
1136

    
1137
        if (config.isDeleteChildren()) {
1138
            Set<DerivationEvent> derivationEvents = specimen.getDerivationEvents();
1139
            //clone to avoid concurrent modification
1140
            //can happen if the child is deleted and deleted its own derivedFrom event
1141
            Set<DerivationEvent> derivationEventsClone = new HashSet<>(derivationEvents);
1142
            for (DerivationEvent derivationEvent : derivationEventsClone) {
1143
                Set<DerivedUnit> derivatives = derivationEvent.getDerivatives();
1144
                Iterator<DerivedUnit> it = derivatives.iterator();
1145
                Set<DerivedUnit> derivativesToDelete = new HashSet<>();
1146
                while (it.hasNext()) {
1147
                    DerivedUnit unit = it.next();
1148
                    derivativesToDelete.add(unit);
1149
                }
1150
                for (DerivedUnit unit:derivativesToDelete){
1151
                    deleteResult.includeResult(delete(unit, config));
1152
                }
1153
            }
1154
        }
1155

    
1156

    
1157

    
1158

    
1159
        // check related objects
1160
        Set<CdmBase> relatedObjects = deleteResult.getRelatedObjects();
1161

    
1162
        for (CdmBase relatedObject : relatedObjects) {
1163
            // check for TypeDesignations
1164
            if (relatedObject.isInstanceOf(SpecimenTypeDesignation.class)) {
1165
                SpecimenTypeDesignation designation = HibernateProxyHelper.deproxy(relatedObject, SpecimenTypeDesignation.class);
1166
                designation.setTypeSpecimen(null);
1167
                List<TaxonName> typifiedNames = new ArrayList<>();
1168
                typifiedNames.addAll(designation.getTypifiedNames());
1169
                for (TaxonName taxonName : typifiedNames) {
1170
                    taxonName.removeTypeDesignation(designation);
1171
                }
1172
            }
1173
            // delete IndividualsAssociation
1174
            if (relatedObject.isInstanceOf(IndividualsAssociation.class)) {
1175
                IndividualsAssociation association = HibernateProxyHelper.deproxy(relatedObject, IndividualsAssociation.class);
1176
                association.setAssociatedSpecimenOrObservation(null);
1177
                association.getInDescription().removeElement(association);
1178
            }
1179
            // check for "described specimen" (deprecated)
1180
            if (relatedObject.isInstanceOf(TaxonDescription.class)) {
1181
                TaxonDescription description = HibernateProxyHelper.deproxy(relatedObject, TaxonDescription.class);
1182
                description.setDescribedSpecimenOrObservation(null);
1183
            }
1184
            // check for specimen description
1185
            if (relatedObject.isInstanceOf(SpecimenDescription.class)) {
1186
                SpecimenDescription specimenDescription = HibernateProxyHelper.deproxy(relatedObject, SpecimenDescription.class);
1187
                specimenDescription.setDescribedSpecimenOrObservation(null);
1188
                // check if description is a description of the given specimen
1189
                if (specimen.getDescriptions().contains(specimenDescription)) {
1190
                    specimen.removeDescription(specimenDescription);
1191
                }
1192
                DeleteResult descriptionDelete = descriptionService.isDeletable(specimenDescription.getUuid(), null);
1193
                if (descriptionDelete.isOk()){
1194
                    deleteResult.includeResult(descriptionService.delete(specimenDescription));
1195
                }
1196
            }
1197
            // check for amplification
1198
            if (relatedObject.isInstanceOf(AmplificationResult.class)) {
1199
                AmplificationResult amplificationResult = HibernateProxyHelper.deproxy(relatedObject, AmplificationResult.class);
1200
                amplificationResult.getDnaSample().removeAmplificationResult(amplificationResult);
1201
            }
1202
            // check for sequence
1203
            if (relatedObject.isInstanceOf(Sequence.class)) {
1204
                Sequence sequence = HibernateProxyHelper.deproxy(relatedObject, Sequence.class);
1205
                sequence.getDnaSample().removeSequence(sequence);
1206
            }
1207
            // check for children and parents (derivation events)
1208
            if (relatedObject.isInstanceOf(DerivationEvent.class)) {
1209
                DerivationEvent derivationEvent = HibernateProxyHelper.deproxy(relatedObject, DerivationEvent.class);
1210
                // parent derivation event (derivedFrom)
1211
                if (derivationEvent.getDerivatives().contains(specimen) && specimen.isInstanceOf(DerivedUnit.class)) {
1212
                    derivationEvent.removeDerivative(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class));
1213
                    if (derivationEvent.getDerivatives().isEmpty()) {
1214
                        Set<SpecimenOrObservationBase> originals = derivationEvent.getOriginals();
1215
                        for (SpecimenOrObservationBase specimenOrObservationBase : originals) {
1216
                            specimenOrObservationBase.removeDerivationEvent(derivationEvent);
1217
                            deleteResult.addUpdatedObject(specimenOrObservationBase);
1218
                        }
1219
                        // if derivationEvent has no derivates anymore, delete it
1220
                        deleteResult.includeResult(eventService.delete(derivationEvent));
1221
                    }
1222
                }
1223
                else{
1224
                    //child derivation events should not occur since we delete the hierarchy from bottom to top
1225
                }
1226
            }
1227
        }
1228
        if (specimen instanceof FieldUnit){
1229
            FieldUnit fieldUnit = HibernateProxyHelper.deproxy(specimen, FieldUnit.class);
1230
            GatheringEvent event = fieldUnit.getGatheringEvent();
1231
            fieldUnit.setGatheringEvent(null);
1232
            if (event != null){
1233
                DeleteResult result = eventService.isDeletable(event.getUuid(), null);
1234
                if (result.isOk()){
1235
                    deleteResult.includeResult( eventService.delete(event));
1236
                }
1237
            }
1238

    
1239
        }
1240
        deleteResult.includeResult(delete(specimen));
1241

    
1242
        return deleteResult;
1243
    }
1244

    
1245
    @Override
1246
    public Collection<IndividualsAssociation> listIndividualsAssociations(SpecimenOrObservationBase<?> specimen, Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1247
        return dao.listIndividualsAssociations(specimen, limit, start, orderHints, propertyPaths);
1248
    }
1249

    
1250
    /**
1251
     * {@inheritDoc}
1252
     */
1253
    @Override
1254
    public Collection<TaxonBase<?>> listAssociatedTaxa(SpecimenOrObservationBase<?> specimen, Integer limit,
1255
            Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1256
        return listAssociatedTaxa(specimen, INCLUDE_UNPUBLISHED, limit, start, orderHints, propertyPaths);
1257
    }
1258
    @Override
1259
    public Collection<TaxonBase<?>> listAssociatedTaxa(SpecimenOrObservationBase<?> specimen, boolean includeUnpublished,
1260
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1261
        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
1262

    
1263
        //individuals associations
1264
        associatedTaxa.addAll(listIndividualsAssociationTaxa(specimen, includeUnpublished, limit, start, orderHints, propertyPaths));
1265
        //type designation
1266
        if(specimen.isInstanceOf(DerivedUnit.class)){
1267
            associatedTaxa.addAll(listTypeDesignationTaxa(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class),
1268
                  includeUnpublished, limit, start, orderHints, propertyPaths));
1269
        }
1270
        //determinations
1271
        associatedTaxa.addAll(listDeterminedTaxa(specimen, includeUnpublished, limit, start, orderHints, propertyPaths));
1272

    
1273
        return associatedTaxa;
1274
    }
1275

    
1276

    
1277

    
1278
    /**
1279
     * {@inheritDoc}
1280
     */
1281
    @Override
1282
    public Collection<TaxonBase<?>> listDeterminedTaxa(SpecimenOrObservationBase<?> specimen, Integer limit,
1283
            Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1284
        return listDeterminedTaxa(specimen, INCLUDE_UNPUBLISHED, limit, start, orderHints, propertyPaths);
1285
    }
1286
    @Override
1287
    public Collection<TaxonBase<?>> listDeterminedTaxa(SpecimenOrObservationBase<?> specimen, boolean includeUnpublished, Integer limit, Integer start,
1288
            List<OrderHint> orderHints, List<String> propertyPaths) {
1289
        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
1290
        for (DeterminationEvent determinationEvent : listDeterminationEvents(specimen, limit, start, orderHints, propertyPaths)) {
1291
            if(determinationEvent.getIdentifiedUnit().equals(specimen)){
1292
                if(determinationEvent.getTaxon()!=null){
1293
                    associatedTaxa.add(taxonService.load(determinationEvent.getTaxon().getUuid(), includeUnpublished, propertyPaths));
1294
                }
1295
                if(determinationEvent.getTaxonName()!=null){
1296
                    Collection<TaxonBase> taxonBases = determinationEvent.getTaxonName().getTaxonBases();
1297
                    for (TaxonBase taxonBase : taxonBases) {
1298
                        associatedTaxa.add(taxonService.load(taxonBase.getUuid(), includeUnpublished, propertyPaths));
1299
                    }
1300
                }
1301
            }
1302
        }
1303
        return associatedTaxa;
1304
    }
1305

    
1306
    /**
1307
     * {@inheritDoc}
1308
     */
1309
    @Override
1310
    public Collection<TaxonBase<?>> listTypeDesignationTaxa(DerivedUnit specimen, Integer limit, Integer start,
1311
            List<OrderHint> orderHints, List<String> propertyPaths) {
1312
        return listTypeDesignationTaxa(specimen, INCLUDE_UNPUBLISHED, limit, start, orderHints, propertyPaths);
1313
    }
1314
    @Override
1315
    public Collection<TaxonBase<?>> listTypeDesignationTaxa(DerivedUnit specimen, boolean includeUnpublished,
1316
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1317
        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
1318
        for (SpecimenTypeDesignation typeDesignation : listTypeDesignations(specimen, limit, start, orderHints, propertyPaths)) {
1319
            if(typeDesignation.getTypeSpecimen().equals(specimen)){
1320
                Set<TaxonName> typifiedNames = typeDesignation.getTypifiedNames();
1321
                for (TaxonName taxonName : typifiedNames) {
1322
                    Set<Taxon> taxa = taxonName.getTaxa();
1323
                    for (Taxon taxon : taxa) {
1324
                        associatedTaxa.add(taxonService.load(taxon.getUuid(), includeUnpublished, propertyPaths));
1325
                    }
1326
                }
1327
            }
1328
        }
1329
        return associatedTaxa;
1330
    }
1331

    
1332
    /**
1333
     * {@inheritDoc}
1334
     */
1335
    @Override
1336
    public Collection<TaxonBase<?>> listIndividualsAssociationTaxa(SpecimenOrObservationBase<?> specimen, Integer limit,
1337
            Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1338
        return listIndividualsAssociationTaxa(specimen, INCLUDE_UNPUBLISHED, limit, start, orderHints, propertyPaths);
1339
    }
1340

    
1341
    @Override
1342
    public Collection<TaxonBase<?>> listIndividualsAssociationTaxa(SpecimenOrObservationBase<?> specimen, boolean includeUnpublished,
1343
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1344
        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
1345
        for (IndividualsAssociation individualsAssociation : listIndividualsAssociations(specimen, null, null, null, null)) {
1346
            if(individualsAssociation.getInDescription().isInstanceOf(TaxonDescription.class)){
1347
                TaxonDescription taxonDescription = HibernateProxyHelper.deproxy(individualsAssociation.getInDescription(), TaxonDescription.class);
1348
                if(taxonDescription.getTaxon()!=null){
1349
                    associatedTaxa.add(taxonService.load(taxonDescription.getTaxon().getUuid(), includeUnpublished, propertyPaths));
1350
                }
1351
            }
1352
        }
1353
        return associatedTaxa;
1354
    }
1355

    
1356
    @Override
1357
    public Collection<DeterminationEvent> listDeterminationEvents(SpecimenOrObservationBase<?> specimen,
1358
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1359
        return dao.listDeterminationEvents(specimen, limit, start, orderHints, propertyPaths);
1360
    }
1361

    
1362
    @Override
1363
    public Map<DerivedUnit, Collection<SpecimenTypeDesignation>> listTypeDesignations(
1364
            Collection<DerivedUnit> specimens, Integer limit, Integer start,
1365
            List<OrderHint> orderHints, List<String> propertyPaths) {
1366
        Map<DerivedUnit, Collection<SpecimenTypeDesignation>> typeDesignationMap = new HashMap<>();
1367
        for (DerivedUnit specimen : specimens) {
1368
            Collection<SpecimenTypeDesignation> typeDesignations = listTypeDesignations(specimen, limit, start, orderHints, propertyPaths);
1369
            typeDesignationMap.put(specimen, typeDesignations);
1370
        }
1371
        return typeDesignationMap;
1372
    }
1373

    
1374
    @Override
1375
    public Collection<SpecimenTypeDesignation> listTypeDesignations(DerivedUnit specimen,
1376
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1377
        return dao.listTypeDesignations(specimen, limit, start, orderHints, propertyPaths);
1378
    }
1379

    
1380
    @Override
1381
    public Collection<DescriptionBase<?>> listDescriptionsWithDescriptionSpecimen(
1382
            SpecimenOrObservationBase<?> specimen, Integer limit, Integer start, List<OrderHint> orderHints,
1383
            List<String> propertyPaths) {
1384
        return dao.listDescriptionsWithDescriptionSpecimen(specimen, limit, start, orderHints, propertyPaths);
1385
    }
1386

    
1387
    @Override
1388
    public Collection<DescriptionElementBase> getCharacterDataForSpecimen(UUID specimenUuid) {
1389
        SpecimenOrObservationBase<?> specimen = load(specimenUuid);
1390
        if (specimen != null) {
1391
            return specimen.characterData();
1392
        }
1393
        else{
1394
            throw new DataRetrievalFailureException("Specimen with the given uuid not found in the data base");
1395
        }
1396
    }
1397

    
1398
    @Override
1399
    public long countByTitle(IIdentifiableEntityServiceConfigurator<SpecimenOrObservationBase> config){
1400
        if (config instanceof FindOccurrencesConfigurator) {
1401
            FindOccurrencesConfigurator occurrenceConfig = (FindOccurrencesConfigurator) config;
1402
            Taxon taxon = null;
1403
            if(occurrenceConfig.getAssociatedTaxonUuid()!=null){
1404
                TaxonBase<?> taxonBase = taxonService.load(occurrenceConfig.getAssociatedTaxonUuid());
1405
                if(taxonBase != null && taxonBase.isInstanceOf(Taxon.class)){
1406
                    taxon = HibernateProxyHelper.deproxy(taxonBase, Taxon.class);
1407
                }
1408
            }
1409
            TaxonName taxonName = null;
1410
            if(occurrenceConfig.getAssociatedTaxonNameUuid()!=null){
1411
                taxonName = nameService.load(occurrenceConfig.getAssociatedTaxonNameUuid());
1412
            }
1413
            /*TODO: #6484 Neither isRetrieveIndirectlyAssociatedSpecimens() nor the AssignmentStatus
1414
             * is currently reflected in the HQL query. So using these in the count method will
1415
             * significantly slow down this method as we have to retrieve the entities instead of
1416
             * just the amount.
1417
             */
1418
            if(occurrenceConfig.isRetrieveIndirectlyAssociatedSpecimens() || !occurrenceConfig.getAssignmentStatus().equals(AssignmentStatus.ALL_SPECIMENS)){
1419
                List<SpecimenOrObservationBase> occurrences = new ArrayList<>();
1420
                List<SpecimenOrObservationBase> sobs = dao.findOccurrences(occurrenceConfig.getClazz(),
1421
                        occurrenceConfig.getTitleSearchStringSqlized(), occurrenceConfig.getSignificantIdentifier(),
1422
                        occurrenceConfig.getSpecimenType(), taxon, taxonName, occurrenceConfig.getMatchMode(), null, null,
1423
                        occurrenceConfig.getOrderHints(), occurrenceConfig.getPropertyPaths());
1424
                occurrences.addAll(sobs);
1425
                occurrences = filterOccurencesByAssignmentAndHierarchy(occurrenceConfig, occurrences, taxon, taxonName);
1426
                return occurrences.size();
1427
            }
1428

    
1429
            return dao.countOccurrences(occurrenceConfig.getClazz(),
1430
                    occurrenceConfig.getTitleSearchStringSqlized(), occurrenceConfig.getSignificantIdentifier(),
1431
                    occurrenceConfig.getSpecimenType(), taxon, taxonName, occurrenceConfig.getMatchMode(), null, null,
1432
                    occurrenceConfig.getOrderHints(), occurrenceConfig.getPropertyPaths());
1433
        }
1434
        else{
1435
            return super.countByTitle(config);
1436
        }
1437
    }
1438

    
1439
    @Override
1440
    public Pager<UuidAndTitleCache<SpecimenOrObservationBase>> findByTitleUuidAndTitleCache(
1441
            FindOccurrencesConfigurator config){
1442
        List<UuidAndTitleCache<SpecimenOrObservationBase>> occurrences = new ArrayList<>();
1443
        Taxon taxon = null;
1444
        if(config.getAssociatedTaxonUuid()!=null){
1445
            TaxonBase<?> taxonBase = taxonService.load(config.getAssociatedTaxonUuid());
1446
            if(taxonBase != null && taxonBase.isInstanceOf(Taxon.class)){
1447
                taxon = CdmBase.deproxy(taxonBase, Taxon.class);
1448
            }
1449
        }
1450
        TaxonName taxonName = null;
1451
        if(config.getAssociatedTaxonNameUuid()!=null){
1452
            taxonName = nameService.load(config.getAssociatedTaxonNameUuid());
1453
        }
1454
        occurrences.addAll(dao.findOccurrencesUuidAndTitleCache(config.getClazz(),
1455
                config.getTitleSearchString(), config.getSignificantIdentifier(),
1456
                config.getSpecimenType(), taxon, taxonName, config.getMatchMode(), null, null,
1457
                config.getOrderHints()));
1458
        long count = Integer.valueOf(occurrences.size()).longValue();
1459
        return new DefaultPagerImpl<>(config.getPageNumber(), count, config.getPageSize(), occurrences);
1460
    }
1461

    
1462
    @Override
1463
    public List<DerivedUnitDTO> findByTitleDerivedUnitDTO(FindOccurrencesConfigurator config) {
1464
        Taxon taxon = null;
1465
        if(config.getAssociatedTaxonUuid()!=null){
1466
            TaxonBase<?> taxonBase = taxonService.load(config.getAssociatedTaxonUuid());
1467
            if(taxonBase != null && taxonBase.isInstanceOf(Taxon.class)){
1468
                taxon = CdmBase.deproxy(taxonBase, Taxon.class);
1469
            }
1470
        }
1471
        TaxonName taxonName = null;
1472
        if(config.getAssociatedTaxonNameUuid()!=null){
1473
            taxonName = nameService.load(config.getAssociatedTaxonNameUuid());
1474
        }
1475
        List<DerivedUnit> occurrences = new ArrayList<>();
1476
        occurrences.addAll(dao.findOccurrences(DerivedUnit.class,
1477
                config.getTitleSearchString(), config.getSignificantIdentifier(),
1478
                config.getSpecimenType(), taxon, taxonName, config.getMatchMode(), null, null,
1479
                config.getOrderHints(), null));
1480

    
1481
        List<DerivedUnitDTO> dtos = new ArrayList<>();
1482
        occurrences.forEach(derivedUnit->dtos.add(assembleDerivedUnitDTO(derivedUnit)));
1483
        return dtos;
1484
    }
1485

    
1486
    @Override
1487
    public <S extends SpecimenOrObservationBase> Pager<S> findByTitle(
1488
            IIdentifiableEntityServiceConfigurator<S> config) {
1489
        if (config instanceof FindOccurrencesConfigurator) {
1490
            FindOccurrencesConfigurator occurrenceConfig = (FindOccurrencesConfigurator) config;
1491
            List<SpecimenOrObservationBase> occurrences = new ArrayList<>();
1492
            Taxon taxon = null;
1493
            if(occurrenceConfig.getAssociatedTaxonUuid()!=null){
1494
                TaxonBase<?> taxonBase = taxonService.load(occurrenceConfig.getAssociatedTaxonUuid());
1495
                if(taxonBase.isInstanceOf(Taxon.class)){
1496
                    taxon = HibernateProxyHelper.deproxy(taxonBase, Taxon.class);
1497
                }
1498
            }
1499
            TaxonName taxonName = null;
1500
            if(occurrenceConfig.getAssociatedTaxonNameUuid()!=null){
1501
                taxonName = nameService.load(occurrenceConfig.getAssociatedTaxonNameUuid());
1502
            }
1503
            List<? extends SpecimenOrObservationBase> foundOccurrences = dao.findOccurrences(occurrenceConfig.getClazz(),
1504
                    occurrenceConfig.getTitleSearchString(), occurrenceConfig.getSignificantIdentifier(),
1505
                    occurrenceConfig.getSpecimenType(), taxon, taxonName, occurrenceConfig.getMatchMode(), null, null,
1506
                    occurrenceConfig.getOrderHints(), occurrenceConfig.getPropertyPaths());
1507
            occurrences.addAll(foundOccurrences);
1508
            occurrences = filterOccurencesByAssignmentAndHierarchy(occurrenceConfig, occurrences, taxon, taxonName);
1509

    
1510
            long count = Integer.valueOf(occurrences.size()).longValue();
1511
            return new DefaultPagerImpl<>(config.getPageNumber(), count, config.getPageSize(), (List<S>)occurrences);
1512
        }
1513
        return super.findByTitle(config);
1514
    }
1515

    
1516
    private List<SpecimenOrObservationBase> filterOccurencesByAssignmentAndHierarchy(
1517
            FindOccurrencesConfigurator occurrenceConfig, List<SpecimenOrObservationBase> occurrences, Taxon taxon,
1518
            TaxonName taxonName) {
1519
        //filter out (un-)assigned specimens
1520
        if(taxon==null && taxonName==null){
1521
            AssignmentStatus assignmentStatus = occurrenceConfig.getAssignmentStatus();
1522
            List<SpecimenOrObservationBase> specimenWithAssociations = new ArrayList<>();
1523
            if(!assignmentStatus.equals(AssignmentStatus.ALL_SPECIMENS)){
1524
                for (SpecimenOrObservationBase<?> specimenOrObservationBase : occurrences) {
1525
                    boolean includeUnpublished = true;  //TODO not sure if this is correct, maybe we have to propagate publish flag to higher methods.
1526
                    Collection<TaxonBase<?>> associatedTaxa = listAssociatedTaxa(specimenOrObservationBase,
1527
                            includeUnpublished, null, null, null, null);
1528
                    if(!associatedTaxa.isEmpty()){
1529
                        specimenWithAssociations.add(specimenOrObservationBase);
1530
                    }
1531
                }
1532
            }
1533
            if(assignmentStatus.equals(AssignmentStatus.UNASSIGNED_SPECIMENS)){
1534
                occurrences.removeAll(specimenWithAssociations);
1535
            }
1536
            if(assignmentStatus.equals(AssignmentStatus.ASSIGNED_SPECIMENS)){
1537
                occurrences = new ArrayList<>(specimenWithAssociations);
1538
            }
1539
        }
1540
        // indirectly associated specimens
1541
        if(occurrenceConfig.isRetrieveIndirectlyAssociatedSpecimens()){
1542
            List<SpecimenOrObservationBase> indirectlyAssociatedOccurrences = new ArrayList<>(occurrences);
1543
            for (SpecimenOrObservationBase<?> specimen : occurrences) {
1544
                List<SpecimenOrObservationBase<?>> allHierarchyDerivates = getAllHierarchyDerivatives(specimen);
1545
                for (SpecimenOrObservationBase<?> specimenOrObservationBase : allHierarchyDerivates) {
1546
                    if(!occurrences.contains(specimenOrObservationBase)){
1547
                        indirectlyAssociatedOccurrences.add(specimenOrObservationBase);
1548
                    }
1549
                }
1550
            }
1551
            occurrences = indirectlyAssociatedOccurrences;
1552
        }
1553
        return occurrences;
1554
    }
1555

    
1556
    @Override
1557
    public List<SpecimenOrObservationBase<?>> getAllHierarchyDerivatives(SpecimenOrObservationBase<?> specimen){
1558
        List<SpecimenOrObservationBase<?>> allHierarchyDerivatives = new ArrayList<>();
1559
        Collection<FieldUnit> fieldUnits = findFieldUnits(specimen.getUuid(), null);
1560
        if(fieldUnits.isEmpty()){
1561
            allHierarchyDerivatives.add(specimen);
1562
            allHierarchyDerivatives.addAll(getAllChildDerivatives(specimen));
1563
        }
1564
        else{
1565
            for (FieldUnit fieldUnit : fieldUnits) {
1566
                allHierarchyDerivatives.add(fieldUnit);
1567
                allHierarchyDerivatives.addAll(getAllChildDerivatives(fieldUnit));
1568
            }
1569
        }
1570
        return allHierarchyDerivatives;
1571
    }
1572

    
1573
    @Override
1574
    public List<DerivedUnit> getAllChildDerivatives(UUID specimenUuid){
1575
        return getAllChildDerivatives(load(specimenUuid));
1576
    }
1577

    
1578
    @Override
1579
    public List<DerivedUnit> getAllChildDerivatives(SpecimenOrObservationBase<?> specimen){
1580
        if (specimen == null){
1581
            return null;
1582
        }
1583
        List<DerivedUnit> childDerivate = new ArrayList<>();
1584
        Set<DerivationEvent> derivationEvents = specimen.getDerivationEvents();
1585
        for (DerivationEvent derivationEvent : derivationEvents) {
1586
            Set<DerivedUnit> derivatives = derivationEvent.getDerivatives();
1587
            for (DerivedUnit derivedUnit : derivatives) {
1588
                childDerivate.add(derivedUnit);
1589
                childDerivate.addAll(getAllChildDerivatives(derivedUnit.getUuid()));
1590
            }
1591
        }
1592
        return childDerivate;
1593
    }
1594

    
1595
    @Override
1596
    public long countOccurrences(IIdentifiableEntityServiceConfigurator<SpecimenOrObservationBase> config){
1597
        return countByTitle(config);
1598
    }
1599

    
1600
    @Override
1601
    public List<FieldUnit> findFieldUnitsForGatheringEvent(UUID gatheringEventUuid) {
1602
        return dao.findFieldUnitsForGatheringEvent(gatheringEventUuid, null, null, null, null);
1603
    }
1604

    
1605
    @Override
1606
    public List<Point> findPointsForFieldUnitList(List<UUID> fieldUnitUuids) {
1607
        return dao.findPointsForFieldUnitList(fieldUnitUuids);
1608
    }
1609
}
(74-74/97)