Project

General

Profile

Download (76.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

    
10
package eu.etaxonomy.cdm.api.service;
11

    
12
import java.io.IOException;
13
import java.net.URI;
14
import java.net.URISyntaxException;
15
import java.util.ArrayList;
16
import java.util.Arrays;
17
import java.util.Collection;
18
import java.util.Collections;
19
import java.util.HashMap;
20
import java.util.HashSet;
21
import java.util.Iterator;
22
import java.util.List;
23
import java.util.Map;
24
import java.util.Map.Entry;
25
import java.util.Set;
26
import java.util.UUID;
27

    
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.joda.time.Partial;
38
import org.springframework.beans.factory.annotation.Autowired;
39
import org.springframework.dao.DataRetrievalFailureException;
40
import org.springframework.stereotype.Service;
41
import org.springframework.transaction.annotation.Transactional;
42

    
43
import eu.etaxonomy.cdm.api.facade.DerivedUnitFacade;
44
import eu.etaxonomy.cdm.api.facade.DerivedUnitFacadeConfigurator;
45
import eu.etaxonomy.cdm.api.facade.DerivedUnitFacadeNotSupportedException;
46
import eu.etaxonomy.cdm.api.service.UpdateResult.Status;
47
import eu.etaxonomy.cdm.api.service.config.DeleteConfiguratorBase;
48
import eu.etaxonomy.cdm.api.service.config.FindOccurrencesConfigurator;
49
import eu.etaxonomy.cdm.api.service.config.FindOccurrencesConfigurator.AssignmentStatus;
50
import eu.etaxonomy.cdm.api.service.config.IIdentifiableEntityServiceConfigurator;
51
import eu.etaxonomy.cdm.api.service.config.SpecimenDeleteConfigurator;
52
import eu.etaxonomy.cdm.api.service.dto.DerivateDTO;
53
import eu.etaxonomy.cdm.api.service.dto.DerivateDataDTO;
54
import eu.etaxonomy.cdm.api.service.dto.DerivateDataDTO.ContigFile;
55
import eu.etaxonomy.cdm.api.service.dto.DerivateDataDTO.Link;
56
import eu.etaxonomy.cdm.api.service.dto.DerivateDataDTO.MolecularData;
57
import eu.etaxonomy.cdm.api.service.dto.FieldUnitDTO;
58
import eu.etaxonomy.cdm.api.service.dto.PreservedSpecimenDTO;
59
import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;
60
import eu.etaxonomy.cdm.api.service.molecular.ISequenceService;
61
import eu.etaxonomy.cdm.api.service.pager.Pager;
62
import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
63
import eu.etaxonomy.cdm.api.service.search.ILuceneIndexToolProvider;
64
import eu.etaxonomy.cdm.api.service.search.ISearchResultBuilder;
65
import eu.etaxonomy.cdm.api.service.search.LuceneParseException;
66
import eu.etaxonomy.cdm.api.service.search.LuceneSearch;
67
import eu.etaxonomy.cdm.api.service.search.QueryFactory;
68
import eu.etaxonomy.cdm.api.service.search.SearchResult;
69
import eu.etaxonomy.cdm.api.service.search.SearchResultBuilder;
70
import eu.etaxonomy.cdm.api.service.util.TaxonRelationshipEdge;
71
import eu.etaxonomy.cdm.common.CdmUtils;
72
import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
73
import eu.etaxonomy.cdm.format.CdmFormatterFactory;
74
import eu.etaxonomy.cdm.format.ICdmFormatter.FormatKey;
75
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
76
import eu.etaxonomy.cdm.model.CdmBaseType;
77
import eu.etaxonomy.cdm.model.agent.AgentBase;
78
import eu.etaxonomy.cdm.model.common.CdmBase;
79
import eu.etaxonomy.cdm.model.common.DefinedTerm;
80
import eu.etaxonomy.cdm.model.common.DefinedTermBase;
81
import eu.etaxonomy.cdm.model.common.ICdmBase;
82
import eu.etaxonomy.cdm.model.common.Language;
83
import eu.etaxonomy.cdm.model.description.CategoricalData;
84
import eu.etaxonomy.cdm.model.description.DescriptionBase;
85
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
86
import eu.etaxonomy.cdm.model.description.IndividualsAssociation;
87
import eu.etaxonomy.cdm.model.description.QuantitativeData;
88
import eu.etaxonomy.cdm.model.description.SpecimenDescription;
89
import eu.etaxonomy.cdm.model.description.TaxonDescription;
90
import eu.etaxonomy.cdm.model.location.Country;
91
import eu.etaxonomy.cdm.model.location.NamedArea;
92
import eu.etaxonomy.cdm.model.media.Media;
93
import eu.etaxonomy.cdm.model.media.MediaRepresentation;
94
import eu.etaxonomy.cdm.model.media.MediaRepresentationPart;
95
import eu.etaxonomy.cdm.model.media.MediaUtils;
96
import eu.etaxonomy.cdm.model.molecular.AmplificationResult;
97
import eu.etaxonomy.cdm.model.molecular.DnaSample;
98
import eu.etaxonomy.cdm.model.molecular.Sequence;
99
import eu.etaxonomy.cdm.model.molecular.SingleRead;
100
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
101
import eu.etaxonomy.cdm.model.name.TaxonName;
102
import eu.etaxonomy.cdm.model.name.TypeDesignationStatusBase;
103
import eu.etaxonomy.cdm.model.occurrence.DerivationEvent;
104
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
105
import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent;
106
import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
107
import eu.etaxonomy.cdm.model.occurrence.GatheringEvent;
108
import eu.etaxonomy.cdm.model.occurrence.MediaSpecimen;
109
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
110
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType;
111
import eu.etaxonomy.cdm.model.taxon.Taxon;
112
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
113
import eu.etaxonomy.cdm.persistence.dao.common.IDefinedTermDao;
114
import eu.etaxonomy.cdm.persistence.dao.initializer.AbstractBeanInitializer;
115
import eu.etaxonomy.cdm.persistence.dao.molecular.ISingleReadDao;
116
import eu.etaxonomy.cdm.persistence.dao.occurrence.IOccurrenceDao;
117
import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
118
import eu.etaxonomy.cdm.persistence.query.OrderHint;
119
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
120
import eu.etaxonomy.cdm.strategy.cache.common.IdentifiableEntityDefaultCacheStrategy;
121

    
122
/**
123
 * @author a.babadshanjan
124
 * @created 01.09.2008
125
 */
126
@Service
127
@Transactional(readOnly = true)
128
public class OccurrenceServiceImpl extends IdentifiableServiceBase<SpecimenOrObservationBase, IOccurrenceDao> implements IOccurrenceService {
129

    
130
    static private final Logger logger = Logger.getLogger(OccurrenceServiceImpl.class);
131

    
132
    @Autowired
133
    private IDefinedTermDao definedTermDao;
134

    
135
    @Autowired
136
    private IDescriptionService descriptionService;
137

    
138
    @Autowired
139
    private INameService nameService;
140

    
141
    @Autowired
142
    private ITaxonService taxonService;
143

    
144
    @Autowired
145
    private ISequenceService sequenceService;
146

    
147
    @Autowired
148
    private ISingleReadDao singleReadDao;
149

    
150
    @Autowired
151
    private AbstractBeanInitializer beanInitializer;
152

    
153
    @Autowired
154
    private ILuceneIndexToolProvider luceneIndexToolProvider;
155

    
156
    private static final String SEPARATOR_STRING = ", ";
157

    
158
    public OccurrenceServiceImpl() {
159
        logger.debug("Load OccurrenceService Bean");
160
    }
161

    
162

    
163
    @Override
164
    @Transactional(readOnly = false)
165
    public void updateTitleCache(Class<? extends SpecimenOrObservationBase> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<SpecimenOrObservationBase> cacheStrategy, IProgressMonitor monitor) {
166
        if (clazz == null) {
167
            clazz = SpecimenOrObservationBase.class;
168
        }
169
        super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);
170
    }
171

    
172
    /**
173
     * FIXME Candidate for harmonization
174
     * move to termService
175
     */
176
    @Override
177
    public Country getCountryByIso(String iso639) {
178
        return this.definedTermDao.getCountryByIso(iso639);
179

    
180
    }
181

    
182
    /**
183
     * FIXME Candidate for harmonization
184
     * move to termService
185
     */
186
    @Override
187
    public List<Country> getCountryByName(String name) {
188
        List<? extends DefinedTermBase> terms = this.definedTermDao.findByTitle(Country.class, name, null, null, null, null, null, null);
189
        List<Country> countries = new ArrayList<>();
190
        for (int i = 0; i < terms.size(); i++) {
191
            countries.add((Country) terms.get(i));
192
        }
193
        return countries;
194
    }
195

    
196
    @Override
197
    @Autowired
198
    protected void setDao(IOccurrenceDao dao) {
199
        this.dao = dao;
200
    }
201

    
202
    @Override
203
    public Pager<DerivationEvent> getDerivationEvents(SpecimenOrObservationBase occurence, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
204
        Integer numberOfResults = dao.countDerivationEvents(occurence);
205

    
206
        List<DerivationEvent> results = new ArrayList<>();
207
        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
208
            results = dao.getDerivationEvents(occurence, pageSize, pageNumber, propertyPaths);
209
        }
210

    
211
        return new DefaultPagerImpl<DerivationEvent>(pageNumber, numberOfResults, pageSize, results);
212
    }
213

    
214
    @Override
215
    public int countDeterminations(SpecimenOrObservationBase occurence, TaxonBase taxonbase) {
216
        return dao.countDeterminations(occurence, taxonbase);
217
    }
218

    
219
    @Override
