Project

General

Profile

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

    
11
import java.io.IOException;
12
import java.util.ArrayList;
13
import java.util.Arrays;
14
import java.util.Collection;
15
import java.util.Collections;
16
import java.util.Comparator;
17
import java.util.EnumSet;
18
import java.util.HashMap;
19
import java.util.HashSet;
20
import java.util.Iterator;
21
import java.util.List;
22
import java.util.Map;
23
import java.util.Set;
24
import java.util.UUID;
25
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.hibernate.HibernateProxyHelper;
71
import eu.etaxonomy.cdm.model.CdmBaseType;
72
import eu.etaxonomy.cdm.model.common.CdmBase;
73
import eu.etaxonomy.cdm.model.common.Language;
74
import eu.etaxonomy.cdm.model.description.DescriptionBase;
75
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
76
import eu.etaxonomy.cdm.model.description.IndividualsAssociation;
77
import eu.etaxonomy.cdm.model.description.SpecimenDescription;
78
import eu.etaxonomy.cdm.model.description.TaxonDescription;
79
import eu.etaxonomy.cdm.model.location.Point;
80
import eu.etaxonomy.cdm.model.media.Media;
81
import eu.etaxonomy.cdm.model.molecular.AmplificationResult;
82
import eu.etaxonomy.cdm.model.molecular.DnaSample;
83
import eu.etaxonomy.cdm.model.molecular.Sequence;
84
import eu.etaxonomy.cdm.model.molecular.SingleRead;
85
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
86
import eu.etaxonomy.cdm.model.name.TaxonName;
87
import eu.etaxonomy.cdm.model.occurrence.DerivationEvent;
88
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
89
import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
90
import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
91
import eu.etaxonomy.cdm.model.occurrence.GatheringEvent;
92
import eu.etaxonomy.cdm.model.occurrence.MediaSpecimen;
93
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
94
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;
95
import eu.etaxonomy.cdm.model.taxon.Taxon;
96
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
97
import eu.etaxonomy.cdm.persistence.dao.initializer.AbstractBeanInitializer;
98
import eu.etaxonomy.cdm.persistence.dao.occurrence.IOccurrenceDao;
99
import eu.etaxonomy.cdm.persistence.dto.SpecimenNodeWrapper;
100
import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
101
import eu.etaxonomy.cdm.persistence.query.AssignmentStatus;
102
import eu.etaxonomy.cdm.persistence.query.OrderHint;
103
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
104

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

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

    
117
    @Autowired
118
    private IDescriptionService descriptionService;
119

    
120
    @Autowired
121
    private INameService nameService;
122

    
123
    @Autowired
124
    private IEventBaseService eventService;
125

    
126
    @Autowired
127
    private ITaxonService taxonService;
128

    
129
    @Autowired
130
    private ISequenceService sequenceService;
131

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

    
135
    @Autowired
136
    private ILuceneIndexToolProvider luceneIndexToolProvider;
137

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
317
        beanInitializer.initializeAll(derivedUnitFacadeList, derivedUnitFacadeInitStrategy);
318

    
319
        return derivedUnitFacadeList;
320
    }
321

    
322

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

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

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

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

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

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

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

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

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

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

    
391

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

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

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

    
417
        return derivedUnitDTO;
418
    }
419

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

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

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

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

    
474

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

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

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

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

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

    
496
        taxa.add(associatedTaxon);
497

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

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

    
508
    }
509

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

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

    
518
    }
519

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

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

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

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

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

    
572
            @Override
