Project

General

Profile

Download (45.3 KB) Statistics
| Branch: | Tag: | Revision:
1
package eu.etaxonomy.cdm.api.service;
2

    
3
import java.math.BigDecimal;
4
import java.util.ArrayList;
5
import java.util.Arrays;
6
import java.util.Collection;
7
import java.util.Collections;
8
import java.util.HashMap;
9
import java.util.HashSet;
10
import java.util.List;
11
import java.util.Map;
12
import java.util.Optional;
13
import java.util.Set;
14
import java.util.UUID;
15
import java.util.stream.Collectors;
16

    
17
import org.apache.log4j.Logger;
18
import org.springframework.beans.factory.annotation.Autowired;
19
import org.springframework.stereotype.Service;
20
import org.springframework.transaction.annotation.Transactional;
21

    
22
import eu.etaxonomy.cdm.api.service.UpdateResult.Status;
23
import eu.etaxonomy.cdm.api.service.config.DeleteDescriptiveDataSetConfigurator;
24
import eu.etaxonomy.cdm.api.service.config.IdentifiableServiceConfiguratorImpl;
25
import eu.etaxonomy.cdm.api.service.config.RemoveDescriptionsFromDescriptiveDataSetConfigurator;
26
import eu.etaxonomy.cdm.api.service.dto.CategoricalDataDto;
27
import eu.etaxonomy.cdm.api.service.dto.DescriptionBaseDto;
28
import eu.etaxonomy.cdm.api.service.dto.DescriptionElementDto;
29
import eu.etaxonomy.cdm.api.service.dto.QuantitativeDataDto;
30
import eu.etaxonomy.cdm.api.service.dto.RowWrapperDTO;
31
import eu.etaxonomy.cdm.api.service.dto.SpecimenOrObservationDTOFactory;
32
import eu.etaxonomy.cdm.api.service.dto.SpecimenRowWrapperDTO;
33
import eu.etaxonomy.cdm.api.service.dto.StateDataDto;
34
import eu.etaxonomy.cdm.api.service.dto.StatisticalMeasurementValueDto;
35
import eu.etaxonomy.cdm.api.service.dto.TaxonRowWrapperDTO;
36
import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
37
import eu.etaxonomy.cdm.filter.TaxonNodeFilter;
38
import eu.etaxonomy.cdm.format.description.DefaultCategoricalDescriptionBuilder;
39
import eu.etaxonomy.cdm.format.description.DefaultQuantitativeDescriptionBuilder;
40
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
41
import eu.etaxonomy.cdm.model.common.CdmBase;
42
import eu.etaxonomy.cdm.model.common.Language;
43
import eu.etaxonomy.cdm.model.description.CategoricalData;
44
import eu.etaxonomy.cdm.model.description.DescriptionBase;
45
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
46
import eu.etaxonomy.cdm.model.description.DescriptionType;
47
import eu.etaxonomy.cdm.model.description.DescriptiveDataSet;
48
import eu.etaxonomy.cdm.model.description.DescriptiveSystemRole;
49
import eu.etaxonomy.cdm.model.description.Feature;
50
import eu.etaxonomy.cdm.model.description.IndividualsAssociation;
51
import eu.etaxonomy.cdm.model.description.MeasurementUnit;
52
import eu.etaxonomy.cdm.model.description.PolytomousKey;
53
import eu.etaxonomy.cdm.model.description.QuantitativeData;
54
import eu.etaxonomy.cdm.model.description.SpecimenDescription;
55
import eu.etaxonomy.cdm.model.description.State;
56
import eu.etaxonomy.cdm.model.description.StatisticalMeasure;
57
import eu.etaxonomy.cdm.model.description.StatisticalMeasurementValue;
58
import eu.etaxonomy.cdm.model.description.TaxonDescription;
59
import eu.etaxonomy.cdm.model.description.TextData;
60
import eu.etaxonomy.cdm.model.location.NamedArea;
61
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
62
import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
63
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
64
import eu.etaxonomy.cdm.model.reference.CdmLinkSource;
65
import eu.etaxonomy.cdm.model.taxon.Classification;
66
import eu.etaxonomy.cdm.model.taxon.Taxon;
67
import eu.etaxonomy.cdm.model.taxon.TaxonBase;
68
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
69
import eu.etaxonomy.cdm.model.term.DefinedTermBase;
70
import eu.etaxonomy.cdm.persistence.dao.description.IDescriptiveDataSetDao;
71
import eu.etaxonomy.cdm.persistence.dao.term.IDefinedTermDao;
72
import eu.etaxonomy.cdm.persistence.dto.DescriptiveDataSetBaseDto;
73
import eu.etaxonomy.cdm.persistence.dto.MergeResult;
74
import eu.etaxonomy.cdm.persistence.dto.SpecimenNodeWrapper;
75
import eu.etaxonomy.cdm.persistence.dto.TaxonNodeDto;
76
import eu.etaxonomy.cdm.persistence.dto.TermDto;
77
import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
78
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
79
import eu.etaxonomy.cdm.strategy.generate.PolytomousKeyGenerator;
80
import eu.etaxonomy.cdm.strategy.generate.PolytomousKeyGeneratorConfigurator;
81

    
82
@Service
83
@Transactional(readOnly=true)
84
public class DescriptiveDataSetService
85
        extends IdentifiableServiceBase<DescriptiveDataSet, IDescriptiveDataSetDao>