220
    public Pager<DeterminationEvent> getDeterminations(SpecimenOrObservationBase occurrence, TaxonBase taxonBase, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
221
        Integer numberOfResults = dao.countDeterminations(occurrence, taxonBase);
222

    
223
        List<DeterminationEvent> results = new ArrayList<>();
224
        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
225
            results = dao.getDeterminations(occurrence, taxonBase, pageSize, pageNumber, propertyPaths);
226
        }
227

    
228
        return new DefaultPagerImpl<DeterminationEvent>(pageNumber, numberOfResults, pageSize, results);
229
    }
230

    
231
    @Override
232
    public Pager<Media> getMedia(SpecimenOrObservationBase occurence, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
233
        Integer numberOfResults = dao.countMedia(occurence);
234

    
235
        List<Media> results = new ArrayList<>();
236
        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
237
            results = dao.getMedia(occurence, pageSize, pageNumber, propertyPaths);
238
        }
239

    
240
        return new DefaultPagerImpl<Media>(pageNumber, numberOfResults, pageSize, results);
241
    }
242

    
243
    @Override
244
    public Pager<Media> getMediainHierarchy(SpecimenOrObservationBase rootOccurence, Integer pageSize,
245
            Integer pageNumber, List<String> propertyPaths) {
246
        List<Media> media = new ArrayList<>();
247
        //media specimens
248
        if(rootOccurence.isInstanceOf(MediaSpecimen.class)){
249
            MediaSpecimen mediaSpecimen = HibernateProxyHelper.deproxy(rootOccurence, MediaSpecimen.class);
250
            media.add(mediaSpecimen.getMediaSpecimen());
251
        }
252
        // pherograms & gelPhotos
253
        if (rootOccurence.isInstanceOf(DnaSample.class)) {
254
            DnaSample dnaSample = CdmBase.deproxy(rootOccurence, DnaSample.class);
255
            Set<Sequence> sequences = dnaSample.getSequences();
256
            //we do show only those gelPhotos which lead to a consensus sequence
257
            for (Sequence sequence : sequences) {
258
                Set<Media> dnaRelatedMedia = new HashSet<>();
259
                for (SingleRead singleRead : sequence.getSingleReads()){
260
                    AmplificationResult amplification = singleRead.getAmplificationResult();
261
                    dnaRelatedMedia.add(amplification.getGelPhoto());
262
                    dnaRelatedMedia.add(singleRead.getPherogram());
263
                    dnaRelatedMedia.remove(null);
264
                }
265
                media.addAll(dnaRelatedMedia);
266
            }
267
        }
268
        if(rootOccurence.isInstanceOf(DerivedUnit.class)){
269
            DerivedUnit derivedUnit = HibernateProxyHelper.deproxy(rootOccurence, DerivedUnit.class);
270
            for (DerivationEvent derivationEvent : derivedUnit.getDerivationEvents()) {
271
                for (DerivedUnit childDerivative : derivationEvent.getDerivatives()) {
272
                    media.addAll(getMediainHierarchy(childDerivative, pageSize, pageNumber, propertyPaths).getRecords());
273
                }
274
            }
275
        }
276
        return new DefaultPagerImpl<Media>(pageNumber, media.size(), pageSize, media);
277
    }
278

    
279
    @Override
280
    public Pager<SpecimenOrObservationBase> list(Class<? extends SpecimenOrObservationBase> type, TaxonName determinedAs, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
281
        Integer numberOfResults = dao.count(type, determinedAs);
282
        List<SpecimenOrObservationBase> results = new ArrayList<>();
283
        pageNumber = pageNumber == null ? 0 : pageNumber;
284
        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
285
            Integer start = pageSize == null ? 0 : pageSize * pageNumber;
286
            results = dao.list(type, determinedAs, pageSize, start, orderHints, propertyPaths);
287
        }
288
        return new DefaultPagerImpl<SpecimenOrObservationBase>(pageNumber, numberOfResults, pageSize, results);
289
    }
290

    
291
    @Override
292
    public Pager<SpecimenOrObservationBase> list(Class<? extends SpecimenOrObservationBase> type, TaxonBase determinedAs, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
293
        Integer numberOfResults = dao.count(type, determinedAs);
294
        List<SpecimenOrObservationBase> results = new ArrayList<>();
295
        pageNumber = pageNumber == null ? 0 : pageNumber;
296
        if(numberOfResults > 0) { // no point checking again  //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
297
            Integer start = pageSize == null ? 0 : pageSize * pageNumber;
298
            results = dao.list(type, determinedAs, pageSize, start, orderHints, propertyPaths);
299
        }
300
        return new DefaultPagerImpl<SpecimenOrObservationBase>(pageNumber, numberOfResults, pageSize, results);
301
    }
302

    
303
    @Override
304
    public List<UuidAndTitleCache<DerivedUnit>> getDerivedUnitUuidAndTitleCache() {
305
        return dao.getDerivedUnitUuidAndTitleCache();
306
    }
307

    
308
    @Override
309
    public List<UuidAndTitleCache<FieldUnit>> getFieldUnitUuidAndTitleCache() {
310
        return dao.getFieldUnitUuidAndTitleCache();
311
    }
312

    
313
    @Override
314
    public DerivedUnitFacade getDerivedUnitFacade(DerivedUnit derivedUnit, List<String> propertyPaths) throws DerivedUnitFacadeNotSupportedException {
315
        derivedUnit = (DerivedUnit) dao.load(derivedUnit.getUuid(), null);
316
        DerivedUnitFacadeConfigurator config = DerivedUnitFacadeConfigurator.NewInstance();
317
        config.setThrowExceptionForNonSpecimenPreservationMethodRequest(false);
318
        DerivedUnitFacade derivedUnitFacade = DerivedUnitFacade.NewInstance(derivedUnit, config);
319
        beanInitializer.initialize(derivedUnitFacade, propertyPaths);
320
        return derivedUnitFacade;
321
    }
322

    
323
    @Override
324
    public List<DerivedUnitFacade> listDerivedUnitFacades(
325
            DescriptionBase description, List<String> propertyPaths) {
326

    
327
        List<DerivedUnitFacade> derivedUnitFacadeList = new ArrayList<>();
328
        IndividualsAssociation tempIndividualsAssociation;
329
        SpecimenOrObservationBase tempSpecimenOrObservationBase;
330
        List<IndividualsAssociation> elements = descriptionService.listDescriptionElements(description, null, IndividualsAssociation.class, null, 0, Arrays.asList(new String []{"associatedSpecimenOrObservation"}));
331
        for (IndividualsAssociation element : elements) {
332
            tempIndividualsAssociation = HibernateProxyHelper.deproxy(element, IndividualsAssociation.class);
333
            if (tempIndividualsAssociation.getAssociatedSpecimenOrObservation() != null) {
334
                tempSpecimenOrObservationBase = HibernateProxyHelper.deproxy(tempIndividualsAssociation.getAssociatedSpecimenOrObservation(), SpecimenOrObservationBase.class);
335
                if (tempSpecimenOrObservationBase.isInstanceOf(DerivedUnit.class)) {
336
                    try {
337
                        derivedUnitFacadeList.add(DerivedUnitFacade.NewInstance(HibernateProxyHelper.deproxy(tempSpecimenOrObservationBase, DerivedUnit.class)));
338
                    } catch (DerivedUnitFacadeNotSupportedException e) {
339
                        logger.warn(tempIndividualsAssociation.getAssociatedSpecimenOrObservation().getTitleCache() + " : " + e.getMessage());
340
                    }
341
                }
342
            }
343
        }
344

    
345
        beanInitializer.initializeAll(derivedUnitFacadeList, propertyPaths);
346

    
347
        return derivedUnitFacadeList;
348
    }
349

    
350

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

    
355
        return pageByAssociatedTaxon(type, includeRelationships, associatedTaxon, maxDepth, pageSize, pageNumber, orderHints, propertyPaths).getRecords();
356
    }
357

    
358
    @Override
359
    public Collection<SpecimenOrObservationBase> listFieldUnitsByAssociatedTaxon(Taxon associatedTaxon, List<OrderHint> orderHints, List<String> propertyPaths) {
360
        return pageFieldUnitsByAssociatedTaxon(null, associatedTaxon, null, null, null, null, propertyPaths).getRecords();
361
    }
362

    
363
    @Override
364
    public Pager<SpecimenOrObservationBase> pageFieldUnitsByAssociatedTaxon(Set<TaxonRelationshipEdge> includeRelationships,
365
            Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
366
            List<String> propertyPaths) {
367

    
368
        if (!getSession().contains(associatedTaxon)) {
369
            associatedTaxon = (Taxon) taxonService.load(associatedTaxon.getUuid());
370
        }
371

    
372
        // gather the IDs of all relevant field units
373
        Set<UUID> fieldUnitUuids = new HashSet<>();
374
        List<SpecimenOrObservationBase> records = listByAssociatedTaxon(null, includeRelationships, associatedTaxon, maxDepth, null, null, orderHints, propertyPaths);
375
        for (SpecimenOrObservationBase<?> specimen : records) {
376
            for (FieldUnit fieldUnit : getFieldUnits(specimen.getUuid())) {
377
                fieldUnitUuids.add(fieldUnit.getUuid());
378
            }
379
        }
380
        //dao.list() does the paging of the field units. Passing the field units directly to the Pager would not work
381
        List<SpecimenOrObservationBase> fieldUnits = dao.list(fieldUnitUuids, pageSize, pageNumber, orderHints, propertyPaths);
382
        return new DefaultPagerImpl<SpecimenOrObservationBase>(pageNumber, fieldUnitUuids.size(), pageSize, fieldUnits);
383
    }
384

    
385
    @Override