573
            public int compare(SpecimenOrObservationBaseDTO o1, SpecimenOrObservationBaseDTO o2) {
574
                if(o1 instanceof FieldUnitDTO && o2 instanceof FieldUnitDTO) {
575
                    FieldUnitDTO fu1 = (FieldUnitDTO)o1;
576
                    FieldUnitDTO fu2 = (FieldUnitDTO)o2;
577
                    if(fu1.getDate() == null && fu2.getDate() == null) {
578
                        return 0;
579
                    }
580
                    if(fu1.getDate() != null && fu2.getDate() == null) {
581
                        return 1;
582
                    }
583
                    if(fu1.getDate() == null && fu2.getDate() != null) {
584
                        return -1;
585
                    }
586
                    return fu1.getDate().compareTo(fu2.getDate());
587
                }
588
                if(o1 instanceof DerivedUnitDTO && o2 instanceof DerivedUnitDTO) {
589
                    SpecimenOrObservationBaseDTO du1 = o1;
590
                    SpecimenOrObservationBaseDTO du2 = o2;
591
                    return StringUtils.compare(du1.getLabel(), du2.getLabel());
592
                 }
593
                if(o1 instanceof FieldUnitDTO && o2 instanceof DerivedUnitDTO) {
594
                    return -1;
595
                } else {
596
                    return 1;
597
                }
598
            }
599
        });
600

    
601
        return orderdDTOs;
602
    }
603

    
604
    @Override
605
    @Transactional
606
    @Deprecated
607
    public  SpecimenOrObservationBaseDTO findByAccessionNumber(String accessionNumberString, List<OrderHint> orderHints)  {
608
        return findByAccessionNumber(accessionNumberString, orderHints);
609
    }
610

    
611
    @Override
612
    @Transactional
613
    public  SpecimenOrObservationBaseDTO findByGeneticAccessionNumber(String accessionNumberString, List<OrderHint> orderHints)  {
614

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

    
630
    @Override
631
    public Pager<SearchResult<SpecimenOrObservationBase>> findByFullText(
632
            Class<? extends SpecimenOrObservationBase> clazz, String queryString, Rectangle boundingBox, List<Language> languages,
633
            boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
634
            List<String> propertyPaths) throws IOException, LuceneParseException {
635

    
636
        LuceneSearch luceneSearch = prepareByFullTextSearch(clazz, queryString, boundingBox, languages, highlightFragments);
637

    
638
        // --- execute search
639
        TopGroups<BytesRef> topDocsResultSet;
640
        try {
641
            topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
642
        } catch (ParseException e) {
643
            LuceneParseException parseException = new LuceneParseException(e.getMessage());
644
            parseException.setStackTrace(e.getStackTrace());
645
            throw parseException;
646
        }
647

    
648
        Map<CdmBaseType, String> idFieldMap = new HashMap<>();
649
        idFieldMap.put(CdmBaseType.SPECIMEN_OR_OBSERVATIONBASE, "id");
650

    
651
        // --- initialize taxa, highlight matches ....
652
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
653
        @SuppressWarnings("rawtypes")
654
        List<SearchResult<SpecimenOrObservationBase>> searchResults = searchResultBuilder.createResultSet(
655
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
656

    
657
        int totalHits = topDocsResultSet != null ? topDocsResultSet.totalGroupCount : 0;
658

    
659
        return new DefaultPagerImpl<>(pageNumber, Long.valueOf(totalHits), pageSize, searchResults);
660
    }
661

    
662
    private LuceneSearch prepareByFullTextSearch(Class<? extends SpecimenOrObservationBase> clazz, String queryString, Rectangle bbox,
663
            List<Language> languages, boolean highlightFragments) {
664

    
665
        Builder finalQueryBuilder = new Builder();
666
        Builder textQueryBuilder = new Builder();
667

    
668
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, FieldUnit.class);
669
        QueryFactory queryFactory = luceneIndexToolProvider.newQueryFactoryFor(FieldUnit.class);
670

    
671
        // --- criteria
672
        luceneSearch.setCdmTypRestriction(clazz);
673
        if (queryString != null) {
674
            textQueryBuilder.add(queryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
675
            finalQueryBuilder.add(textQueryBuilder.build(), Occur.MUST);
676
        }
677

    
678
        // --- spacial query
679
        if (bbox != null) {
680
            finalQueryBuilder.add(QueryFactory.buildSpatialQueryByRange(bbox, "gatheringEvent.exactLocation.point"), Occur.MUST);
681
        }
682

    
683
        luceneSearch.setQuery(finalQueryBuilder.build());
684

    
685
        // --- sorting
686
        SortField[] sortFields = new SortField[] { SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.Type.STRING, false) };
687
        luceneSearch.setSortFields(sortFields);
688

    
689
        if (highlightFragments) {
690
            luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray());
691
        }
692
        return luceneSearch;
693
    }