86
        implements IDescriptiveDataSetService {
87

    
88
    private static Logger logger = Logger.getLogger(DescriptiveDataSetService.class);
89

    
90
    @Autowired
91
    private IOccurrenceService occurrenceService;
92

    
93
    @Autowired
94
    private ITaxonService taxonService;
95

    
96
    @Autowired
97
    private IPolytomousKeyService polytomousKeyService;
98

    
99
    @Autowired
100
    private IDefinedTermDao termDao;
101

    
102
    @Autowired
103
    private IDescriptionService descriptionService;
104

    
105
    @Autowired
106
    private ITaxonNodeService taxonNodeService;
107

    
108
	@Override
109
	@Autowired
110
	protected void setDao(IDescriptiveDataSetDao dao) {
111
		this.dao = dao;
112
	}
113

    
114
	@Override
115
    public Map<DescriptionBase, Set<DescriptionElementBase>> getDescriptionElements(DescriptiveDataSet descriptiveDataSet, Set<Feature> features, Integer pageSize,	Integer pageNumber,
116
			List<String> propertyPaths) {
117
		return dao.getDescriptionElements(descriptiveDataSet, features, pageSize, pageNumber, propertyPaths);
118
	}
119

    
120
	@Override
121
	public <T extends DescriptionElementBase> Map<UuidAndTitleCache, Map<UUID, Set<T>>> getTaxonFeatureDescriptionElementMap(
122
			Class<T> clazz, UUID descriptiveDataSetUuid, DescriptiveSystemRole role) {
123
		return dao.getTaxonFeatureDescriptionElementMap(clazz, descriptiveDataSetUuid, role);
124
	}
125

    
126
	@Override
127
    public List<UuidAndTitleCache<DescriptiveDataSet>> getDescriptiveDataSetUuidAndTitleCache(Integer limitOfInitialElements, String pattern) {
128
        return dao.getDescriptiveDataSetUuidAndTitleCache( limitOfInitialElements, pattern);
129
    }
130

    
131
	@Override
132
	public List<RowWrapperDTO<?>> getRowWrapper(UUID descriptiveDataSetUuid, IProgressMonitor monitor) {
133
	    DescriptiveDataSet descriptiveDataSet = load(descriptiveDataSetUuid);
134
	    monitor.beginTask("Load row wrapper", descriptiveDataSet.getDescriptions().size());
135
	    List<RowWrapperDTO<?>> wrappers = new ArrayList<>();
136
	    Set<DescriptionBase> descriptions = descriptiveDataSet.getDescriptions();
137
	    for (DescriptionBase<?> description : descriptions) {
138
            if(monitor.isCanceled()){
139
                return new ArrayList<>();
140
            }
141
            RowWrapperDTO<?> rowWrapper = null;
142
            // only viable descriptions are aggregated, literature or default descriptions
143
            if(HibernateProxyHelper.isInstanceOf(description, TaxonDescription.class) &&
144
                    (description.isAggregatedStructuredDescription()
145
                            || description.getTypes().contains(DescriptionType.DEFAULT_VALUES_FOR_AGGREGATION)
146
                            || description.getTypes().contains(DescriptionType.SECONDARY_DATA)
147
                            )){
148
                rowWrapper = createTaxonRowWrapper((TaxonDescription)description, descriptiveDataSet.getUuid());
149
            }
150
            else if (HibernateProxyHelper.isInstanceOf(description, SpecimenDescription.class)&&
151
                    !description.getTypes().contains(DescriptionType.CLONE_FOR_SOURCE)){
152
                rowWrapper = createSpecimenRowWrapper(HibernateProxyHelper.deproxy(description, SpecimenDescription.class), descriptiveDataSetUuid);
153
            }
154
            if(rowWrapper!=null){
155
                wrappers.add(rowWrapper);
156
            }
157
            monitor.worked(1);
158
        }
159
	    return wrappers;
160
	}
161

    
162
    @Override
163
    public Collection<SpecimenNodeWrapper> loadSpecimens(DescriptiveDataSet descriptiveDataSet){
164
        List<UUID> filteredNodes = findFilteredTaxonNodes(descriptiveDataSet);
165
        if(filteredNodes.isEmpty()){
166
            return Collections.emptySet();
167
        }
168
        Collection<SpecimenNodeWrapper> result = occurrenceService.listUuidAndTitleCacheByAssociatedTaxon(filteredNodes, null, null);
169

    
170
        return result;
171
    }
172

    
173
    @Override
174
    public Collection<SpecimenNodeWrapper> loadSpecimens(UUID descriptiveDataSetUuid){
175
        DescriptiveDataSet dataSet = load(descriptiveDataSetUuid);
176
        return loadSpecimens(dataSet);
177
    }
178

    
179

    
180
    @Override
181
    public List<UUID> findFilteredTaxonNodes(DescriptiveDataSet descriptiveDataSet){
182
        TaxonNodeFilter filter = TaxonNodeFilter.NewRankInstance(descriptiveDataSet.getMinRank(), descriptiveDataSet.getMaxRank());
183
        descriptiveDataSet.getGeoFilter().forEach(area -> filter.orArea(area.getUuid()));
184
        descriptiveDataSet.getTaxonSubtreeFilter().forEach(node -> filter.orSubtree(node));
185
        filter.setIncludeUnpublished(true);
186

    
187
        return taxonNodeService.uuidList(filter);
188
    }
189

    
190
    @Override
191
    public List<TaxonNode> loadFilteredTaxonNodes(DescriptiveDataSet descriptiveDataSet, List<String> propertyPaths){
192
        return taxonNodeService.load(findFilteredTaxonNodes(descriptiveDataSet), propertyPaths);
193
    }
194

    
195
    @Override
196
    public TaxonDescription findDefaultDescription(UUID specimenDescriptionUuid, UUID dataSetUuid){
197
        SpecimenDescription specimenDescription = (SpecimenDescription) descriptionService.load(specimenDescriptionUuid);
198
        DescriptiveDataSet dataSet = load(dataSetUuid);
199
        TaxonNode node = findTaxonNodeForDescription(specimenDescription, dataSet);
200
        return recurseDefaultDescription(node, dataSet);
201
    }
202

    
203
    private TaxonDescription recurseDefaultDescription(TaxonNode node, DescriptiveDataSet dataSet){
204
        TaxonDescription defaultDescription = null;
205
        if(node!=null && node.getTaxon()!=null){
206
            defaultDescription = findTaxonDescriptionByDescriptionType(dataSet, node.getTaxon(), DescriptionType.DEFAULT_VALUES_FOR_AGGREGATION);
207
            if(defaultDescription==null && node.getParent()!=null){
208
                defaultDescription = recurseDefaultDescription(node.getParent(), dataSet);
209
            }
210
        }
211
        return defaultDescription;
212
    }
213

    
214
    private TaxonNode findTaxonNodeForDescription(SpecimenDescription description, DescriptiveDataSet descriptiveDataSet){
215
        SpecimenOrObservationBase<?> specimen = description.getDescribedSpecimenOrObservation();
216
        //get taxon node
217

    
218
        @SuppressWarnings({ "unchecked", "cast" })
219
        //NOTE: don't remove cast as it does not compile on some systems
220
        Set<IndividualsAssociation> associations = (Set<IndividualsAssociation>)descriptiveDataSet.getDescriptions()
221
                .stream()
222
                .flatMap(desc->desc.getElements().stream())// put all description element in one stream
223
                .filter(element->element instanceof IndividualsAssociation)
224
                .map(ia->(IndividualsAssociation)ia)
225
                .collect(Collectors.toSet());
226
        Classification classification = descriptiveDataSet.getTaxonSubtreeFilter().iterator().next().getClassification();
227
        for (IndividualsAssociation individualsAssociation : associations) {
228
            if(individualsAssociation.getAssociatedSpecimenOrObservation().equals(specimen)){
229
                return ((TaxonDescription) individualsAssociation.getInDescription()).getTaxon().getTaxonNode(classification);
230
            }
231
        }
232
        return null;
233
    }
234

    
235
    @Override
236
    public TaxonRowWrapperDTO createTaxonRowWrapper(TaxonDescription taxonDescription, UUID descriptiveDataSetUuid) {
237
        Classification classification = null;
238
        DescriptionBaseDto description = DescriptionBaseDto.fromDescription(taxonDescription);
239

    
240
        DescriptiveDataSet descriptiveDataSet = dao.load(descriptiveDataSetUuid, null);
241
        Optional<TaxonNode> first = descriptiveDataSet.getTaxonSubtreeFilter().stream()
242
                .filter(node->node.getClassification()!=null).findFirst();
243
        Optional<Classification> classificationOptional = first.map(node->node.getClassification());
244
        Set<DescriptionBaseDto> descriptions = new HashSet<>();
245
        TaxonNodeDto nodeDto = null;
246
        if(classificationOptional.isPresent()){
247
            classification = classificationOptional.get();
248
            nodeDto = taxonNodeService.dto(description.getTaxonDto().getUuid(), classification.getUuid());
249
        }
250

    
251
        return new TaxonRowWrapperDTO(description, nodeDto, descriptions);
252
    }
253

    
254
    @Override
255
    @Transactional(readOnly=false)
256
    public UpdateResult addRowWrapperToDataset(Collection<SpecimenRowWrapperDTO> wrappers, UUID datasetUuid){
257
        UpdateResult result = new UpdateResult();
258
        DescriptiveDataSet dataSet = load(datasetUuid);
259
        result.setCdmEntity(dataSet);
260

    
261
        List<UUID> taxonUuids = wrappers.stream().map(wrapper->wrapper.getTaxonNode().getTaxonUuid()).collect(Collectors.toList());
262
        List<TaxonBase> taxa = taxonService.load(taxonUuids, Arrays.asList(new String[]{"descriptions"}));
263

    
264
        for (SpecimenRowWrapperDTO wrapper : wrappers) {
265
            Optional<TaxonBase> findAny = taxa.stream().filter(taxon->taxon.getUuid().equals(wrapper.getTaxonNode().getTaxonUuid())).findAny();
266
            if(!findAny.isPresent()){
267
                result.addException(new IllegalArgumentException("Could not create wrapper for "+ wrapper.getSpecimenDto().getLabel()));
268
                continue;
269
            }
270
            Taxon taxon = (Taxon) findAny.get();
271

    
272
            SpecimenOrObservationBase<?> specimen = occurrenceService.load(wrapper.getSpecimenDto().getUuid());
273

    
274
            TaxonDescription taxonDescription = taxon.getDescriptions().stream()
275
                    .filter(desc->desc.getTypes().contains(DescriptionType.INDIVIDUALS_ASSOCIATION))
276
                    .findFirst().orElseGet(()->{
277
                        TaxonDescription td = TaxonDescription.NewInstance(taxon);
278
                        td.addType(DescriptionType.INDIVIDUALS_ASSOCIATION);
279
                        td.setTitleCache("Specimens used by " + dataSet.getTitleCache() + " for " + getTaxonLabel(taxon), true);
280
                        return td;});
281

    
282
            IndividualsAssociation association = IndividualsAssociation.NewInstance(specimen);
283
            taxonDescription.addElement(association);
284
            taxonService.saveOrUpdate(taxon);
285
            result.addUpdatedObject(taxon);
286

    
287
            UUID specimenDescriptionUuid = wrapper.getDescription().getDescriptionUuid();
288
            DescriptionBaseDto descriptionDto = wrapper.getDescription();
289
            DescriptionBase<?> specimenDescription =  descriptionService.load(specimenDescriptionUuid);
290
            //if description already exist use the loaded one and add changed data otherwise create a new one and add to specimen
291
            if (specimenDescription == null){
292
                specimenDescription = SpecimenDescription.NewInstance(specimen);
293
                specimenDescription.setUuid(specimenDescriptionUuid);
294
                List<DescriptionElementDto> elementDtos = descriptionDto.getElements();
295

    
296
                for (DescriptionElementDto elementDto: elementDtos){
297
                    if (elementDto instanceof CategoricalDataDto){
298
                        eu.etaxonomy.cdm.model.description.Character feature = DefinedTermBase.getTermByClassAndUUID(eu.etaxonomy.cdm.model.description.Character.class, elementDto.getFeatureUuid());
299
                        CategoricalData data = CategoricalData.NewInstance(feature);
300
                        for (StateDataDto stateDto:((CategoricalDataDto) elementDto).getStates()){
301
                            State state = DefinedTermBase.getTermByClassAndUUID(State.class, stateDto.getState().getUuid());
302
                            data.addStateData(state);
303
                            specimenDescription.addElement(data);
304
                        }
305
                    }
306
                    if (elementDto instanceof QuantitativeDataDto){
307
                        eu.etaxonomy.cdm.model.description.Character feature = DefinedTermBase.getTermByClassAndUUID(eu.etaxonomy.cdm.model.description.Character.class, elementDto.getFeatureUuid());
308
                        QuantitativeData data = QuantitativeData.NewInstance(feature);
309
                        if (((QuantitativeDataDto) elementDto).getMeasurementUnit() != null){
310
                            MeasurementUnit unit = DefinedTermBase.getTermByClassAndUUID(MeasurementUnit.class, ((QuantitativeDataDto) elementDto).getMeasurementUnit().getUuid());
311
                            data.setUnit(unit);
312
                        }
313

    
314
                        for (StatisticalMeasurementValueDto stateDto:((QuantitativeDataDto) elementDto).getValues()){
315
                            StatisticalMeasure statMeasure = DefinedTermBase.getTermByClassAndUUID(StatisticalMeasure.class, stateDto.getType().getUuid());
316
                            StatisticalMeasurementValue value = StatisticalMeasurementValue.NewInstance(statMeasure, stateDto.getValue());
317
                            data.addStatisticalValue(value);
318
                            specimenDescription.addElement(data);
319
                        }
320
                    }
321
                }
322

    
323
            }else {
324
                List<DescriptionElementDto> elementDtos = descriptionDto.getElements();
325
                for (DescriptionElementDto elementDto: elementDtos){
326
                    if (elementDto instanceof CategoricalDataDto){
327
                        eu.etaxonomy.cdm.model.description.Character feature = DefinedTermBase.getTermByClassAndUUID(eu.etaxonomy.cdm.model.description.Character.class, elementDto.getFeatureUuid());
328
                        List<DescriptionElementBase> uniqueElementList = specimenDescription.getElements().stream().filter(element -> element.getUuid().equals(elementDto.getElementUuid())).collect(Collectors.toList());
329
                        List<State> allStates = new ArrayList<>();
330
                        CategoricalData element = null;
331
                        if (uniqueElementList.size() == 1){
332
                            element = HibernateProxyHelper.deproxy(uniqueElementList.get(0), CategoricalData.class);
333
                        }else{
334
                            element = CategoricalData.NewInstance(feature);
335
                        }
336
                        for (StateDataDto stateDto:((CategoricalDataDto) elementDto).getStates()){
337
                            State state = DefinedTermBase.getTermByClassAndUUID(State.class, stateDto.getState().getUuid());
338
                            allStates.add(state);
339
                        }
340
                        element.setStateDataOnly(allStates);
341
                    }
342
                    if (elementDto instanceof QuantitativeDataDto){
343
                        eu.etaxonomy.cdm.model.description.Character feature = DefinedTermBase.getTermByClassAndUUID(eu.etaxonomy.cdm.model.description.Character.class, elementDto.getFeatureUuid());
344
                        QuantitativeData data = QuantitativeData.NewInstance(feature);
345
                        if (((QuantitativeDataDto) elementDto).getMeasurementUnit() != null){
346
                            MeasurementUnit unit = DefinedTermBase.getTermByClassAndUUID(MeasurementUnit.class, ((QuantitativeDataDto) elementDto).getMeasurementUnit().getUuid());
347
                            data.setUnit(unit);
348
                        }
349

    
350
                        for (StatisticalMeasurementValueDto stateDto:((QuantitativeDataDto) elementDto).getValues()){
351
                            StatisticalMeasure statMeasure = DefinedTermBase.getTermByClassAndUUID(StatisticalMeasure.class, stateDto.getType().getUuid());
352
                            StatisticalMeasurementValue value = StatisticalMeasurementValue.NewInstance(statMeasure, stateDto.getValue());
353
                            data.addStatisticalValue(value);
354
                            specimenDescription.addElement(data);
355
                        }
356
                    }
357
                }
358
            }
359

    
360
            //add specimen description to data set
361
            specimenDescription.addDescriptiveDataSet(dataSet);
362
            //add taxon description with IndividualsAssociation to the specimen to data set
363
            taxonDescription.addDescriptiveDataSet(dataSet);
364
            result.addUpdatedObject(specimen);
365
            result.addUpdatedObject(specimenDescription);
366
            result.addUpdatedObject(taxonDescription);
367
        }
368
        saveOrUpdate(dataSet);
369
        return result;
370
    }
371

    
372
    private String getTaxonLabel(Taxon taxon) {
373
        if (taxon.getName() != null){
374
            return taxon.getName().getTitleCache();
375
        }else{
376
            return taxon.getTitleCache();
377
        }
378
    }
379

    
380
    private SpecimenRowWrapperDTO createSpecimenRowWrapper(SpecimenDescription description, UUID taxonNodeUuid,
381
            UUID datasetUuid) {
382
        TaxonNode taxonNode = taxonNodeService.load(taxonNodeUuid);
383
        DescriptiveDataSet descriptiveDataSet = load(datasetUuid);
384
        SpecimenOrObservationBase<?> specimen = HibernateProxyHelper.deproxy(description.getDescribedSpecimenOrObservation(), SpecimenOrObservationBase.class);
385
        //supplemental information
386
        if(taxonNode==null){
387
            taxonNode = findTaxonNodeForDescription(description, descriptiveDataSet);
388
        }
389
        FieldUnit fieldUnit = null;
390
        String identifier = null;
391
        NamedArea country = null;
392
        if(taxonNode==null){
393
            return null;
394
        }
395
        //taxon node was found
396

    
397
        //get field unit
398
        Collection<FieldUnit> fieldUnits = occurrenceService.findFieldUnits(specimen.getUuid(),
399
                Arrays.asList(new String[]{
400
                        "gatheringEvent",
401
                        "gatheringEvent.country"
402
                }));
403
        if(fieldUnits.size()>1){
404
            logger.error("More than one or no field unit found for specimen"); //$NON-NLS-1$
405
            return null;
406
        }
407
        else{
408
            if (fieldUnits.size()>0){
409
                fieldUnit = fieldUnits.iterator().next();
410
            }
411
        }
412
        //get identifier
413
        if(HibernateProxyHelper.isInstanceOf(specimen, DerivedUnit.class)){
414
            identifier = occurrenceService.getMostSignificantIdentifier(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class));
415
        }
416
        //get country
417
        if(fieldUnit != null && fieldUnit.getGatheringEvent() != null){
418
            country = fieldUnit.getGatheringEvent().getCountry();
419
        }
420
        //get default taxon description
421
//        TaxonDescription defaultTaxonDescription = findDefaultDescription(description.getUuid(), descriptiveDataSet.getUuid());
422
        TaxonDescription defaultTaxonDescription = recurseDefaultDescription(taxonNode, descriptiveDataSet);
423
        TaxonRowWrapperDTO taxonRowWrapper = defaultTaxonDescription != null
424
                ? createTaxonRowWrapper(defaultTaxonDescription, descriptiveDataSet.getUuid()) : null;
425
//                use description not specimen for specimenRow
426
        SpecimenRowWrapperDTO specimenRowWrapperDTO = new SpecimenRowWrapperDTO(DescriptionBaseDto.fromDescription(description), SpecimenOrObservationDTOFactory.fromEntity(specimen), specimen.getRecordBasis(), new TaxonNodeDto(taxonNode), fieldUnit, identifier, country);
427
        specimenRowWrapperDTO.setDefaultDescription(taxonRowWrapper);
428
        return specimenRowWrapperDTO;
429
    }