386
    public FieldUnitDTO assembleFieldUnitDTO(FieldUnit fieldUnit, UUID associatedTaxonUuid) {
387

    
388
        if (!getSession().contains(fieldUnit)) {
389
            fieldUnit = (FieldUnit) load(fieldUnit.getUuid());
390
        }
391
        TaxonBase associatedTaxon = taxonService.load(associatedTaxonUuid);
392

    
393
        FieldUnitDTO fieldUnitDTO = new FieldUnitDTO();
394

    
395
        if (fieldUnit.getGatheringEvent() != null) {
396
            GatheringEvent gatheringEvent = fieldUnit.getGatheringEvent();
397
            // Country
398
            NamedArea country = gatheringEvent.getCountry();
399
            fieldUnitDTO.setCountry(country != null ? country.getLabel() : null);
400
            // Collection
401
            AgentBase collector = gatheringEvent.getCollector();
402
            String fieldNumber = fieldUnit.getFieldNumber();
403
            String collectionString = "";
404
            if (collector != null || fieldNumber != null) {
405
                collectionString += collector != null ? collector : "";
406
                if (!collectionString.isEmpty()) {
407
                    collectionString += " ";
408
                }
409
                collectionString += (fieldNumber != null ? fieldNumber : "");
410
                collectionString.trim();
411
            }
412
            fieldUnitDTO.setCollection(collectionString);
413
            // Date
414
            Partial gatheringDate = gatheringEvent.getGatheringDate();
415
            String dateString = null;
416
            if (gatheringDate != null) {
417
                dateString = gatheringDate.toString();
418
            }
419
            else if(gatheringEvent.getTimeperiod()!=null && gatheringEvent.getTimeperiod().getFreeText()!=null){
420
                dateString = gatheringEvent.getTimeperiod().getFreeText();
421
            }
422
            fieldUnitDTO.setDate(dateString);
423
        }
424

    
425
        // Taxon Name
426
        fieldUnitDTO.setTaxonName(associatedTaxon.getName().getTitleCache());
427

    
428
        // Herbaria map
429
        Map<eu.etaxonomy.cdm.model.occurrence.Collection, Integer> collectionToCountMap = new HashMap<>();
430
        // List of accession numbers for citation
431
        List<String> preservedSpecimenAccessionNumbers = new ArrayList<>();
432

    
433
        // assemble preserved specimen DTOs
434
        Set<DerivationEvent> derivationEvents = fieldUnit.getDerivationEvents();
435
        for (DerivationEvent derivationEvent : derivationEvents) {
436
            Set<DerivedUnit> derivatives = derivationEvent.getDerivatives();
437
            for (DerivedUnit derivedUnit : derivatives) {
438
                if(!derivedUnit.isPublish()){
439
                    continue;
440
                }
441
                // collect accession numbers for citation
442
                String mostSignificantIdentifier = getMostSignificantIdentifier(derivedUnit);
443
                if (mostSignificantIdentifier != null) {
444
                    preservedSpecimenAccessionNumbers.add(mostSignificantIdentifier);
445
                }
446
                // collect collections for herbaria column
447
                if (derivedUnit.getCollection() != null) {
448
                    Integer herbariumCount = collectionToCountMap.get(derivedUnit.getCollection());
449
                    if (herbariumCount == null) {
450
                        herbariumCount = 0;
451
                    }
452
                    collectionToCountMap.put(derivedUnit.getCollection(), herbariumCount + 1);
453
                }
454
                if (derivedUnit.getRecordBasis().equals(SpecimenOrObservationType.PreservedSpecimen)) {
455
                    PreservedSpecimenDTO preservedSpecimenDTO = assemblePreservedSpecimenDTO(derivedUnit, fieldUnitDTO);
456
                    fieldUnitDTO.addPreservedSpecimenDTO(preservedSpecimenDTO);
457
                    fieldUnitDTO.setHasCharacterData(fieldUnitDTO.isHasCharacterData() || preservedSpecimenDTO.isHasCharacterData());
458
                    fieldUnitDTO.setHasDetailImage(fieldUnitDTO.isHasDetailImage() || preservedSpecimenDTO.isHasDetailImage());
459
                    fieldUnitDTO.setHasDna(fieldUnitDTO.isHasDna() || preservedSpecimenDTO.isHasDna());
460
                    fieldUnitDTO.setHasSpecimenScan(fieldUnitDTO.isHasSpecimenScan() || preservedSpecimenDTO.isHasSpecimenScan());
461
                }
462
            }
463
        }
464
        // assemble derivate data DTO
465
        assembleDerivateDataDTO(fieldUnitDTO, fieldUnit);
466

    
467
        // assemble citation
468
        String citation = fieldUnit.getTitleCache();
469
        if((CdmUtils.isBlank(citation) || citation.equals(IdentifiableEntityDefaultCacheStrategy.TITLE_CACHE_GENERATION_NOT_IMPLEMENTED))
470
                && !fieldUnit.isProtectedTitleCache()){
471
            fieldUnit.setTitleCache(null);
472
            citation = fieldUnit.getTitleCache();
473
        }
474
        if (!preservedSpecimenAccessionNumbers.isEmpty()) {
475
            citation += " (";
476
            for (String accessionNumber : preservedSpecimenAccessionNumbers) {
477
                if (!accessionNumber.isEmpty()) {
478
                    citation += accessionNumber + SEPARATOR_STRING;
479
                }
480
            }
481
            citation = removeTail(citation, SEPARATOR_STRING);
482
            citation += ")";
483
        }
484
        fieldUnitDTO.setCitation(citation);
485

    
486
        // assemble herbaria string
487
        String herbariaString = "";
488
        for (Entry<eu.etaxonomy.cdm.model.occurrence.Collection, Integer> e : collectionToCountMap.entrySet()) {
489
            eu.etaxonomy.cdm.model.occurrence.Collection collection = e.getKey();
490
            if (collection.getCode() != null) {
491
                herbariaString += collection.getCode();
492
            }
493
            if (e.getValue() > 1) {
494
                herbariaString += "(" + e.getValue() + ")";
495
            }
496
            herbariaString += SEPARATOR_STRING;
497
        }
498
        herbariaString = removeTail(herbariaString, SEPARATOR_STRING);
499
        fieldUnitDTO.setHerbarium(herbariaString);
500

    
501
        return fieldUnitDTO;
502
    }
503

    
504
    @Override
505
    public PreservedSpecimenDTO assemblePreservedSpecimenDTO(DerivedUnit derivedUnit) {
506
        return assemblePreservedSpecimenDTO(derivedUnit, null);
507
    }
508

    
509
    @Override
510
    public String getMostSignificantIdentifier(DerivedUnit derivedUnit) {
511
        if (derivedUnit.getAccessionNumber() != null && !derivedUnit.getAccessionNumber().isEmpty()) {
512
            return derivedUnit.getAccessionNumber();
513
        }
514
        else if(derivedUnit.getBarcode()!=null && !derivedUnit.getBarcode().isEmpty()){
515
            return derivedUnit.getBarcode();
516
        }
517
        else if(derivedUnit.getCatalogNumber()!=null && !derivedUnit.getCatalogNumber().isEmpty()){
518
            return derivedUnit.getCatalogNumber();
519
        }
520
        return null;
521
    }