694

    
695

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

    
702
        // FIXME: use HQL queries to avoid entity instantiation and to increase performance
703

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

    
716
        fieldUnits = beanInitializer.initializeAll(fieldUnits, propertyPaths);
717
        return fieldUnits;
718
    }
719

    
720
    @Override
721
    @Transactional(readOnly=true)
722
    public Collection<SpecimenOrObservationBase> findRootUnits(UUID derivedUnitUuid, List<String> propertyPaths) {
723

    
724
        // FIXME: use HQL queries to avoid entity instantiation and to increase performance
725

    
726
        SpecimenOrObservationBase<?> specimen = load(derivedUnitUuid);
727
        Collection<SpecimenOrObservationBase> rootUnits = new ArrayList<>();
728
        if (specimen == null){
729
            return null;
730
        }
731
        if (specimen.isInstanceOf(FieldUnit.class)) {
732
            rootUnits.add(HibernateProxyHelper.deproxy(specimen, FieldUnit.class));
733
        }
734
        else if(specimen.isInstanceOf(DerivedUnit.class)){
735
            rootUnits.addAll(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class).collectRootUnits(SpecimenOrObservationBase.class));
736
        }
737

    
738
        rootUnits = beanInitializer.initializeAll(rootUnits, propertyPaths);
739
        return rootUnits;
740
    }
741

    
742
    @Override
743
    public Collection<SpecimenOrObservationBaseDTO> findRootUnitDTOs(UUID unitUUID) {
744

    
745

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

    
758
        return rootUnitDTOs;
759

    
760
    }
761

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

    
779
        HashMap<UUID, SpecimenOrObservationBaseDTO> rootUnitDTOs = new HashMap<>();
780
        _findRootUnitDTOs(derivedUnitDTO, rootUnitDTOs, alreadyCollectedSpecimen);
781
        return rootUnitDTOs.values();
782

    
783
    }
784

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

    
794
        List<String> propertyPaths = new ArrayList<>();
795

    
796
        // add the supplied DTO the the alreadyCollectedSpecimen if not yet there
797
        if(!alreadyCollectedSpecimen.containsKey(derivedUnitDTO.getUuid())) {
798
            alreadyCollectedSpecimen.put(derivedUnitDTO.getUuid(), derivedUnitDTO);
799
        }
800

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

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

    
842
    }
843

    
844
    @Override
845
    @Transactional(readOnly=true)
846
    public FieldUnitDTO loadFieldUnitDTO(UUID derivedUnitUuid) {
847

    
848
        FieldUnitDTO fieldUnitDTO = null;
849
        DerivedUnitDTO derivedUnitDTO = null;
850

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

    
862
                    Set<SpecimenOrObservationBaseDTO> originals = originalDTOs(derivedUnitDTO.getUuid());
863

    
864
                    if(originals.isEmpty()){
865
                        break;
866
                    }
867
                    if (originals.size() > 1){
868
                        logger.debug("The derived unit with uuid " + derivedUnitUuid + "has more than one orginal, ignoring all but the first one.");
869
                    }
870

    
871
                    SpecimenOrObservationBaseDTO originalDTO = originals.iterator().next();
872

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

    
895
                    if (originalDTO instanceof FieldUnitDTO){
896
                        fieldUnitDTO = (FieldUnitDTO)originalDTO;
897
                        break;
898
                    }else{
899
                        // So this must be a DerivedUnitDTO
900
                        if (derivedUnitDTO == null){
901
                            derivedUnitDTO = (DerivedUnitDTO)originalDTO;
902
                        } else {
903
                            derivedUnitDTO = (DerivedUnitDTO)originalDTO;
904
                        }
905
                    }
906
                }