430

    
431
    @Override
432
    public SpecimenRowWrapperDTO createSpecimenRowWrapper(SpecimenDescription description, UUID descriptiveDataSetUuid){
433
        return createSpecimenRowWrapper(description, null, descriptiveDataSetUuid);
434
	}
435

    
436
    @Override
437
    public SpecimenRowWrapperDTO createSpecimenRowWrapper(UUID specimenUuid, UUID taxonNodeUuid, UUID descriptiveDataSetUuid){
438

    
439
        SpecimenOrObservationBase<?> specimen = occurrenceService.load(specimenUuid);
440
        SpecimenDescription specimenDescription = findSpecimenDescription(descriptiveDataSetUuid, specimen, true);
441
        return createSpecimenRowWrapper(specimenDescription, taxonNodeUuid, descriptiveDataSetUuid);
442
    }
443

    
444
    @Override
445
    @Transactional(readOnly = false)
446
    public UpdateResult updateCaches(Class<? extends DescriptiveDataSet> clazz, Integer stepSize,
447
            IIdentifiableEntityCacheStrategy<DescriptiveDataSet> cacheStrategy, IProgressMonitor monitor) {
448
        if (clazz == null) {
449
            clazz = DescriptiveDataSet.class;
450
        }
451
        return super.updateCachesImpl(clazz, stepSize, cacheStrategy, monitor);
452
    }