522

    
523
    public PreservedSpecimenDTO assemblePreservedSpecimenDTO(DerivedUnit derivedUnit, FieldUnitDTO fieldUnitDTO) {
524
        if (!getSession().contains(derivedUnit)) {
525
            derivedUnit = (DerivedUnit) load(derivedUnit.getUuid());
526
        }
527
        PreservedSpecimenDTO preservedSpecimenDTO = new PreservedSpecimenDTO();
528

    
529
        //specimen identifier
530
        FormatKey collectionKey = FormatKey.COLLECTION_CODE;
531
        String specimenIdentifier = CdmFormatterFactory.format(derivedUnit, collectionKey);
532
        if (CdmUtils.isBlank(specimenIdentifier)) {
533
            collectionKey = FormatKey.COLLECTION_NAME;
534
        }
535
        specimenIdentifier = CdmFormatterFactory.format(derivedUnit, new FormatKey[] {
536
                collectionKey, FormatKey.SPACE,
537
                FormatKey.MOST_SIGNIFICANT_IDENTIFIER, FormatKey.SPACE });
538
        if(CdmUtils.isBlank(specimenIdentifier)){
539
            specimenIdentifier = derivedUnit.getUuid().toString();
540
        }
541
        preservedSpecimenDTO.setAccessionNumber(specimenIdentifier);
542
        preservedSpecimenDTO.setUuid(derivedUnit.getUuid().toString());
543

    
544
        //preferred stable URI
545
        preservedSpecimenDTO.setPreferredStableUri(derivedUnit.getPreferredStableUri());
546

    
547
        // citation
548
        Collection<FieldUnit> fieldUnits = getFieldUnits(derivedUnit);
549
        if (fieldUnits.size() == 1) {
550
            preservedSpecimenDTO.setCitation(fieldUnits.iterator().next().getTitleCache());
551
        }
552
        else{
553
            preservedSpecimenDTO.setCitation("No Citation available. This specimen either has no or multiple field units.");
554
        }
555

    
556
        // character state data
557
        Collection<DescriptionElementBase> characterDataForSpecimen = getCharacterDataForSpecimen(derivedUnit);
558
        if (!characterDataForSpecimen.isEmpty()) {
559
            if (fieldUnitDTO != null) {
560
                fieldUnitDTO.setHasCharacterData(true);
561
            }
562
        }
563
        for (DescriptionElementBase descriptionElementBase : characterDataForSpecimen) {
564
            String character = descriptionElementBase.getFeature().getLabel();
565
            ArrayList<Language> languages = new ArrayList<>(Collections.singleton(Language.DEFAULT()));
566
            if (descriptionElementBase instanceof QuantitativeData) {
567
                QuantitativeData quantitativeData = (QuantitativeData) descriptionElementBase;
568
                DefaultQuantitativeDescriptionBuilder builder = new DefaultQuantitativeDescriptionBuilder();
569
                String state = builder.build(quantitativeData, languages).getText(Language.DEFAULT());
570
                preservedSpecimenDTO.addCharacterData(character, state);
571
            }
572
            else if(descriptionElementBase instanceof CategoricalData){
573
                CategoricalData categoricalData = (CategoricalData) descriptionElementBase;
574
                DefaultCategoricalDescriptionBuilder builder = new DefaultCategoricalDescriptionBuilder();
575
                String state = builder.build(categoricalData, languages).getText(Language.DEFAULT());
576
                preservedSpecimenDTO.addCharacterData(character, state);
577
            }
578
        }
579
        // check type designations
580
        Collection<SpecimenTypeDesignation> specimenTypeDesignations = listTypeDesignations(derivedUnit, null, null, null, null);
581
        for (SpecimenTypeDesignation specimenTypeDesignation : specimenTypeDesignations) {
582
            if (fieldUnitDTO != null) {
583
                fieldUnitDTO.setHasType(true);
584
            }
585
            TypeDesignationStatusBase<?> typeStatus = specimenTypeDesignation.getTypeStatus();
586
            if (typeStatus != null) {
587
                List<String> typedTaxaNames = new ArrayList<>();
588
                String label = typeStatus.getLabel();
589
                Set<TaxonName> typifiedNames = specimenTypeDesignation.getTypifiedNames();
590
                for (TaxonName taxonName : typifiedNames) {
591
                    typedTaxaNames.add(taxonName.getFullTitleCache());
592
                }
593
                preservedSpecimenDTO.addTypes(label, typedTaxaNames);
594
            }
595
        }
596

    
597
        // individuals associations
598
        Collection<IndividualsAssociation> individualsAssociations = listIndividualsAssociations(derivedUnit, null, null, null, null);
599
        for (IndividualsAssociation individualsAssociation : individualsAssociations) {
600
            if (individualsAssociation.getInDescription() != null) {
601
                if (individualsAssociation.getInDescription().isInstanceOf(TaxonDescription.class)) {
602
                    TaxonDescription taxonDescription = HibernateProxyHelper.deproxy(individualsAssociation.getInDescription(), TaxonDescription.class);
603
                    Taxon taxon = taxonDescription.getTaxon();
604
                    if (taxon != null) {
605
                        preservedSpecimenDTO.addAssociatedTaxon(taxon);
606
                    }
607
                }
608
            }
609
        }
610
        // assemble sub derivates
611
        preservedSpecimenDTO.setDerivateDataDTO(assembleDerivateDataDTO(preservedSpecimenDTO, derivedUnit));
612
        return preservedSpecimenDTO;
613
    }
614

    
615
    private DerivateDataDTO assembleDerivateDataDTO(DerivateDTO derivateDTO, SpecimenOrObservationBase<?> specimenOrObservation) {
616
        DerivateDataDTO derivateDataDTO = new DerivateDataDTO();
617
        Collection<DerivedUnit> childDerivates = getDerivedUnitsFor(specimenOrObservation);
618
        for (DerivedUnit childDerivate : childDerivates) {
619
            // assemble molecular data
620
            //pattern: DNAMarker [contig1, primer1_1, primer1_2, ...][contig2, primer2_1, ...]...
621
            if (childDerivate.isInstanceOf(DnaSample.class)) {
622
                if (childDerivate.getRecordBasis() == SpecimenOrObservationType.TissueSample) {
623
                    // TODO implement TissueSample assembly for web service
624
                }
625
                if (childDerivate.getRecordBasis() == SpecimenOrObservationType.DnaSample) {
626

    
627
                    DnaSample dna = HibernateProxyHelper.deproxy(childDerivate, DnaSample.class);
628
                    if (!dna.getSequences().isEmpty()) {
629
                        derivateDTO.setHasDna(true);
630
                    }
631
                    for (Sequence sequence : dna.getSequences()) {
632
                        URI boldUri = null;
633
                        try {
634
                            boldUri = sequence.getBoldUri();
635
                        } catch (URISyntaxException e1) {
636
                            logger.error("Could not create BOLD URI", e1);
637
                        }
638
                        final DefinedTerm dnaMarker = sequence.getDnaMarker();
639
                        Link providerLink = null;
640
                        if(boldUri!=null && dnaMarker!=null){
641
                        	providerLink = new DerivateDataDTO.Link(boldUri, dnaMarker.getLabel());
642
                        }
643
                        MolecularData molecularData = derivateDataDTO.addProviderLink(providerLink);
644

    
645
                        //contig file
646
                        ContigFile contigFile = null;
647
                        if (sequence.getContigFile() != null) {
648
                            MediaRepresentationPart contigMediaRepresentationPart = MediaUtils.getFirstMediaRepresentationPart(sequence.getContigFile());
649
                            if (contigMediaRepresentationPart != null) {
650
                                contigFile = molecularData.addContigFile(new Link(contigMediaRepresentationPart.getUri(), "contig"));
651
                            }
652
                        }
653
                        else{
654
                        	contigFile = molecularData.addContigFile(null);
655
                        }
656
                        // primer files
657
                        if (sequence.getSingleReads() != null) {
658
                            int readCount = 1;
659
                            for (SingleRead singleRead : sequence.getSingleReads()) {
660
                                MediaRepresentationPart pherogramMediaRepresentationPart = MediaUtils.getFirstMediaRepresentationPart(singleRead.getPherogram());
661
                                if (pherogramMediaRepresentationPart != null) {
662
                                    contigFile.addPrimerLink(pherogramMediaRepresentationPart.getUri(), "read"+readCount++);
663
                                }
664
                            }
665
                        }
666
                    }
667
                }
668
            }
669
            // assemble media data
670
            else if (childDerivate.isInstanceOf(MediaSpecimen.class)) {
671
                MediaSpecimen media = HibernateProxyHelper.deproxy(childDerivate, MediaSpecimen.class);
672

    
673
                URI mediaUri = getMediaUri(media);
674
                if (media.getKindOfUnit() != null) {
675
                    // specimen scan
676
                    if (media.getKindOfUnit().getUuid().equals(DefinedTerm.uuidSpecimenScan)) {
677
                        derivateDataDTO.addSpecimenScanUuid(media.getMediaSpecimen().getUuid());
678
                        derivateDTO.setHasSpecimenScan(true);
679
                        String imageLinkText = "scan";
680
                        if (derivateDTO instanceof PreservedSpecimenDTO && ((PreservedSpecimenDTO) derivateDTO).getAccessionNumber() != null) {
681
                            imageLinkText = ((PreservedSpecimenDTO) derivateDTO).getAccessionNumber();
682
                        }
683
                        derivateDataDTO.addSpecimenScan(mediaUri, imageLinkText);
684
                    }
685
                    // detail image
686
                    else if (media.getKindOfUnit().getUuid().equals(DefinedTerm.uuidDetailImage)) {
687
                        derivateDataDTO.addDetailImageUuid(media.getMediaSpecimen().getUuid());
688
                        derivateDTO.setHasDetailImage(true);
689
                        String motif = "detail image";
690
                        if (media.getMediaSpecimen()!=null){
691
                        	if(CdmUtils.isNotBlank(media.getMediaSpecimen().getTitleCache())) {
692
                        		motif = media.getMediaSpecimen().getTitleCache();
693
                        	}
694
                        }
695
                        derivateDataDTO.addDetailImage(mediaUri, motif);
696
                    }
697
                }
698
            }
699
        }
700
        return derivateDataDTO;
701
    }
702

    
703
    private String removeTail(String string, final String tail) {
704
        if (string.endsWith(tail)) {
705
            string = string.substring(0, string.length() - tail.length());
706
        }
707
        return string;
708
    }
709

    
710
    private URI getMediaUri(MediaSpecimen mediaSpecimen) {
711
        URI mediaUri = null;
712
        Collection<MediaRepresentation> mediaRepresentations = mediaSpecimen.getMediaSpecimen().getRepresentations();
713
        if (mediaRepresentations != null && !mediaRepresentations.isEmpty()) {
714
            Collection<MediaRepresentationPart> mediaRepresentationParts = mediaRepresentations.iterator().next().getParts();
715
            if (mediaRepresentationParts != null && !mediaRepresentationParts.isEmpty()) {
716
                MediaRepresentationPart part = mediaRepresentationParts.iterator().next();
717
                if (part.getUri() != null) {
718
                    mediaUri = part.getUri();
719
                }
720
            }
721
        }
722
        return mediaUri;
723
    }
724

    
725
    private Collection<DerivedUnit> getDerivedUnitsFor(SpecimenOrObservationBase<?> specimen) {
726
        Collection<DerivedUnit> derivedUnits = new ArrayList<>();
727
        for (DerivationEvent derivationEvent : specimen.getDerivationEvents()) {
728
            for (DerivedUnit derivative : derivationEvent.getDerivatives()) {
729
                derivedUnits.add(derivative);
730
                derivedUnits.addAll(getDerivedUnitsFor(derivative));
731
            }
732
        }
733
        return derivedUnits;
734
    }
735

    
736

    
737
    @SuppressWarnings("unchecked")
738
    @Override
739
    public <T extends SpecimenOrObservationBase> Pager<T> pageByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,
740
            Taxon associatedTaxon, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
741

    
742
        Set<Taxon> taxa = new HashSet<>();
743
        Set<Integer> occurrenceIds = new HashSet<>();
744
        List<T> occurrences = new ArrayList<>();
745

    
746
        // Integer limit = PagerUtils.limitFor(pageSize);
747
        // Integer start = PagerUtils.startFor(pageSize, pageNumber);