907
            }
908
        }
909
        return fieldUnitDTO;
910

    
911
    }
912

    
913
    /**
914
     * @param originalDTO
915
     * @return
916
     */
917
    private Set<SpecimenOrObservationBaseDTO> originalDTOs(UUID derivativeUuid) {
918

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

    
931

    
932
    @Override
933
    @Transactional(readOnly = false)
934
    public UpdateResult moveSequence(DnaSample from, DnaSample to, Sequence sequence) {
935
        return moveSequence(from.getUuid(), to.getUuid(), sequence.getUuid());
936
    }
937

    
938
    @Override
939
    @Transactional(readOnly = false)
940
    public UpdateResult moveSequence(UUID fromUuid, UUID toUuid, UUID sequenceUuid) {
941
        // reload specimens to avoid session conflicts
942
        DnaSample from = (DnaSample) load(fromUuid);
943
        DnaSample to = (DnaSample) load(toUuid);
944
        Sequence sequence = sequenceService.load(sequenceUuid);
945

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

    
961
    @Override
962
    @Transactional(readOnly = false)
963
    public boolean moveDerivate(SpecimenOrObservationBase<?> from, SpecimenOrObservationBase<?> to, DerivedUnit derivate) {
964
        return moveDerivate(from!=null?from.getUuid():null, to.getUuid(), derivate.getUuid()).isOk();
965
    }
966

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

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

    
1018
            if(from!=null){
1019
                saveOrUpdate(from);
1020
            }
1021
            saveOrUpdate(to);
1022
            result.setStatus(Status.OK);
1023
            result.addUpdatedObject(from);
1024
            result.addUpdatedObject(to);
1025
        } else {
1026
            result.setStatus(Status.ERROR);
1027
        }
1028
        return result;
1029
    }
1030

    
1031
    @Override
1032
    public DeleteResult isDeletable(UUID specimenUuid, DeleteConfiguratorBase config) {
1033
        DeleteResult deleteResult = new DeleteResult();
1034
        SpecimenOrObservationBase specimen = this.load(specimenUuid);
1035
        SpecimenDeleteConfigurator specimenDeleteConfigurator = (SpecimenDeleteConfigurator) config;
1036

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

    
1122
    /**
1123
     * {@inheritDoc}
1124
     */
1125
    @Transactional(readOnly = false)
1126
    @Override
1127
    public DeleteResult delete(UUID specimenUuid, SpecimenDeleteConfigurator config) {
1128
        return delete(load(specimenUuid), config);
1129
    }
1130

    
1131

    
1132
    @Transactional(readOnly = false)
1133
    @Override
1134
    public DeleteResult delete(SpecimenOrObservationBase<?> specimen, SpecimenDeleteConfigurator config) {
1135
        specimen = HibernateProxyHelper.deproxy(specimen, SpecimenOrObservationBase.class);
1136

    
1137
        DeleteResult deleteResult = isDeletable(specimen.getUuid(), config);
1138
        if (!deleteResult.isOk()) {
1139
            return deleteResult;
1140
        }
1141

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

    
1161

    
1162

    
1163

    
1164
        // check related objects
1165
        Set<CdmBase> relatedObjects = deleteResult.getRelatedObjects();
1166

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

    
1244
        }
1245
        deleteResult.includeResult(delete(specimen));
1246

    
1247
        return deleteResult;
1248
    }
1249

    
1250
    @Override
1251
    public Collection<IndividualsAssociation> listIndividualsAssociations(SpecimenOrObservationBase<?> specimen, Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1252
        return dao.listIndividualsAssociations(specimen, limit, start, orderHints, propertyPaths);
1253
    }
1254

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

    
1268
        //individuals associations
1269
        associatedTaxa.addAll(listIndividualsAssociationTaxa(specimen, includeUnpublished, limit, start, orderHints, propertyPaths));
1270
        //type designation