453

    
454
    private TaxonDescription findTaxonDescriptionByDescriptionType(DescriptiveDataSet dataSet, Taxon taxon, DescriptionType descriptionType){
455
        Optional<TaxonDescription> first = taxon.getDescriptions().stream()
456
                .filter(desc -> desc.getTypes().stream().anyMatch(type -> type.equals(descriptionType)))
457
                .filter(desc -> dataSet.getDescriptions().contains(desc))
458
                .findFirst();
459
        if(first.isPresent()){
460
            return HibernateProxyHelper.deproxy(descriptionService.load(first.get().getUuid(),
461
                  Arrays.asList("taxon", "descriptionElements", "descriptionElements.feature")), TaxonDescription.class);
462
        }
463
        return null;
464
    }
465

    
466
    @Override
467
    public TaxonDescription findTaxonDescriptionByDescriptionType(UUID dataSetUuid, UUID taxonNodeUuid, DescriptionType descriptionType){
468
        DescriptiveDataSet dataSet = load(dataSetUuid);
469
        TaxonNode taxonNode = taxonNodeService.load(taxonNodeUuid);
470
        return findTaxonDescriptionByDescriptionType(dataSet, taxonNode.getTaxon(), descriptionType);
471
    }
472

    
473
    @Override
474
    @Transactional(readOnly=false)