748

    
749
        if (!getSession().contains(associatedTaxon)) {
750
            associatedTaxon = (Taxon) taxonService.load(associatedTaxon.getUuid());
751
        }
752

    
753
        if (includeRelationships != null) {
754
            taxa = taxonService.listRelatedTaxa(associatedTaxon, includeRelationships, maxDepth, null, null, propertyPaths);
755
        }
756

    
757
        taxa.add(associatedTaxon);
758

    
759
        for (Taxon taxon : taxa) {
760
            List<T> perTaxonOccurrences = dao.listByAssociatedTaxon(type, taxon, null, null, orderHints, propertyPaths);
761
            for (SpecimenOrObservationBase o : perTaxonOccurrences) {
762
                occurrenceIds.add(o.getId());
763
            }
764
        }
765
        occurrences = (List<T>) dao.loadList(occurrenceIds, propertyPaths);
766

    
767
        return new DefaultPagerImpl<T>(pageNumber, occurrenceIds.size(), pageSize, occurrences);
768

    
769
    }
770

    
771
    @Override
772
    public <T extends SpecimenOrObservationBase> Pager<T> pageByAssociatedTaxon(Class<T> type, Set<TaxonRelationshipEdge> includeRelationships,
773
            String taxonUUID, Integer maxDepth, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths) {
774

    
775
        UUID uuid = UUID.fromString(taxonUUID);
776
        Taxon tax = (Taxon) taxonService.load(uuid);
777
        // TODO REMOVE NULL STATEMENT
778
        type = null;
779
        return pageByAssociatedTaxon(type, includeRelationships, tax, maxDepth, pageSize, pageNumber, orderHints, propertyPaths);
780

    
781
    }
782

    
783
    @Override
784
    public Pager<SearchResult<SpecimenOrObservationBase>> findByFullText(
785
            Class<? extends SpecimenOrObservationBase> clazz, String queryString, Rectangle boundingBox, List<Language> languages,
786
            boolean highlightFragments, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints,
787
            List<String> propertyPaths) throws IOException, LuceneParseException {
788

    
789
        LuceneSearch luceneSearch = prepareByFullTextSearch(clazz, queryString, boundingBox, languages, highlightFragments);
790

    
791
        // --- execute search
792
        TopGroups<BytesRef> topDocsResultSet;
793
        try {
794
            topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber);
795
        } catch (ParseException e) {
796
            LuceneParseException parseException = new LuceneParseException(e.getMessage());
797
            parseException.setStackTrace(e.getStackTrace());
798
            throw parseException;
799
        }
800

    
801
        Map<CdmBaseType, String> idFieldMap = new HashMap<>();
802
        idFieldMap.put(CdmBaseType.SPECIMEN_OR_OBSERVATIONBASE, "id");
803

    
804
        // --- initialize taxa, highlight matches ....
805
        ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery());
806
        @SuppressWarnings("rawtypes")
807
        List<SearchResult<SpecimenOrObservationBase>> searchResults = searchResultBuilder.createResultSet(
808
                topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths);
809

    
810
        int totalHits = topDocsResultSet != null ? topDocsResultSet.totalGroupCount : 0;
811

    
812
        return new DefaultPagerImpl<SearchResult<SpecimenOrObservationBase>>(pageNumber, totalHits, pageSize,
813
                searchResults);
814

    
815
    }
816

    
817
    private LuceneSearch prepareByFullTextSearch(Class<? extends SpecimenOrObservationBase> clazz, String queryString, Rectangle bbox,
818
            List<Language> languages, boolean highlightFragments) {
819

    
820
        Builder finalQueryBuilder = new Builder();
821
        Builder textQueryBuilder = new Builder();
822

    
823
        LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, FieldUnit.class);
824
        QueryFactory queryFactory = luceneIndexToolProvider.newQueryFactoryFor(FieldUnit.class);
825

    
826
        // --- criteria
827
        luceneSearch.setCdmTypRestriction(clazz);
828
        if (queryString != null) {
829
            textQueryBuilder.add(queryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD);
830
            finalQueryBuilder.add(textQueryBuilder.build(), Occur.MUST);
831
        }
832

    
833
        // --- spacial query
834
        if (bbox != null) {
835
            finalQueryBuilder.add(QueryFactory.buildSpatialQueryByRange(bbox, "gatheringEvent.exactLocation.point"), Occur.MUST);
836
        }
837

    
838
        luceneSearch.setQuery(finalQueryBuilder.build());
839

    
840
        // --- sorting
841
        SortField[] sortFields = new SortField[] { SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.Type.STRING, false) };
842
        luceneSearch.setSortFields(sortFields);
843

    
844
        if (highlightFragments) {
845
            luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray());
846
        }
847
        return luceneSearch;
848
    }
849

    
850

    
851
    @Override
852
    public Collection<FieldUnit> getFieldUnits(UUID derivedUnitUuid) {
853
        //It will search recursively over all {@link DerivationEvent}s and get the "originals" ({@link SpecimenOrObservationBase})
854
        //from which this DerivedUnit was derived until all FieldUnits are found.
855

    
856
        // FIXME: use HQL queries to increase performance
857
        SpecimenOrObservationBase<?> specimen = load(derivedUnitUuid);
858
//        specimen = HibernateProxyHelper.deproxy(specimen, SpecimenOrObservationBase.class);
859
        Collection<FieldUnit> fieldUnits = new ArrayList<>();
860

    
861
        if (specimen.isInstanceOf(FieldUnit.class)) {
862
            fieldUnits.add(HibernateProxyHelper.deproxy(specimen, FieldUnit.class));
863
        }
864
        else if(specimen.isInstanceOf(DerivedUnit.class)){
865
            fieldUnits.addAll(getFieldUnits(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class)));
866
        }
867
        return fieldUnits;
868
    }
869

    
870
    private Collection<FieldUnit> getFieldUnits(DerivedUnit derivedUnit) {
871
        Collection<FieldUnit> fieldUnits = new HashSet<>();
872
        Set<SpecimenOrObservationBase> originals = derivedUnit.getOriginals();
873
        if (originals != null && !originals.isEmpty()) {
874
            for (SpecimenOrObservationBase<?> original : originals) {
875
                if (original.isInstanceOf(FieldUnit.class)) {
876
                    fieldUnits.add(HibernateProxyHelper.deproxy(original, FieldUnit.class));
877
                }
878
                else if(original.isInstanceOf(DerivedUnit.class)){
879
                    fieldUnits.addAll(getFieldUnits(HibernateProxyHelper.deproxy(original, DerivedUnit.class)));
880
                }
881
            }
882
        }
883
        return fieldUnits;
884
    }
885

    
886
    @Override
887
    @Transactional(readOnly = false)
888
    public UpdateResult moveSequence(DnaSample from, DnaSample to, Sequence sequence) {
889
        return moveSequence(from.getUuid(), to.getUuid(), sequence.getUuid());
890
    }
891

    
892
    @Override
893
    @Transactional(readOnly = false)
894
    public UpdateResult moveSequence(UUID fromUuid, UUID toUuid, UUID sequenceUuid) {
895
        // reload specimens to avoid session conflicts
896
        DnaSample from = (DnaSample) load(fromUuid);
897
        DnaSample to = (DnaSample) load(toUuid);
898
        Sequence sequence = sequenceService.load(sequenceUuid);
899

    
900
        if (from == null || to == null || sequence == null) {
901
            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" +
902
                    "Operation was move "+sequence+ " from "+from+" to "+to);
903
        }
904
        UpdateResult result = new UpdateResult();
905
        from.removeSequence(sequence);
906
        saveOrUpdate(from);
907
        to.addSequence(sequence);
908
        saveOrUpdate(to);
909
        result.setStatus(Status.OK);
910
        result.addUpdatedObject(from);
911
        result.addUpdatedObject(to);
912
        return result;
913
    }
914

    
915
    @Override
916
    @Transactional(readOnly = false)
917
    public boolean moveDerivate(SpecimenOrObservationBase<?> from, SpecimenOrObservationBase<?> to, DerivedUnit derivate) {
918
        return moveDerivate(from!=null?from.getUuid():null, to.getUuid(), derivate.getUuid()).isOk();
919
    }
920

    
921
    @Override
922
    @Transactional(readOnly = false)
923
    public UpdateResult moveDerivate(UUID specimenFromUuid, UUID specimenToUuid, UUID derivateUuid) {
924
        // reload specimens to avoid session conflicts
925
        SpecimenOrObservationBase<?> from = null;
926
        if(specimenFromUuid!=null){
927
            from = load(specimenFromUuid);
928
        }
929
        SpecimenOrObservationBase<?> to = load(specimenToUuid);
930
        DerivedUnit derivate = (DerivedUnit) load(derivateUuid);
931

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

    
972
            if(from!=null){
973
                saveOrUpdate(from);
974
            }
975
            saveOrUpdate(to);
976
            result.setStatus(Status.OK);
977
            result.addUpdatedObject(from);
978
            result.addUpdatedObject(to);
979
        } else {
980
            result.setStatus(Status.ERROR);
981
        }
982
        return result;
983
    }
984

    
985
    @Override
