Merge branch 'develop' into taxonDecription
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / DescriptiveDataSetService.java
1 package eu.etaxonomy.cdm.api.service;
2
3 import java.io.Serializable;
4 import java.util.ArrayList;
5 import java.util.Arrays;
6 import java.util.Collection;
7 import java.util.HashMap;
8 import java.util.HashSet;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.Optional;
12 import java.util.Set;
13 import java.util.UUID;
14
15 import org.apache.log4j.Logger;
16 import org.springframework.beans.factory.annotation.Autowired;
17 import org.springframework.stereotype.Service;
18 import org.springframework.transaction.annotation.Transactional;
19
20 import eu.etaxonomy.cdm.api.service.dto.RowWrapperDTO;
21 import eu.etaxonomy.cdm.api.service.dto.SpecimenRowWrapperDTO;
22 import eu.etaxonomy.cdm.api.service.dto.TaxonRowWrapperDTO;
23 import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
24 import eu.etaxonomy.cdm.common.monitor.IRemotingProgressMonitor;
25 import eu.etaxonomy.cdm.common.monitor.RemotingProgressMonitorThread;
26 import eu.etaxonomy.cdm.filter.TaxonNodeFilter;
27 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
28 import eu.etaxonomy.cdm.model.common.Language;
29 import eu.etaxonomy.cdm.model.description.CategoricalData;
30 import eu.etaxonomy.cdm.model.description.DescriptionBase;
31 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
32 import eu.etaxonomy.cdm.model.description.DescriptiveDataSet;
33 import eu.etaxonomy.cdm.model.description.DescriptiveSystemRole;
34 import eu.etaxonomy.cdm.model.description.Feature;
35 import eu.etaxonomy.cdm.model.description.QuantitativeData;
36 import eu.etaxonomy.cdm.model.description.SpecimenDescription;
37 import eu.etaxonomy.cdm.model.description.StatisticalMeasure;
38 import eu.etaxonomy.cdm.model.description.TaxonDescription;
39 import eu.etaxonomy.cdm.model.description.TextData;
40 import eu.etaxonomy.cdm.model.location.NamedArea;
41 import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
42 import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
43 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
44 import eu.etaxonomy.cdm.model.taxon.Classification;
45 import eu.etaxonomy.cdm.model.taxon.Taxon;
46 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
47 import eu.etaxonomy.cdm.persistence.dao.description.IDescriptiveDataSetDao;
48 import eu.etaxonomy.cdm.persistence.dto.SpecimenNodeWrapper;
49 import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
50 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
51
52 @Service
53 @Transactional(readOnly = false)
54 public class DescriptiveDataSetService
55 extends IdentifiableServiceBase<DescriptiveDataSet, IDescriptiveDataSetDao>
56 implements IDescriptiveDataSetService {
57
58 private static Logger logger = Logger.getLogger(DescriptiveDataSetService.class);
59
60 @Autowired
61 private IOccurrenceService occurrenceService;
62
63 @Autowired
64 private ITaxonService taxonService;
65
66 @Autowired
67 private IDescriptionService descriptionService;
68
69 @Autowired
70 private ITaxonNodeService taxonNodeService;
71
72 @Autowired
73 private IProgressMonitorService progressMonitorService;
74
75 @Override
76 @Autowired
77 protected void setDao(IDescriptiveDataSetDao dao) {
78 this.dao = dao;
79 }
80
81 @Override
82 public Map<DescriptionBase, Set<DescriptionElementBase>> getDescriptionElements(DescriptiveDataSet descriptiveDataSet, Set<Feature> features, Integer pageSize, Integer pageNumber,
83 List<String> propertyPaths) {
84 return dao.getDescriptionElements(descriptiveDataSet, features, pageSize, pageNumber, propertyPaths);
85 }
86
87 @Override
88 public <T extends DescriptionElementBase> Map<UuidAndTitleCache, Map<UUID, Set<T>>> getTaxonFeatureDescriptionElementMap(
89 Class<T> clazz, UUID descriptiveDataSetUuid, DescriptiveSystemRole role) {
90 return dao.getTaxonFeatureDescriptionElementMap(clazz, descriptiveDataSetUuid, role);
91 }
92
93 @Override
94 public List<UuidAndTitleCache<DescriptiveDataSet>> getDescriptiveDataSetUuidAndTitleCache(Integer limitOfInitialElements, String pattern) {
95 return dao.getDescriptiveDataSetUuidAndTitleCache( limitOfInitialElements, pattern);
96 }
97
98
99 @Override
100 @Transactional
101 public UUID monitGetRowWrapper(DescriptiveDataSet descriptiveDataSet) {
102 RemotingProgressMonitorThread monitorThread = new RemotingProgressMonitorThread() {
103 @Override
104 public Serializable doRun(IRemotingProgressMonitor monitor) {
105 return getRowWrapper(descriptiveDataSet, monitor);
106 }
107 };
108 UUID uuid = progressMonitorService.registerNewRemotingMonitor(monitorThread);
109 monitorThread.setPriority(3);
110 monitorThread.start();
111 return uuid;
112 }
113
114 @Override
115 public ArrayList<RowWrapperDTO> getRowWrapper(DescriptiveDataSet descriptiveDataSet, IProgressMonitor monitor) {
116 monitor.beginTask("Load row wrapper", descriptiveDataSet.getDescriptions().size());
117 ArrayList<RowWrapperDTO> wrappers = new ArrayList<>();
118 Set<DescriptionBase> descriptions = descriptiveDataSet.getDescriptions();
119 for (DescriptionBase description : descriptions) {
120 if(monitor.isCanceled()){
121 return new ArrayList<>();
122 }
123 RowWrapperDTO rowWrapper = null;
124 if(HibernateProxyHelper.isInstanceOf(description, TaxonDescription.class)){
125 rowWrapper = createTaxonRowWrapper(HibernateProxyHelper.deproxy(description, TaxonDescription.class), descriptiveDataSet);
126 }
127 else if (HibernateProxyHelper.isInstanceOf(description, SpecimenDescription.class)){
128 rowWrapper = createSpecimenRowWrapper(HibernateProxyHelper.deproxy(description, SpecimenDescription.class), descriptiveDataSet);
129 }
130 if(rowWrapper!=null){
131 wrappers.add(rowWrapper);
132 }
133 monitor.worked(1);
134 }
135 return wrappers;
136 }
137
138 @Override
139 public Collection<SpecimenNodeWrapper> loadSpecimens(DescriptiveDataSet descriptiveDataSet){
140 List<UUID> filteredNodes = findFilteredTaxonNodes(descriptiveDataSet);
141 return occurrenceService.listUuidAndTitleCacheByAssociatedTaxon(filteredNodes, null, null);
142 }
143
144 @Override
145 public List<UUID> findFilteredTaxonNodes(DescriptiveDataSet descriptiveDataSet){
146 TaxonNodeFilter filter = TaxonNodeFilter.NewRankInstance(descriptiveDataSet.getMinRank(), descriptiveDataSet.getMaxRank());
147 descriptiveDataSet.getGeoFilter().forEach(area -> filter.orArea(area.getUuid()));
148 descriptiveDataSet.getTaxonSubtreeFilter().forEach(node -> filter.orSubtree(node));
149 filter.setIncludeUnpublished(true);
150
151 return taxonNodeService.uuidList(filter);
152 }
153
154 @Override
155 public List<TaxonNode> loadFilteredTaxonNodes(DescriptiveDataSet descriptiveDataSet, List<String> propertyPaths){
156 return taxonNodeService.load(findFilteredTaxonNodes(descriptiveDataSet), propertyPaths);
157 }
158
159 private TaxonNode findTaxonNodeForDescription(TaxonNode taxonNode, SpecimenOrObservationBase specimen){
160 Collection<SpecimenNodeWrapper> nodeWrapper = occurrenceService.listUuidAndTitleCacheByAssociatedTaxon(Arrays.asList(taxonNode.getUuid()), null, null);
161 for (SpecimenNodeWrapper specimenNodeWrapper : nodeWrapper) {
162 if(specimenNodeWrapper.getUuidAndTitleCache().getId().equals(specimen.getId())){
163 return taxonNode;
164 }
165 }
166 return null;
167 }
168
169 @Override
170 public TaxonRowWrapperDTO createTaxonRowWrapper(TaxonDescription description,
171 DescriptiveDataSet descriptiveDataSet) {
172 TaxonNode taxonNode = null;
173 Classification classification = null;
174 Optional<TaxonNode> first = descriptiveDataSet.getTaxonSubtreeFilter().stream()
175 .filter(node->node.getClassification()!=null).findFirst();
176 Optional<Classification> classificationOptional = first.map(node->node.getClassification());
177 if(classificationOptional.isPresent()){
178 classification = classificationOptional.get();
179 Taxon taxon = (Taxon) taxonService.load(description.getTaxon().getId(), Arrays.asList("taxonNodes", "taxonNodes.classification"));
180 taxonNode = taxon.getTaxonNode(classification);
181 }
182 return new TaxonRowWrapperDTO(description, taxonNode);
183 }
184
185 @Override
186 public SpecimenRowWrapperDTO createSpecimenRowWrapper(SpecimenDescription description, DescriptiveDataSet descriptiveDataSet){
187 SpecimenOrObservationBase specimen = description.getDescribedSpecimenOrObservation();
188 TaxonNode taxonNode = null;
189 FieldUnit fieldUnit = null;
190 String identifier = null;
191 NamedArea country = null;
192 //supplemental information
193 if(specimen!=null){
194 //get taxon node
195 Set<TaxonNode> taxonSubtreeFilter = descriptiveDataSet.getTaxonSubtreeFilter();
196 for (TaxonNode node : taxonSubtreeFilter) {
197 //check for node
198 node = taxonNodeService.load(node.getId(), Arrays.asList("taxon"));
199 taxonNode = findTaxonNodeForDescription(node, specimen);
200 if(taxonNode!=null){
201 break;
202 }
203 else{
204 //check for child nodes
205 List<TaxonNode> allChildren = taxonNodeService.loadChildNodesOfTaxonNode(node, Arrays.asList("taxon"), true, true, null);
206 for (TaxonNode child : allChildren) {
207 taxonNode = findTaxonNodeForDescription(child, specimen);
208 if(taxonNode!=null){
209 break;
210 }
211 }
212 }
213 }
214 if(taxonNode==null){
215 return null;
216 }
217 Collection<FieldUnit> fieldUnits = occurrenceService.findFieldUnits(specimen.getUuid(),
218 Arrays.asList(new String[]{
219 "gatheringEvent",
220 "gatheringEvent.country"
221 }));
222 if(fieldUnits.size()!=1){
223 logger.error("More than one or no field unit found for specimen"); //$NON-NLS-1$
224 return null;
225 }
226 else{
227 fieldUnit = fieldUnits.iterator().next();
228 }
229 if(specimen instanceof DerivedUnit){
230 identifier = occurrenceService.getMostSignificantIdentifier(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class));
231 }
232 if(fieldUnit!=null && fieldUnit.getGatheringEvent()!=null){
233 country = fieldUnit.getGatheringEvent().getCountry();
234 }
235 }
236 return new SpecimenRowWrapperDTO(description, taxonNode, fieldUnit, identifier, country);
237 }
238
239 @Override
240 @Transactional(readOnly = false)
241 public void updateTitleCache(Class<? extends DescriptiveDataSet> clazz, Integer stepSize,
242 IIdentifiableEntityCacheStrategy<DescriptiveDataSet> cacheStrategy, IProgressMonitor monitor) {
243 if (clazz == null) {
244 clazz = DescriptiveDataSet.class;
245 }
246 super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);
247 }
248
249 @Override
250 public SpecimenDescription findDescriptionForDescriptiveDataSet(UUID descriptiveDataSetUuid, UUID specimenUuid){
251 DescriptiveDataSet dataSet = load(descriptiveDataSetUuid);
252 SpecimenOrObservationBase specimen = occurrenceService.load(specimenUuid);
253
254 Set<Feature> datasetFeatures = dataSet.getDescriptiveSystem().getDistinctFeatures();
255 List<DescriptionElementBase> matchingDescriptionElements = new ArrayList<>();
256
257 for (SpecimenDescription specimenDescription : (Set<SpecimenDescription>) specimen.getDescriptions()) {
258 specimenDescription = (SpecimenDescription) descriptionService.load(specimenDescription.getUuid());
259
260 //check if description is already added to data set
261 if(dataSet.getDescriptions().contains(specimenDescription)){
262 return specimenDescription;
263 }
264
265 //gather specimen description features and check for match with dataset features
266 Set<Feature> specimenDescriptionFeatures = new HashSet<>();
267 for (DescriptionElementBase specimenDescriptionElement : specimenDescription.getElements()) {
268 Feature feature = specimenDescriptionElement.getFeature();
269 specimenDescriptionFeatures.add(feature);
270 if(datasetFeatures.contains(feature)){
271 matchingDescriptionElements.add(specimenDescriptionElement);
272 }
273 }
274 }
275 //Create new specimen description if description has not already been added to the dataset
276 SpecimenDescription newDesription = SpecimenDescription.NewInstance(specimen);
277 newDesription.setTitleCache("Dataset "+dataSet.getLabel()+": "+newDesription.generateTitle(), true); //$NON-NLS-2$
278
279 //check for equals description element (same feature and same values)
280 Map<Feature, List<DescriptionElementBase>> featureToElementMap = new HashMap<>();
281 for(DescriptionElementBase element:matchingDescriptionElements){
282 List<DescriptionElementBase> list = featureToElementMap.get(element.getFeature());
283 if(list==null){
284 list = new ArrayList<>();
285 }
286 list.add(element);
287 featureToElementMap.put(element.getFeature(), list);
288 }
289 Set<DescriptionElementBase> descriptionElementsToClone = new HashSet<>();
290 for(Feature feature:featureToElementMap.keySet()){
291 List<DescriptionElementBase> elements = featureToElementMap.get(feature);
292 //no duplicate description elements found for this feature
293 if(elements.size()==1){
294 descriptionElementsToClone.add(elements.get(0));
295 }
296 //duplicates found -> check if all are equal
297 else{
298 DescriptionElementBase match = null;
299 for (DescriptionElementBase descriptionElementBase : elements) {
300 if(match==null){
301 match = descriptionElementBase;
302 }
303 else if(!new DescriptionElementCompareWrapper(match).equals(new DescriptionElementCompareWrapper(descriptionElementBase))){
304 match = null;
305 //TODO: propagate message
306 // MessagingUtils.informationDialog(Messages.CharacterMatrix_MULTIPLE_DATA,
307 // String.format(Messages.CharacterMatrix_MULTIPLE_DATA_MESSAGE, feature.getLabel()));
308 break;
309 }
310 }
311 if(match!=null){
312 descriptionElementsToClone.add(match);
313 }
314 }
315 }
316 //clone matching descriptionElements
317 for (DescriptionElementBase descriptionElementBase : descriptionElementsToClone) {
318 DescriptionElementBase clone;
319 try {
320 clone = descriptionElementBase.clone(newDesription);
321 clone.getSources().forEach(source -> {
322 if(descriptionElementBase instanceof CategoricalData){
323 TextData label = new DefaultCategoricalDescriptionBuilder().build((CategoricalData) descriptionElementBase, null);
324 source.setOriginalNameString(label.getText(Language.DEFAULT()));
325 }
326 else if(descriptionElementBase instanceof QuantitativeData){
327 TextData label = new DefaultQuantitativeDescriptionBuilder().build((QuantitativeData) descriptionElementBase, null);
328 source.setOriginalNameString(label.getText(Language.DEFAULT()));
329 }
330 });
331 } catch (CloneNotSupportedException e) {
332 // MessagingUtils.error(CharacterMatrix.class, e);
333 }
334 }
335
336 //add all remaining description elements to the new description
337 for(Feature wsFeature:datasetFeatures){
338 boolean featureFound = false;
339 for(DescriptionElementBase element:newDesription.getElements()){
340 if(element.getFeature().equals(wsFeature)){
341 featureFound = true;
342 break;
343 }
344 }
345 if(!featureFound){
346 if(wsFeature.isSupportsCategoricalData()){
347 newDesription.addElement(CategoricalData.NewInstance(wsFeature));
348 }
349 else if(wsFeature.isSupportsQuantitativeData()){
350 newDesription.addElement(QuantitativeData.NewInstance(wsFeature));
351 }
352 }
353 }
354 return newDesription;
355
356 }
357
358 //TODO: this should either be solved in the model class itself
359 //OR this should cover all possibilities including modifiers for example
360 private class DescriptionElementCompareWrapper {
361
362 private DescriptionElementBase element;
363 private Set<UUID> stateUuids = new HashSet<>();
364 private Set<Float> avgs = new HashSet<>();
365 private Set<Float> exacts = new HashSet<>();
366 private Set<Float> maxs = new HashSet<>();
367 private Set<Float> mins = new HashSet<>();
368 private Set<Float> sampleSizes = new HashSet<>();
369 private Set<Float> standardDevs = new HashSet<>();
370 private Set<Float> lowerBounds = new HashSet<>();
371 private Set<Float> upperBounds = new HashSet<>();
372 private Set<Float> variances = new HashSet<>();
373
374 public DescriptionElementCompareWrapper(DescriptionElementBase element) {
375 this.element = element;
376 if(element.isInstanceOf(CategoricalData.class)){
377 CategoricalData elementData = (CategoricalData)element;
378 elementData.getStatesOnly().forEach(state->stateUuids.add(state.getUuid()));
379 }
380 else if(element.isInstanceOf(QuantitativeData.class)){
381 QuantitativeData elementData = (QuantitativeData)element;
382 elementData.getStatisticalValues().forEach(value->{
383 if(value.getType().equals(StatisticalMeasure.AVERAGE())){
384 avgs.add(value.getValue());
385 }
386 else if(value.getType().equals(StatisticalMeasure.EXACT_VALUE())){
387 exacts.add(value.getValue());
388
389 }
390 else if(value.getType().equals(StatisticalMeasure.MAX())){
391 maxs.add(value.getValue());
392 }
393 else if(value.getType().equals(StatisticalMeasure.MIN())){
394 mins.add(value.getValue());
395 }
396 else if(value.getType().equals(StatisticalMeasure.SAMPLE_SIZE())){
397 sampleSizes.add(value.getValue());
398
399 }
400 else if(value.getType().equals(StatisticalMeasure.STANDARD_DEVIATION())){
401 standardDevs.add(value.getValue());
402 }
403 else if(value.getType().equals(StatisticalMeasure.TYPICAL_LOWER_BOUNDARY())){
404 lowerBounds.add(value.getValue());
405
406 }
407 else if(value.getType().equals(StatisticalMeasure.TYPICAL_UPPER_BOUNDARY())){
408 upperBounds.add(value.getValue());
409 }
410 else if(value.getType().equals(StatisticalMeasure.VARIANCE())){
411 variances.add(value.getValue());
412 }
413 });
414 }
415 }
416
417 @Override
418 public int hashCode() {
419 final int prime = 31;
420 int result = 1;
421 result = prime * result + getOuterType().hashCode();
422 result = prime * result + ((avgs == null) ? 0 : avgs.hashCode());
423 result = prime * result + ((element == null) ? 0 : element.hashCode());
424 result = prime * result + ((exacts == null) ? 0 : exacts.hashCode());
425 result = prime * result + ((lowerBounds == null) ? 0 : lowerBounds.hashCode());
426 result = prime * result + ((maxs == null) ? 0 : maxs.hashCode());
427 result = prime * result + ((mins == null) ? 0 : mins.hashCode());
428 result = prime * result + ((sampleSizes == null) ? 0 : sampleSizes.hashCode());
429 result = prime * result + ((standardDevs == null) ? 0 : standardDevs.hashCode());
430 result = prime * result + ((stateUuids == null) ? 0 : stateUuids.hashCode());
431 result = prime * result + ((upperBounds == null) ? 0 : upperBounds.hashCode());
432 result = prime * result + ((variances == null) ? 0 : variances.hashCode());
433 return result;
434 }
435
436 @Override
437 public boolean equals(Object obj) {
438 if (this == obj) {
439 return true;
440 }
441 if (obj == null) {
442 return false;
443 }
444 if (getClass() != obj.getClass()) {
445 return false;
446 }
447 DescriptionElementCompareWrapper other = (DescriptionElementCompareWrapper) obj;
448 if (!getOuterType().equals(other.getOuterType())) {
449 return false;
450 }
451 if (avgs == null) {
452 if (other.avgs != null) {
453 return false;
454 }
455 } else if (!avgs.equals(other.avgs)) {
456 return false;
457 }
458 if (element == null) {
459 if (other.element != null) {
460 return false;
461 }
462 } else if (!element.equals(other.element)) {
463 return false;
464 }
465 if (exacts == null) {
466 if (other.exacts != null) {
467 return false;
468 }
469 } else if (!exacts.equals(other.exacts)) {
470 return false;
471 }
472 if (lowerBounds == null) {
473 if (other.lowerBounds != null) {
474 return false;
475 }
476 } else if (!lowerBounds.equals(other.lowerBounds)) {
477 return false;
478 }
479 if (maxs == null) {
480 if (other.maxs != null) {
481 return false;
482 }
483 } else if (!maxs.equals(other.maxs)) {
484 return false;
485 }
486 if (mins == null) {
487 if (other.mins != null) {
488 return false;
489 }
490 } else if (!mins.equals(other.mins)) {
491 return false;
492 }
493 if (sampleSizes == null) {
494 if (other.sampleSizes != null) {
495 return false;
496 }
497 } else if (!sampleSizes.equals(other.sampleSizes)) {
498 return false;
499 }
500 if (standardDevs == null) {
501 if (other.standardDevs != null) {
502 return false;
503 }
504 } else if (!standardDevs.equals(other.standardDevs)) {
505 return false;
506 }
507 if (stateUuids == null) {
508 if (other.stateUuids != null) {
509 return false;
510 }
511 } else if (!stateUuids.equals(other.stateUuids)) {
512 return false;
513 }
514 if (upperBounds == null) {
515 if (other.upperBounds != null) {
516 return false;
517 }
518 } else if (!upperBounds.equals(other.upperBounds)) {
519 return false;
520 }
521 if (variances == null) {
522 if (other.variances != null) {
523 return false;
524 }
525 } else if (!variances.equals(other.variances)) {
526 return false;
527 }
528 return true;
529 }
530
531 private DescriptiveDataSetService getOuterType() {
532 return DescriptiveDataSetService.this;
533 }
534
535 }
536
537 }