475
    public UpdateResult generatePolytomousKey(UUID descriptiveDataSetUuid, UUID taxonUuid) {
476
        UpdateResult result = new UpdateResult();
477

    
478
        PolytomousKeyGeneratorConfigurator keyConfig = new PolytomousKeyGeneratorConfigurator();
479
        DescriptiveDataSet descriptiveDataSet = load(descriptiveDataSetUuid);
480
        keyConfig.setDataSet(descriptiveDataSet);
481
        PolytomousKey key = new PolytomousKeyGenerator().invoke(keyConfig);
482
        IdentifiableServiceConfiguratorImpl<PolytomousKey> serviceConfig= new IdentifiableServiceConfiguratorImpl<>();
483
        serviceConfig.setTitleSearchString(descriptiveDataSet.getTitleCache());
484
        List<PolytomousKey> list = polytomousKeyService.findByTitle(serviceConfig).getRecords();
485
        if(list!=null){
486
            list.forEach(polytomousKey->polytomousKeyService.delete(polytomousKey));
487
        }
488
        key.setTitleCache(descriptiveDataSet.getTitleCache(), true);
489

    
490
        Taxon taxon = (Taxon) taxonService.load(taxonUuid);
491
        key.addTaxonomicScope(taxon);
492

    
493
        polytomousKeyService.saveOrUpdate(key);
494

    
495
        result.setCdmEntity(key);
496
        result.addUpdatedObject(taxon);
497
        return result;
498
    }
499

    
500
    @Override
501
    @Transactional(readOnly=false)
502
    public DeleteResult removeDescription(UUID descriptionUuid, UUID descriptiveDataSetUuid, RemoveDescriptionsFromDescriptiveDataSetConfigurator config) {
503
        DeleteResult result = new DeleteResult();
504
        DescriptiveDataSet dataSet = load(descriptiveDataSetUuid);
505
        DescriptionBase<?> descriptionBase = descriptionService.load(descriptionUuid);
506
        if(dataSet==null || descriptionBase==null){
507
            result.setError();
508
        }
509
        else{
510
            removeDescriptionFromDataSet(result, dataSet, descriptionBase, config);
511
        }
512
        return result;
513
    }
514

    
515

    
516
    @Override
517
    @Transactional(readOnly=false)