986
    public Collection<ICdmBase> getNonCascadedAssociatedElements(SpecimenOrObservationBase<?> specimen) {
987
        // potential fields that are not persisted cascadingly
988
        /*
989
         * SOOB
990
        -DescriptionBase
991
        -determinations
992
        --modifier TERM
993
        -kindOfUnit TERM
994
        -lifeStage TERM
995
        -sex TERM
996

    
997
        FieldUnit
998
        -GatheringEvent
999
        --Country TERM
1000
        --CollectingAreas TERM
1001

    
1002
        DerivedUnit
1003
        -collection
1004
        --institute
1005
        ---types TERM
1006
        -preservationMethod
1007
        --medium TERM
1008
        -storedUnder CDM TaxonName
1009
         */
1010

    
1011
        Collection<ICdmBase> nonCascadedCdmEntities = new HashSet<>();
1012

    
1013
        //Choose the correct entry point to traverse the graph (FieldUnit or DerivedUnit)
1014

    
1015
        // FieldUnit
1016
        if (specimen.isInstanceOf(FieldUnit.class)) {
1017
            nonCascadedCdmEntities.addAll(getFieldUnitNonCascadedAssociatedElements(HibernateProxyHelper.deproxy(specimen, FieldUnit.class)));
1018
        }
1019
        // DerivedUnit
1020
        else if (specimen.isInstanceOf(DerivedUnit.class)) {
1021
            DerivedUnit derivedUnit = HibernateProxyHelper.deproxy(specimen, DerivedUnit.class);
1022
            if (derivedUnit.getDerivedFrom() != null) {
1023
                Collection<FieldUnit> fieldUnits = getFieldUnits(derivedUnit);
1024
                for (FieldUnit fieldUnit : fieldUnits) {
1025
                    nonCascadedCdmEntities.addAll(getFieldUnitNonCascadedAssociatedElements(fieldUnit));
1026
                }
1027
            }
1028
        }
1029
        return nonCascadedCdmEntities;
1030
    }
1031

    
1032
    private Collection<ICdmBase> getFieldUnitNonCascadedAssociatedElements(FieldUnit fieldUnit) {
1033
        // get non cascaded element on SpecimenOrObservationBase level
1034
        Collection<ICdmBase> nonCascadedCdmEntities = getSpecimenOrObservationNonCascadedAssociatedElements(fieldUnit);
1035

    
1036
        // get FieldUnit specific elements
1037
        GatheringEvent gatheringEvent = fieldUnit.getGatheringEvent();
1038
        if (gatheringEvent != null) {
1039
            // country
1040
            if (gatheringEvent.getCountry() != null) {
1041
                nonCascadedCdmEntities.add(gatheringEvent.getCountry());
1042
            }
1043
            // collecting areas
1044
            for (NamedArea namedArea : gatheringEvent.getCollectingAreas()) {
1045
                nonCascadedCdmEntities.add(namedArea);
1046
            }
1047
        }
1048
        for (DerivationEvent derivationEvent : fieldUnit.getDerivationEvents()) {
1049
            for (DerivedUnit derivedUnit : derivationEvent.getDerivatives()) {
1050
                nonCascadedCdmEntities.addAll(getDerivedUnitNonCascadedAssociatedElements(derivedUnit));
1051
            }
1052
        }
1053
        return nonCascadedCdmEntities;
1054
    }
1055

    
1056
    private Collection<ICdmBase> getDerivedUnitNonCascadedAssociatedElements(DerivedUnit derivedUnit) {
1057
        // get non cascaded element on SpecimenOrObservationBase level
1058
        Collection<ICdmBase> nonCascadedCdmEntities = getSpecimenOrObservationNonCascadedAssociatedElements(derivedUnit);
1059

    
1060
        // get DerivedUnit specific elements
1061
        if (derivedUnit.getCollection() != null && derivedUnit.getCollection().getInstitute() != null) {
1062
            for (DefinedTerm type : derivedUnit.getCollection().getInstitute().getTypes()) {
1063
                nonCascadedCdmEntities.add(type);
1064
            }
1065
        }
1066
        if (derivedUnit.getPreservation() != null && derivedUnit.getPreservation().getMedium() != null) {
1067
            nonCascadedCdmEntities.add(derivedUnit.getPreservation().getMedium());
1068
        }
1069
        if (derivedUnit.getStoredUnder() != null) {
1070
            nonCascadedCdmEntities.add(derivedUnit.getStoredUnder());
1071
        }
1072
        return nonCascadedCdmEntities;
1073
    }
1074

    
1075
    private Collection<ICdmBase> getSpecimenOrObservationNonCascadedAssociatedElements(
1076
            SpecimenOrObservationBase<?> specimen) {
1077
        Collection<ICdmBase> nonCascadedCdmEntities = new HashSet<>();
1078
        // scan SpecimenOrObservationBase
1079
        for (DeterminationEvent determinationEvent : specimen.getDeterminations()) {
1080
            // modifier
1081
            if (determinationEvent.getModifier() != null) {
1082
                nonCascadedCdmEntities.add(determinationEvent.getModifier());
1083
            }
1084
        }
1085
        // kindOfUnit
1086
        if (specimen.getKindOfUnit() != null) {
1087
            nonCascadedCdmEntities.add(specimen.getKindOfUnit());
1088
        }
1089
        // lifeStage
1090
        if (specimen.getLifeStage() != null) {
1091
            nonCascadedCdmEntities.add(specimen.getLifeStage());
1092
        }
1093
        // sex
1094
        if (specimen.getSex() != null) {
1095
            nonCascadedCdmEntities.add(specimen.getSex());
1096
        }
1097
        return nonCascadedCdmEntities;
1098
    }
1099

    
1100
    @Override
1101
    public DeleteResult isDeletable(UUID specimenUuid, DeleteConfiguratorBase config) {
1102
        DeleteResult deleteResult = new DeleteResult();
1103
        SpecimenOrObservationBase specimen = this.load(specimenUuid);
1104
        SpecimenDeleteConfigurator specimenDeleteConfigurator = (SpecimenDeleteConfigurator) config;
1105

    
1106
        // check elements found by super method
1107
        Set<CdmBase> relatedObjects = super.isDeletable(specimenUuid, config).getRelatedObjects();
1108
        for (CdmBase cdmBase : relatedObjects) {
1109
            // check for type designation
1110
            if (cdmBase.isInstanceOf(SpecimenTypeDesignation.class) && !specimenDeleteConfigurator.isDeleteFromTypeDesignation()) {
1111
                deleteResult.setAbort();
1112
                deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation is a type specimen."));
1113
                deleteResult.addRelatedObject(cdmBase);
1114
                break;
1115
            }
1116
            // check for IndividualsAssociations
1117
            else if (cdmBase.isInstanceOf(IndividualsAssociation.class) && !specimenDeleteConfigurator.isDeleteFromIndividualsAssociation()) {
1118
                deleteResult.setAbort();
1119
                deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation is still associated via IndividualsAssociations"));
1120
                deleteResult.addRelatedObject(cdmBase);
1121
                break;
1122
            }
1123
            // check for taxon description
1124
            else if(cdmBase.isInstanceOf(TaxonDescription.class)
1125
                    && HibernateProxyHelper.deproxy(cdmBase, TaxonDescription.class).getDescribedSpecimenOrObservation().equals(specimen)
1126
                    && !specimenDeleteConfigurator.isDeleteFromDescription()){
1127
                deleteResult.setAbort();
1128
                deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation is still used as \"Described Specimen\" in a taxon description."));
1129
                deleteResult.addRelatedObject(cdmBase);
1130
                break;
1131
            }
1132
            // check for children and parents (derivation events)
1133
            else if (cdmBase.isInstanceOf(DerivationEvent.class)) {
1134
                DerivationEvent derivationEvent = HibernateProxyHelper.deproxy(cdmBase, DerivationEvent.class);
1135
                // check if derivation event is empty
1136
                if (!derivationEvent.getDerivatives().isEmpty() && derivationEvent.getOriginals().contains(specimen)) {
1137
                    // if derivationEvent is the childEvent and contains derivations
1138
//                    if (derivationEvent.getDerivatives().contains(specimen)) {
1139
//                        //if it is the parent event the specimen is still deletable
1140
//                        continue;
1141
//                    }
1142
                    if(!specimenDeleteConfigurator.isDeleteChildren()){
1143
                        //if children should not be deleted then it is undeletable
1144
                        deleteResult.setAbort();
1145
                        deleteResult.addException(new ReferencedObjectUndeletableException("Specimen or obeservation still has child derivatives."));
1146
                        deleteResult.addRelatedObject(cdmBase);
1147
                        break;
1148
                    }
1149
                    else{
1150
                        // check all children if they can be deleted
1151
                        Set<DerivedUnit> derivatives = derivationEvent.getDerivatives();
1152
                        DeleteResult childResult = new DeleteResult();
1153
                        for (DerivedUnit derivedUnit : derivatives) {
1154
                            childResult.includeResult(isDeletable(derivedUnit.getUuid(), specimenDeleteConfigurator));
1155
                        }
1156
                        if (!childResult.isOk()) {
1157
                            deleteResult.setAbort();
1158
                            deleteResult.includeResult(childResult);
1159
                            deleteResult.addRelatedObject(cdmBase);
1160
                            break;
1161
                        }
1162
                    }
1163
                }
1164
            }
1165
            // check for amplification
1166
            else if (cdmBase.isInstanceOf(AmplificationResult.class)
1167
                    && !specimenDeleteConfigurator.isDeleteMolecularData()
1168
                    && !specimenDeleteConfigurator.isDeleteChildren()) {
1169
                deleteResult.setAbort();
1170
                deleteResult.addException(new ReferencedObjectUndeletableException("DnaSample is used in amplification results."));
1171
                deleteResult.addRelatedObject(cdmBase);
1172
                break;
1173
            }
1174
            // check for sequence
1175
            else if (cdmBase.isInstanceOf(Sequence.class)
1176
                    && !specimenDeleteConfigurator.isDeleteMolecularData()
1177
                    && !specimenDeleteConfigurator.isDeleteChildren()) {
1178
                deleteResult.setAbort();
1179
                deleteResult.addException(new ReferencedObjectUndeletableException("DnaSample is used in sequences."));
1180
                deleteResult.addRelatedObject(cdmBase);
1181
                break;
1182
            }
1183
        }