1271
        if(specimen.isInstanceOf(DerivedUnit.class)){
1272
            associatedTaxa.addAll(listTypeDesignationTaxa(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class),
1273
                  includeUnpublished, limit, start, orderHints, propertyPaths));
1274
        }
1275
        //determinations
1276
        associatedTaxa.addAll(listDeterminedTaxa(specimen, includeUnpublished, limit, start, orderHints, propertyPaths));
1277

    
1278
        return associatedTaxa;
1279
    }
1280

    
1281

    
1282

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

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

    
1337
    /**
1338
     * {@inheritDoc}
1339
     */
1340
    @Override
1341
    public Collection<TaxonBase<?>> listIndividualsAssociationTaxa(SpecimenOrObservationBase<?> specimen, Integer limit,
1342
            Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1343
        return listIndividualsAssociationTaxa(specimen, INCLUDE_UNPUBLISHED, limit, start, orderHints, propertyPaths);
1344
    }
1345

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

    
1361
    @Override
1362
    public Collection<DeterminationEvent> listDeterminationEvents(SpecimenOrObservationBase<?> specimen,
1363
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1364
        return dao.listDeterminationEvents(specimen, limit, start, orderHints, propertyPaths);
1365
    }
1366

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

    
1379
    @Override
1380
    public Collection<SpecimenTypeDesignation> listTypeDesignations(DerivedUnit specimen,
1381
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1382
        return dao.listTypeDesignations(specimen, limit, start, orderHints, propertyPaths);
1383
    }
1384

    
1385
    @Override
1386
    public Collection<DescriptionBase<?>> listDescriptionsWithDescriptionSpecimen(
1387
            SpecimenOrObservationBase<?> specimen, Integer limit, Integer start, List<OrderHint> orderHints,
1388
            List<String> propertyPaths) {
1389
        return dao.listDescriptionsWithDescriptionSpecimen(specimen, limit, start, orderHints, propertyPaths);
1390
    }
1391

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

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

    
1434
            return dao.countOccurrences(occurrenceConfig.getClazz(),
1435
                    occurrenceConfig.getTitleSearchStringSqlized(), occurrenceConfig.getSignificantIdentifier(),
1436
                    occurrenceConfig.getSpecimenType(), taxon, taxonName, occurrenceConfig.getMatchMode(), null, null,
1437
                    occurrenceConfig.getOrderHints(), occurrenceConfig.getPropertyPaths());
1438
        }
1439
        else{
1440
            return super.countByTitle(config);
1441
        }
1442
    }
1443

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

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

    
1486
        List<DerivedUnitDTO> dtos = new ArrayList<>();
1487
        occurrences.forEach(derivedUnit->dtos.add(assembleDerivedUnitDTO(derivedUnit)));
1488
        return dtos;
1489
    }
1490

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

    
1515
            long count = Integer.valueOf(occurrences.size()).longValue();
1516
            return new DefaultPagerImpl<>(config.getPageNumber(), count, config.getPageSize(), (List<S>)occurrences);
1517
        }
1518
        return super.findByTitle(config);
1519
    }
1520

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

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

    
1578
    @Override
1579
    public List<DerivedUnit> getAllChildDerivatives(UUID specimenUuid){
1580
        return getAllChildDerivatives(load(specimenUuid));
1581
    }
1582

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

    
1600
    @Override
1601
    public long countOccurrences(IIdentifiableEntityServiceConfigurator<SpecimenOrObservationBase> config){
1602
        return countByTitle(config);
1603
    }
1604

    
1605
    @Override
1606
    public List<FieldUnit> findFieldUnitsForGatheringEvent(UUID gatheringEventUuid) {
1607
        return dao.findFieldUnitsForGatheringEvent(gatheringEventUuid, null, null, null, null);
1608
    }
1609

    
1610
    @Override
1611
    public List<Point> findPointsForFieldUnitList(List<UUID> fieldUnitUuids) {
1612
        return dao.findPointsForFieldUnitList(fieldUnitUuids);
1613
    }
1614
}
(74-74/97)