518
    public DeleteResult removeDescriptions(List<UUID> descriptionUuids, UUID descriptiveDataSetUuid, RemoveDescriptionsFromDescriptiveDataSetConfigurator config) {
519
        DeleteResult result = new DeleteResult();
520
        DescriptiveDataSet dataSet = load(descriptiveDataSetUuid);
521
        List<DescriptionBase> descriptions = descriptionService.load(descriptionUuids, null);
522
        if(dataSet==null || descriptions==null){
523
            result.setError();
524
        }
525
        else{
526
            for (DescriptionBase<?> description: descriptions){
527
                removeDescriptionFromDataSet(result, dataSet, description, config);
528
            }
529
        }
530
        return result;
531
    }
532

    
533
    private void removeDescriptionFromDataSet(DeleteResult result, DescriptiveDataSet dataSet,
534
            DescriptionBase<?> description, RemoveDescriptionsFromDescriptiveDataSetConfigurator config) {
535
        if (description == null){
536
            return;
537
        }
538
        boolean success = dataSet.removeDescription(description);
539
        result.addDeletedObject(description);// remove taxon description with IndividualsAssociation from data set
540
        if(description instanceof SpecimenDescription){
541
            @SuppressWarnings({ "unchecked", "cast" })
542
            //NOTE: don't remove cast as it does not compile on some systems
543
            Set<IndividualsAssociation> associations = (Set<IndividualsAssociation>)dataSet.getDescriptions()
544
                    .stream()
545
                    .flatMap(desc->desc.getElements().stream())// put all description element in one stream
546
                    .filter(element->element instanceof IndividualsAssociation)
547
                    .map(ia->(IndividualsAssociation)ia)
548
                    .collect(Collectors.toSet());
549

    
550
            for (IndividualsAssociation individualsAssociation : associations) {
551
                if(individualsAssociation.getAssociatedSpecimenOrObservation().equals(description.getDescribedSpecimenOrObservation())){
552
                    dataSet.removeDescription(individualsAssociation.getInDescription());
553
                    result.addUpdatedObject(individualsAssociation.getInDescription());
554
                }
555
            }
556
        }
557
        if (description instanceof TaxonDescription){
558
            DeleteResult isDeletable = descriptionService.isDeletable(description.getUuid());
559
            for (CdmBase relatedCdmBase: isDeletable.getRelatedObjects()){
560
                if (relatedCdmBase instanceof CdmLinkSource){
561
                    CdmLinkSource linkSource = (CdmLinkSource)relatedCdmBase;
562
                    if (linkSource.getTarget().equals(this)){
563

    
564
                    }
565
                }
566
            }
567

    
568

    
569
        }
570
        if (!config.isOnlyRemoveDescriptionsFromDataSet()){
571
            DeleteResult deleteResult = descriptionService.deleteDescription(description);
572
            result.includeResult(deleteResult);
573
            result.addUpdatedObject(dataSet);
574
        }else{
575
            MergeResult<DescriptiveDataSet> mergeResult = dao.merge(dataSet, true);
576
            result.addUpdatedObject(mergeResult.getMergedEntity());
577
        }
578

    
579
        result.setStatus(success?Status.OK:Status.ERROR);
580
    }
581

    
582
    @Override
583
    @Transactional(readOnly = false)
584
    public DeleteResult delete(UUID datasetUuid, DeleteDescriptiveDataSetConfigurator config,  IProgressMonitor monitor){
585
        DescriptiveDataSet dataSet = dao.load(datasetUuid);
586
        monitor.beginTask("Delete Descriptive Dataset", dataSet.getDescriptions().size() +1);
587

    
588
        DeleteResult result = new DeleteResult();
589
        DeleteResult descriptionResult = new DeleteResult();
590
        if (!dataSet.getDescriptions().isEmpty()){
591
            Set<DescriptionBase> descriptions = new HashSet<>();;
592
            for (DescriptionBase<?> desc: dataSet.getDescriptions()){
593
                descriptions.add(desc);
594
            }
595
            monitor.subTask("Delete descriptions");
596
            for (DescriptionBase<?> desc: descriptions){
597
                dataSet.removeDescription(desc);
598
                if (desc instanceof SpecimenDescription && config.isDeleteAllSpecimenDescriptions()){
599
                    descriptionResult.includeResult(descriptionService.deleteDescription(desc));
600
                }else if (desc instanceof TaxonDescription){
601
                    if( desc.getTypes().contains(DescriptionType.DEFAULT_VALUES_FOR_AGGREGATION) && config.isDeleteAllDefaultDescriptions()){
602
                        descriptionResult.includeResult(descriptionService.deleteDescription(desc));
603
                    }else if (desc.getTypes().contains(DescriptionType.SECONDARY_DATA) && config.isDeleteAllLiteratureDescriptions()){
604
                        descriptionResult.includeResult(descriptionService.deleteDescription(desc));
605
                    }else if (desc.getTypes().contains(DescriptionType.AGGREGATED_STRUC_DESC) && config.isDeleteAllAggregatedDescriptions()){
606
                        descriptionResult.includeResult(descriptionService.deleteDescription(desc));
607
                    }
608
                }
609
            }
610
        }
611
        dao.delete(dataSet);
612
        monitor.worked(1);
613
        monitor.done();
614
        result.includeResult(descriptionResult);
615
        result.setStatus(Status.OK);
616
        result.addDeletedObject(dataSet);
617
        return result;
618
    }
619

    
620
    @Override
621
    @Transactional(readOnly=false)
622
    public TaxonRowWrapperDTO createTaxonDescription(UUID dataSetUuid, UUID taxonNodeUuid, DescriptionType descriptionType){
623
        DescriptiveDataSet dataSet = load(dataSetUuid);
624
        TaxonNode taxonNode = taxonNodeService.load(taxonNodeUuid, Arrays.asList("taxon"));
625
        TaxonDescription newTaxonDescription = TaxonDescription.NewInstance(taxonNode.getTaxon());
626
        newTaxonDescription.setTitleCache(dataSet.getLabel()+": "+newTaxonDescription.generateTitle(), true); //$NON-NLS-2$
627
        newTaxonDescription.getTypes().add(descriptionType);
628
        dataSet.addDescription(newTaxonDescription);
629
        saveOrUpdate(dataSet);
630
        return createTaxonRowWrapper(newTaxonDescription, dataSet.getUuid());
631
    }
632

    
633
    @Override