1184
        if (deleteResult.isOk()) {
1185
            //add all related object if deletion is OK so they can be handled by the delete() method
1186
            deleteResult.addRelatedObjects(relatedObjects);
1187
        }
1188
        return deleteResult;
1189
    }
1190

    
1191
    /**
1192
     * {@inheritDoc}
1193
     */
1194
    @Transactional(readOnly = false)
1195
    @Override
1196
    public DeleteResult delete(UUID specimenUuid, SpecimenDeleteConfigurator config) {
1197
        return delete(load(specimenUuid), config);
1198
    }
1199

    
1200

    
1201
    @Transactional(readOnly = false)
1202
    @Override
1203
    public DeleteResult delete(SpecimenOrObservationBase<?> specimen, SpecimenDeleteConfigurator config) {
1204
        specimen = HibernateProxyHelper.deproxy(specimen, SpecimenOrObservationBase.class);
1205

    
1206
        if (config.isDeleteChildren()) {
1207
            Set<DerivationEvent> derivationEvents = specimen.getDerivationEvents();
1208
            //clone to avoid concurrent modification
1209
            //can happen if the child is deleted and deleted its own derivedFrom event
1210
            Set<DerivationEvent> derivationEventsClone = new HashSet<>(derivationEvents);
1211
            for (DerivationEvent derivationEvent : derivationEventsClone) {
1212
                Set<DerivedUnit> derivatives = derivationEvent.getDerivatives();
1213
                Iterator<DerivedUnit> it = derivatives.iterator();
1214
                Set<DerivedUnit> derivativesToDelete = new HashSet<>();
1215
                while (it.hasNext()) {
1216
                    DerivedUnit unit = it.next();
1217
                    derivativesToDelete.add(unit);
1218
                }
1219
                for (DerivedUnit unit:derivativesToDelete){
1220
                    delete(unit, config);
1221
                }
1222
            }
1223
        }
1224

    
1225

    
1226

    
1227
        DeleteResult deleteResult = isDeletable(specimen.getUuid(), config);
1228
        if (!deleteResult.isOk()) {
1229
            return deleteResult;
1230
        }
1231

    
1232
        // check related objects
1233
        Set<CdmBase> relatedObjects = deleteResult.getRelatedObjects();
1234

    
1235
        for (CdmBase relatedObject : relatedObjects) {
1236
            // check for TypeDesignations
1237
            if (relatedObject.isInstanceOf(SpecimenTypeDesignation.class)) {
1238
                SpecimenTypeDesignation designation = HibernateProxyHelper.deproxy(relatedObject, SpecimenTypeDesignation.class);
1239
                designation.setTypeSpecimen(null);
1240
                List<TaxonName> typifiedNames = new ArrayList<>();
1241
                typifiedNames.addAll(designation.getTypifiedNames());
1242
                for (TaxonName taxonName : typifiedNames) {
1243
                    taxonName.removeTypeDesignation(designation);
1244
                }
1245
            }
1246
            // delete IndividualsAssociation
1247
            if (relatedObject.isInstanceOf(IndividualsAssociation.class)) {
1248
                IndividualsAssociation association = HibernateProxyHelper.deproxy(relatedObject, IndividualsAssociation.class);
1249
                association.setAssociatedSpecimenOrObservation(null);
1250
                association.getInDescription().removeElement(association);
1251
            }
1252
            // check for "described specimen" (deprecated)
1253
            if (relatedObject.isInstanceOf(TaxonDescription.class)) {
1254
                TaxonDescription description = HibernateProxyHelper.deproxy(relatedObject, TaxonDescription.class);
1255
                description.setDescribedSpecimenOrObservation(null);
1256
            }
1257
            // check for specimen description
1258
            if (relatedObject.isInstanceOf(SpecimenDescription.class)) {
1259
                SpecimenDescription specimenDescription = HibernateProxyHelper.deproxy(relatedObject, SpecimenDescription.class);
1260
                specimenDescription.setDescribedSpecimenOrObservation(null);
1261
                // check if description is a description of the given specimen
1262
                if (specimen.getDescriptions().contains(specimenDescription)) {
1263
                    specimen.removeDescription(specimenDescription);
1264
                }
1265
                DeleteResult descriptionDelete = descriptionService.isDeletable(specimenDescription.getUuid(), null);
1266
                if (descriptionDelete.isOk()){
1267
                    descriptionService.delete(specimenDescription);
1268
                }
1269
            }
1270
            // check for amplification
1271
            if (relatedObject.isInstanceOf(AmplificationResult.class)) {
1272
                AmplificationResult amplificationResult = HibernateProxyHelper.deproxy(relatedObject, AmplificationResult.class);
1273
                amplificationResult.getDnaSample().removeAmplificationResult(amplificationResult);
1274
            }
1275
            // check for sequence
1276
            if (relatedObject.isInstanceOf(Sequence.class)) {
1277
                Sequence sequence = HibernateProxyHelper.deproxy(relatedObject, Sequence.class);
1278
                sequence.getDnaSample().removeSequence(sequence);
1279
            }
1280
            // check for children and parents (derivation events)
1281
            if (relatedObject.isInstanceOf(DerivationEvent.class)) {
1282
                DerivationEvent derivationEvent = HibernateProxyHelper.deproxy(relatedObject, DerivationEvent.class);
1283
                // parent derivation event (derivedFrom)
1284
                if (derivationEvent.getDerivatives().contains(specimen) && specimen.isInstanceOf(DerivedUnit.class)) {
1285
                    derivationEvent.removeDerivative(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class));
1286
                    if (derivationEvent.getDerivatives().isEmpty()) {
1287
                        Set<SpecimenOrObservationBase> originals = derivationEvent.getOriginals();
1288
                        for (SpecimenOrObservationBase specimenOrObservationBase : originals) {
1289
                            specimenOrObservationBase.removeDerivationEvent(derivationEvent);
1290
                            deleteResult.addUpdatedObject(specimenOrObservationBase);
1291
                        }
1292
                    }
1293
                }
1294
                else{
1295
                    //child derivation events should not occur since we delete the hierarchy from bottom to top
1296
                }
1297
            }
1298
        }
1299
        if (specimen instanceof FieldUnit){
1300
            FieldUnit fieldUnit = HibernateProxyHelper.deproxy(specimen, FieldUnit.class);
1301
            GatheringEvent event = fieldUnit.getGatheringEvent();
1302
            fieldUnit.setGatheringEvent(null);
1303

    
1304

    
1305
        }
1306
        deleteResult.includeResult(delete(specimen));
1307

    
1308
        return deleteResult;
1309
    }
1310

    
1311
    @Override
1312
    public Collection<IndividualsAssociation> listIndividualsAssociations(SpecimenOrObservationBase<?> specimen, Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1313
        return dao.listIndividualsAssociations(specimen, null, null, null, null);
1314
    }
1315

    
1316
    @Override
1317
    public Collection<TaxonBase<?>> listAssociatedTaxa(SpecimenOrObservationBase<?> specimen, Integer limit, Integer start,
1318
            List<OrderHint> orderHints, List<String> propertyPaths) {
1319
        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
1320

    
1321
        //individuals associations
1322
        associatedTaxa.addAll(listIndividualsAssociationTaxa(specimen, limit, start, orderHints, propertyPaths));
1323
        //type designation
1324
        if(specimen.isInstanceOf(DerivedUnit.class)){
1325
            associatedTaxa.addAll(listTypeDesignationTaxa(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class), limit, start, orderHints, propertyPaths));
1326
        }
1327
        //determinations
1328
        associatedTaxa.addAll(listDeterminedTaxa(specimen, limit, start, orderHints, propertyPaths));
1329

    
1330
        return associatedTaxa;
1331
    }
1332

    
1333

    
1334
    @Override
1335
    public Collection<TaxonBase<?>> listDeterminedTaxa(SpecimenOrObservationBase<?> specimen, Integer limit, Integer start,
1336
            List<OrderHint> orderHints, List<String> propertyPaths) {
1337
        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
1338
        for (DeterminationEvent determinationEvent : listDeterminationEvents(specimen, limit, start, orderHints, propertyPaths)) {
1339
            if(determinationEvent.getIdentifiedUnit().equals(specimen)){
1340
                if(determinationEvent.getTaxon()!=null){
1341
                    associatedTaxa.add(determinationEvent.getTaxon());
1342
                }
1343
                if(determinationEvent.getTaxonName()!=null){
1344
                    associatedTaxa.addAll((Collection)determinationEvent.getTaxonName().getTaxonBases());
1345
                }
1346
            }
1347
        }
1348
        return associatedTaxa;
1349
    }
1350

    
1351
    @Override
1352
    public Collection<TaxonBase<?>> listTypeDesignationTaxa(DerivedUnit specimen, Integer limit, Integer start,
1353
            List<OrderHint> orderHints, List<String> propertyPaths) {
1354
        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
1355
        for (SpecimenTypeDesignation typeDesignation : listTypeDesignations(specimen, limit, start, orderHints, propertyPaths)) {
1356
            if(typeDesignation.getTypeSpecimen().equals(specimen)){
1357
                Set<TaxonName> typifiedNames = typeDesignation.getTypifiedNames();
1358
                for (TaxonName taxonName : typifiedNames) {
1359
                    associatedTaxa.addAll(taxonName.getTaxa());
1360
                }
1361
            }
1362
        }
1363
        return associatedTaxa;
1364
    }
1365

    
1366
    @Override
