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.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

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

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

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

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

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

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

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

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

    
319
        beanInitializer.initializeAll(derivedUnitFacadeList, derivedUnitFacadeInitStrategy);
320

    
321
        return derivedUnitFacadeList;
322
    }
323

    
324

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

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

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

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

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

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

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

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

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

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

    
393

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

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

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

    
419
        return derivedUnitDTO;
420
    }
421

    
422
    @Override
423

    
424
    public String getMostSignificantIdentifier(UUID derivedUnitUuid) {
425
        return dao.findMostSignificantIdentifier(derivedUnitUuid);
426
    }
427

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

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

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

    
476

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

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

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

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

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

    
498
        taxa.add(associatedTaxon);
499

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

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

    
510
    }
511

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

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

    
520
    }
521

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

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

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

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

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

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

    
595
        });
596

    
597
        return orderdDTOs;
598
    }
599

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
691

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

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

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

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

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

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

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

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

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

    
741

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

    
754
        return rootUnitDTOs;
755

    
756
    }
757

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

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

    
779
    }
780

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

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

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

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

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

    
838
    }
839

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

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

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

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

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

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

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

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

    
907
    }
908

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

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

    
927

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

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

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

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

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

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

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

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

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

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

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

    
1129
        DeleteResult deleteResult = isDeletable(specimen.getUuid(), config);
1130
        if (!deleteResult.isOk()) {
1131
            return deleteResult;
1132
        }
1133

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

    
1154
        // check related objects
1155
        Set<CdmBase> relatedObjects = deleteResult.getRelatedObjects();
1156

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

    
1235
        }
1236
        deleteResult.includeResult(delete(specimen));
1237

    
1238
        return deleteResult;
1239
    }
1240

    
1241
    @Override
1242
    public Collection<IndividualsAssociation> listIndividualsAssociations(SpecimenOrObservationBase<?> specimen, Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1243
        return dao.listIndividualsAssociations(specimen, limit, start, orderHints, propertyPaths);
1244
    }
1245

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

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

    
1269
        return associatedTaxa;
1270
    }
1271

    
1272

    
1273

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

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

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

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

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

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

    
1370
    @Override
1371
    public Collection<SpecimenTypeDesignation> listTypeDesignations(DerivedUnit specimen,
1372
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1373
        return dao.listTypeDesignations(specimen, limit, start, orderHints, propertyPaths);
1374
    }
1375

    
1376
    @Override
1377
    public Collection<DescriptionBase<?>> listDescriptionsWithDescriptionSpecimen(
1378
            SpecimenOrObservationBase<?> specimen, Integer limit, Integer start, List<OrderHint> orderHints,
1379
            List<String> propertyPaths) {
1380
        return dao.listDescriptionsWithDescriptionSpecimen(specimen, limit, start, orderHints, propertyPaths);
1381
    }
1382

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

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

    
1425
            return dao.countOccurrences(occurrenceConfig.getClazz(),
1426
                    occurrenceConfig.getTitleSearchStringSqlized(), occurrenceConfig.getSignificantIdentifier(),
1427
                    occurrenceConfig.getSpecimenType(), taxon, taxonName, occurrenceConfig.getMatchMode(), null, null,
1428
                    occurrenceConfig.getOrderHints(), occurrenceConfig.getPropertyPaths());
1429
        }
1430
        else{
1431
            return super.countByTitle(config);
1432
        }
1433
    }
1434

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

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

    
1477
        List<DerivedUnitDTO> dtos = new ArrayList<>();
1478
        occurrences.forEach(derivedUnit->dtos.add(assembleDerivedUnitDTO(derivedUnit)));
1479
        return dtos;
1480
    }
1481

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

    
1506
            long count = Integer.valueOf(occurrences.size()).longValue();
1507
            return new DefaultPagerImpl<>(config.getPageNumber(), count, config.getPageSize(), (List<S>)occurrences);
1508
        }
1509
        return super.findByTitle(config);
1510
    }
1511

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

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

    
1569
    @Override
1570
    public List<DerivedUnit> getAllChildDerivatives(UUID specimenUuid){
1571
        return getAllChildDerivatives(load(specimenUuid));
1572
    }
1573

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

    
1591
    @Override
1592
    public long countOccurrences(IIdentifiableEntityServiceConfigurator<SpecimenOrObservationBase> config){
1593
        return countByTitle(config);
1594
    }
1595

    
1596
    @Override
1597
    public List<FieldUnit> findFieldUnitsForGatheringEvent(UUID gatheringEventUuid) {
1598
        return dao.findFieldUnitsForGatheringEvent(gatheringEventUuid, null, null, null, null);
1599
    }
1600

    
1601
    @Override
1602
    public List<Point> findPointsForFieldUnitList(List<UUID> fieldUnitUuids) {
1603
        return dao.findPointsForFieldUnitList(fieldUnitUuids);
1604
    }
1605
}
(72-72/95)