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