1367
    public Collection<TaxonBase<?>> listIndividualsAssociationTaxa(SpecimenOrObservationBase<?> specimen, Integer limit, Integer start,
1368
            List<OrderHint> orderHints, List<String> propertyPaths) {
1369
        Collection<TaxonBase<?>> associatedTaxa = new HashSet<>();
1370
        for (IndividualsAssociation individualsAssociation : listIndividualsAssociations(specimen, limit, start, orderHints, propertyPaths)) {
1371
            if(individualsAssociation.getInDescription().isInstanceOf(TaxonDescription.class)){
1372
                TaxonDescription taxonDescription = HibernateProxyHelper.deproxy(individualsAssociation.getInDescription(), TaxonDescription.class);
1373
                if(taxonDescription.getTaxon()!=null){
1374
                    associatedTaxa.add(taxonDescription.getTaxon());
1375
                }
1376
            }
1377
        }
1378
        return associatedTaxa;
1379
    }
1380

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

    
1387
    @Override
1388
    public Map<DerivedUnit, Collection<SpecimenTypeDesignation>> listTypeDesignations(
1389
            Collection<DerivedUnit> specimens, Integer limit, Integer start,
1390
            List<OrderHint> orderHints, List<String> propertyPaths) {
1391
        Map<DerivedUnit, Collection<SpecimenTypeDesignation>> typeDesignationMap = new HashMap<>();
1392
        for (DerivedUnit specimen : specimens) {
1393
            Collection<SpecimenTypeDesignation> typeDesignations = listTypeDesignations(specimen, limit, start, orderHints, propertyPaths);
1394
            typeDesignationMap.put(specimen, typeDesignations);
1395
        }
1396
        return typeDesignationMap;
1397
    }
1398

    
1399
    @Override
1400
    public Collection<SpecimenTypeDesignation> listTypeDesignations(DerivedUnit specimen,
1401
            Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
1402
        return dao.listTypeDesignations(specimen, limit, start, orderHints, propertyPaths);
1403
    }
1404

    
1405
    @Override
1406
    public Collection<DescriptionBase<?>> listDescriptionsWithDescriptionSpecimen(
1407
            SpecimenOrObservationBase<?> specimen, Integer limit, Integer start, List<OrderHint> orderHints,
1408
            List<String> propertyPaths) {
1409
        return dao.listDescriptionsWithDescriptionSpecimen(specimen, limit, start, orderHints, propertyPaths);
1410
    }
1411

    
1412
    @Override
1413
    @Deprecated //this is not a service layer task so it may be removed in future versions
1414
    public Collection<DescriptionElementBase> getCharacterDataForSpecimen(SpecimenOrObservationBase<?> specimen) {
1415
        if (specimen != null) {
1416
            return specimen.characterData();
1417
        }else{
1418
            return new ArrayList<>();
1419
        }
1420
    }
1421

    
1422
    @Override
1423
    public Collection<DescriptionElementBase> getCharacterDataForSpecimen(UUID specimenUuid) {
1424
        SpecimenOrObservationBase<?> specimen = load(specimenUuid);
1425
        if (specimen != null) {
1426
            return getCharacterDataForSpecimen(specimen);
1427
        }
1428
        else{
1429
            throw new DataRetrievalFailureException("Specimen with the given uuid not found in the data base");
1430
        }
1431
    }
1432

    
1433

    
1434
    @Override
1435
    public Integer countByTitle(IIdentifiableEntityServiceConfigurator<SpecimenOrObservationBase> config){
1436
        if (config instanceof FindOccurrencesConfigurator) {
1437
            FindOccurrencesConfigurator occurrenceConfig = (FindOccurrencesConfigurator) config;
1438
            Taxon taxon = null;
1439
            if(occurrenceConfig.getAssociatedTaxonUuid()!=null){
1440
                TaxonBase taxonBase = taxonService.load(occurrenceConfig.getAssociatedTaxonUuid());
1441
                if(taxonBase.isInstanceOf(Taxon.class)){
1442
                    taxon = HibernateProxyHelper.deproxy(taxonBase, Taxon.class);
1443
                }
1444
            }
1445
            TaxonName taxonName = null;
1446
            if(occurrenceConfig.getAssociatedTaxonNameUuid()!=null){
1447
                taxonName = nameService.load(occurrenceConfig.getAssociatedTaxonNameUuid());
1448
            }
1449
            return dao.countOccurrences(occurrenceConfig.getClazz(),
1450
                    occurrenceConfig.getTitleSearchString(), occurrenceConfig.getSignificantIdentifier(),
1451
                    occurrenceConfig.getSpecimenType(), taxon, taxonName, occurrenceConfig.getMatchMode(), null, null,
1452
                    occurrenceConfig.getOrderHints(), occurrenceConfig.getPropertyPaths());
1453
        }
1454
        else{
1455
            return dao.countByTitle(config.getTitleSearchString());
1456
        }
1457

    
1458
    }
1459

    
1460
    @Override
1461
    public Pager<SpecimenOrObservationBase> findByTitle(
1462
            IIdentifiableEntityServiceConfigurator<SpecimenOrObservationBase> config) {
1463
        if (config instanceof FindOccurrencesConfigurator) {
1464
            FindOccurrencesConfigurator occurrenceConfig = (FindOccurrencesConfigurator) config;
1465
            List<SpecimenOrObservationBase> occurrences = new ArrayList<>();
1466
            Taxon taxon = null;
1467
            if(occurrenceConfig.getAssociatedTaxonUuid()!=null){
1468
                TaxonBase taxonBase = taxonService.load(occurrenceConfig.getAssociatedTaxonUuid());
1469
                if(taxonBase.isInstanceOf(Taxon.class)){
1470
                    taxon = HibernateProxyHelper.deproxy(taxonBase, Taxon.class);
1471
                }
1472
            }
1473
            TaxonName taxonName = null;
1474
            if(occurrenceConfig.getAssociatedTaxonNameUuid()!=null){
1475
                taxonName = nameService.load(occurrenceConfig.getAssociatedTaxonNameUuid());
1476
            }
1477
            occurrences.addAll(dao.findOccurrences(occurrenceConfig.getClazz(),
1478
                    occurrenceConfig.getTitleSearchString(), occurrenceConfig.getSignificantIdentifier(),
1479
                    occurrenceConfig.getSpecimenType(), taxon, taxonName, occurrenceConfig.getMatchMode(), null, null,
1480
                    occurrenceConfig.getOrderHints(), occurrenceConfig.getPropertyPaths()));
1481
            //filter out (un-)assigned specimens
1482
            if(taxon==null && taxonName==null){
1483
                AssignmentStatus assignmentStatus = occurrenceConfig.getAssignmentStatus();
1484
                List<SpecimenOrObservationBase<?>> specimenWithAssociations = new ArrayList<>();
1485
                if(!assignmentStatus.equals(AssignmentStatus.ALL_SPECIMENS)){
1486
                    for (SpecimenOrObservationBase specimenOrObservationBase : occurrences) {
1487
                        Collection<TaxonBase<?>> associatedTaxa = listAssociatedTaxa(specimenOrObservationBase, null, null, null, null);
1488
                        if(!associatedTaxa.isEmpty()){
1489
                            specimenWithAssociations.add(specimenOrObservationBase);
1490
                        }
1491
                    }
1492
                }
1493
                if(assignmentStatus.equals(AssignmentStatus.UNASSIGNED_SPECIMENS)){
1494
                    occurrences.removeAll(specimenWithAssociations);
1495
                }
1496
                if(assignmentStatus.equals(AssignmentStatus.ASSIGNED_SPECIMENS)){
1497
                    occurrences = new ArrayList<>(specimenWithAssociations);
1498
                }
1499
            }
1500
            // indirectly associated specimens
1501
            if(occurrenceConfig.isRetrieveIndirectlyAssociatedSpecimens()){
1502
                List<SpecimenOrObservationBase> indirectlyAssociatedOccurrences = new ArrayList<>(occurrences);
1503
                for (SpecimenOrObservationBase specimen : occurrences) {
1504
                    List<SpecimenOrObservationBase<?>> allHierarchyDerivates = getAllHierarchyDerivatives(specimen);
1505
                    for (SpecimenOrObservationBase<?> specimenOrObservationBase : allHierarchyDerivates) {
1506
                        if(!occurrences.contains(specimenOrObservationBase)){
1507
                            indirectlyAssociatedOccurrences.add(specimenOrObservationBase);
1508
                        }
1509
                    }
1510
                }
1511
                occurrences = indirectlyAssociatedOccurrences;
1512
            }
1513

    
1514
            return new DefaultPagerImpl<SpecimenOrObservationBase>(config.getPageNumber(), occurrences.size(), config.getPageSize(), occurrences);
1515
        }
1516
        return super.findByTitle(config);
1517
    }
1518

    
1519
    @Override
1520
    public List<SpecimenOrObservationBase<?>> getAllHierarchyDerivatives(SpecimenOrObservationBase<?> specimen){
1521
        List<SpecimenOrObservationBase<?>> allHierarchyDerivatives = new ArrayList<>();
1522
        Collection<FieldUnit> fieldUnits = getFieldUnits(specimen.getUuid());
1523
        if(fieldUnits.isEmpty()){
1524
            allHierarchyDerivatives.add(specimen);
1525
            allHierarchyDerivatives.addAll(getAllChildDerivatives(specimen));
1526
        }
1527
        else{
1528
            for (FieldUnit fieldUnit : fieldUnits) {
1529
                allHierarchyDerivatives.add(fieldUnit);
1530
                allHierarchyDerivatives.addAll(getAllChildDerivatives(fieldUnit));
1531
            }
1532
        }
1533
        return allHierarchyDerivatives;
1534
    }
1535

    
1536
    @Override
1537
    public List<DerivedUnit> getAllChildDerivatives(UUID specimenUuid){
1538
        return getAllChildDerivatives(load(specimenUuid));
1539
    }
1540

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

    
1558
    @Override
1559
    public int countOccurrences(IIdentifiableEntityServiceConfigurator<SpecimenOrObservationBase> config){
1560
        return countByTitle(config);
1561
    }
1562

    
1563
}
(80-80/101)