634
    public Map<UUID, List<TermDto>> getSupportedStatesForFeature(Set<UUID> featureUuids){
635
        return termDao.getSupportedStatesForFeature(featureUuids);
636
    }
637

    
638
    @Override
639
    @Transactional(readOnly=false)
640
    public SpecimenDescription findSpecimenDescription(UUID descriptiveDataSetUuid, SpecimenOrObservationBase specimen, boolean addDatasetSource){
641
        DescriptiveDataSet dataSet = load(descriptiveDataSetUuid);
642
//        SpecimenOrObservationBase specimen = occurrenceService.load(specimenUuid);
643

    
644
        Set<? extends Feature> datasetFeatures = dataSet.getDescriptiveSystem().getDistinctTerms();
645
        List<DescriptionElementBase> matchingDescriptionElements = new ArrayList<>();
646

    
647
        for (SpecimenDescription specimenDescription : (Set<SpecimenDescription>) specimen.getDescriptions()) {
648
            specimenDescription = (SpecimenDescription) descriptionService.load(specimenDescription.getUuid());
649

    
650
            //check if description is already added to data set
651
            if(dataSet.getDescriptions().contains(specimenDescription) ){
652
                return specimenDescription;
653
            }
654

    
655
            //gather specimen description features and check for match with dataset features
656
            Set<Feature> specimenDescriptionFeatures = new HashSet<>();
657
            for (DescriptionElementBase specimenDescriptionElement : specimenDescription.getElements()) {
658
                Feature feature = specimenDescriptionElement.getFeature();
659
                specimenDescriptionFeatures.add(feature);
660
                if(datasetFeatures.contains(feature) && RowWrapperDTO.hasData(specimenDescriptionElement)){
661
                    matchingDescriptionElements.add(specimenDescriptionElement);
662
                }
663
            }
664
        }
665
        //Create new specimen description if description has not already been added to the dataset
666
        SpecimenDescription newDesription = SpecimenDescription.NewInstance(specimen);
667
        newDesription.setTitleCache("Dataset "+dataSet.getLabel()+": "+newDesription.generateTitle(), true); //$NON-NLS-2$
668

    
669
        //check for equals description element (same feature and same values)
670
        Map<Feature, List<DescriptionElementBase>> featureToElementMap = new HashMap<>();
671
        for(DescriptionElementBase element:matchingDescriptionElements){
672
            List<DescriptionElementBase> list = featureToElementMap.get(element.getFeature());
673
            if(list==null){
674
                list = new ArrayList<>();
675
            }
676
            list.add(element);
677
            featureToElementMap.put(element.getFeature(), list);
678
        }
679
        Set<DescriptionElementBase> descriptionElementsToClone = new HashSet<>();
680
        for(Feature feature:featureToElementMap.keySet()){
681
            List<DescriptionElementBase> elements = featureToElementMap.get(feature);
682
            //no duplicate description elements found for this feature
683
            if(elements.size()==1){
684
                descriptionElementsToClone.add(elements.get(0));
685
            }
686
            //duplicates found -> check if all are equal
687
            else{
688
                DescriptionElementBase match = null;
689
                for (DescriptionElementBase descriptionElementBase : elements) {
690
                    if(match==null){
691
                        match = descriptionElementBase;
692
                    }
693
                    else if(!new DescriptionElementCompareWrapper(match).equals(new DescriptionElementCompareWrapper(descriptionElementBase))){
694
                        match = null;
695
                        //TODO: propagate message
696
//                        MessagingUtils.informationDialog(Messages.CharacterMatrix_MULTIPLE_DATA,
697
//                                String.format(Messages.CharacterMatrix_MULTIPLE_DATA_MESSAGE, feature.getLabel()));
698
                        break;
699
                    }
700
                }
701
                if(match!=null){
702
                    descriptionElementsToClone.add(match);
703
                }
704
            }
705
        }
706
        //clone matching descriptionElements
707
        for (DescriptionElementBase descriptionElementBase : descriptionElementsToClone) {
708
            DescriptionElementBase clone;
709
            clone = descriptionElementBase.clone(newDesription);
710
            clone.getSources().forEach(source -> {
711
                if(descriptionElementBase instanceof CategoricalData){
712
                    TextData label = new DefaultCategoricalDescriptionBuilder().build((CategoricalData) descriptionElementBase, Arrays.asList(new Language[]{Language.DEFAULT()}));
713
                    source.setOriginalNameString(label.getText(Language.DEFAULT()));
714
                }
715
                else if(descriptionElementBase instanceof QuantitativeData){
716
                    TextData label = new DefaultQuantitativeDescriptionBuilder().build((QuantitativeData) descriptionElementBase, Arrays.asList(new Language[]{Language.DEFAULT()}));
717
                    source.setOriginalNameString(label.getText(Language.DEFAULT()));
718
                }
719
            });
720
        }
721

    
722
        //add sources of data set
723
        if(addDatasetSource){
724
            dataSet.getSources().forEach(source->{
725
                try {
726
                    newDesription.addSource(source.clone());
727
                } catch (CloneNotSupportedException e) {
728
                    //nothing
729
                }
730
            });
731
        }
732
        return newDesription;
733

    
734
    }
735

    
736
    //TODO: this should either be solved in the model class itself
737
    //OR this should cover all possibilities including modifiers for example
738
    private class DescriptionElementCompareWrapper {
739

    
740
        private DescriptionElementBase element;
741
        private Set<UUID> stateUuids = new HashSet<>();
742
        private Set<BigDecimal> avgs = new HashSet<>();
743
        private Set<BigDecimal> exacts = new HashSet<>();
744
        private Set<BigDecimal> maxs = new HashSet<>();
745
        private Set<BigDecimal> mins = new HashSet<>();
746
        private Set<BigDecimal> sampleSizes = new HashSet<>();
747
        private Set<BigDecimal> standardDevs = new HashSet<>();
748
        private Set<BigDecimal> lowerBounds = new HashSet<>();
749
        private Set<BigDecimal> upperBounds = new HashSet<>();
750
        private Set<BigDecimal> variances = new HashSet<>();
751

    
752
        public DescriptionElementCompareWrapper(DescriptionElementBase element) {
753
            this.element = element;
754
            if(element.isInstanceOf(CategoricalData.class)){
755
                CategoricalData elementData = (CategoricalData)element;
756
                elementData.getStatesOnly().forEach(state->stateUuids.add(state.getUuid()));
757
            }
758
            else if(element.isInstanceOf(QuantitativeData.class)){
759
                QuantitativeData elementData = (QuantitativeData)element;
760
                elementData.getStatisticalValues().forEach(value->{
761
                    if(value.getType().equals(StatisticalMeasure.AVERAGE())){
762
                        avgs.add(value.getValue());
763
                    }
764
                    else if(value.getType().equals(StatisticalMeasure.EXACT_VALUE())){
765
                        exacts.add(value.getValue());
766

    
767
                    }
768
                    else if(value.getType().equals(StatisticalMeasure.MAX())){
769
                        maxs.add(value.getValue());
770
                    }
771
                    else if(value.getType().equals(StatisticalMeasure.MIN())){
772
                        mins.add(value.getValue());
773
                    }
774
                    else if(value.getType().equals(StatisticalMeasure.SAMPLE_SIZE())){
775
                        sampleSizes.add(value.getValue());
776

    
777
                    }
778
                    else if(value.getType().equals(StatisticalMeasure.STANDARD_DEVIATION())){
779
                        standardDevs.add(value.getValue());
780
                    }
781
                    else if(value.getType().equals(StatisticalMeasure.TYPICAL_LOWER_BOUNDARY())){
782
                        lowerBounds.add(value.getValue());
783

    
784
                    }
785
                    else if(value.getType().equals(StatisticalMeasure.TYPICAL_UPPER_BOUNDARY())){
786
                        upperBounds.add(value.getValue());
787
                    }
788
                    else if(value.getType().equals(StatisticalMeasure.VARIANCE())){
789
                        variances.add(value.getValue());
790
                    }
791
                });
792
            }
793
        }
794

    
795
        @Override
796
        public int hashCode() {
797
            final int prime = 31;
798
            int result = 1;
799
            result = prime * result + getOuterType().hashCode();
800
            result = prime * result + ((avgs == null) ? 0 : avgs.hashCode());
801
            result = prime * result + ((element == null) ? 0 : element.hashCode());
802
            result = prime * result + ((exacts == null) ? 0 : exacts.hashCode());
803
            result = prime * result + ((lowerBounds == null) ? 0 : lowerBounds.hashCode());
804
            result = prime * result + ((maxs == null) ? 0 : maxs.hashCode());
805
            result = prime * result + ((mins == null) ? 0 : mins.hashCode());
806
            result = prime * result + ((sampleSizes == null) ? 0 : sampleSizes.hashCode());
807
            result = prime * result + ((standardDevs == null) ? 0 : standardDevs.hashCode());
808
            result = prime * result + ((stateUuids == null) ? 0 : stateUuids.hashCode());
809
            result = prime * result + ((upperBounds == null) ? 0 : upperBounds.hashCode());
810
            result = prime * result + ((variances == null) ? 0 : variances.hashCode());
811
            return result;
812
        }
813

    
814
        @Override
815
        public boolean equals(Object obj) {
816
            if (this == obj) {
817
                return true;
818
            }
819
            if (obj == null) {
820
                return false;
821
            }
822
            if (getClass() != obj.getClass()) {
823
                return false;
824
            }
825
            DescriptionElementCompareWrapper other = (DescriptionElementCompareWrapper) obj;
826
            if (!getOuterType().equals(other.getOuterType())) {
827
                return false;
828
            }
829
            if (avgs == null) {
830
                if (other.avgs != null) {
831
                    return false;
832
                }
833
            } else if (!avgs.equals(other.avgs)) {
834
                return false;
835
            }
836
            if (element == null) {
837
                if (other.element != null) {
838
                    return false;
839
                }
840
            } else if (!element.equals(other.element)) {
841
                return false;
842
            }
843
            if (exacts == null) {
844
                if (other.exacts != null) {
845
                    return false;
846
                }
847
            } else if (!exacts.equals(other.exacts)) {
848
                return false;
849
            }
850
            if (lowerBounds == null) {
851
                if (other.lowerBounds != null) {
852
                    return false;
853
                }
854
            } else if (!lowerBounds.equals(other.lowerBounds)) {
855
                return false;
856
            }
857
            if (maxs == null) {
858
                if (other.maxs != null) {
859
                    return false;
860
                }
861
            } else if (!maxs.equals(other.maxs)) {
862
                return false;
863
            }
864
            if (mins == null) {
865
                if (other.mins != null) {
866
                    return false;
867
                }
868
            } else if (!mins.equals(other.mins)) {
869
                return false;
870
            }
871
            if (sampleSizes == null) {
872
                if (other.sampleSizes != null) {
873
                    return false;
874
                }
875
            } else if (!sampleSizes.equals(other.sampleSizes)) {
876
                return false;
877
            }
878
            if (standardDevs == null) {
879
                if (other.standardDevs != null) {
880
                    return false;
881
                }
882
            } else if (!standardDevs.equals(other.standardDevs)) {
883
                return false;
884
            }
885
            if (stateUuids == null) {
886
                if (other.stateUuids != null) {
887
                    return false;
888
                }
889
            } else if (!stateUuids.equals(other.stateUuids)) {
890
                return false;
891
            }
892
            if (upperBounds == null) {
893
                if (other.upperBounds != null) {
894
                    return false;
895
                }
896
            } else if (!upperBounds.equals(other.upperBounds)) {
897
                return false;
898
            }
899
            if (variances == null) {
900
                if (other.variances != null) {
901
                    return false;
902
                }
903
            } else if (!variances.equals(other.variances)) {
904
                return false;
905
            }
906
            return true;
907
        }
908

    
909
        private DescriptiveDataSetService getOuterType() {
910
            return DescriptiveDataSetService.this;
911
        }
912
    }
913

    
914
    @Override
915
    public DescriptiveDataSetBaseDto getDescriptiveDataSetDtoByUuid(UUID uuid) {
916
        return dao.getDescriptiveDataSetDtoByUuid(uuid);
917
    }
918

    
919
}
(12-12/97)