ref #7674 Create default taxon description when adding specimens
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / DescriptiveDataSetService.java
index 9ed2428dc14d628bb88ef4c88be88b04659a722f..d5d97989b0d5a880a80ba96347ea6ee292617b3b 100644 (file)
@@ -8,8 +8,10 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.UUID;
+import java.util.stream.Collectors;
 
 import org.apache.log4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -17,12 +19,15 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import eu.etaxonomy.cdm.api.service.dto.RowWrapperDTO;
+import eu.etaxonomy.cdm.api.service.dto.SpecimenRowWrapperDTO;
+import eu.etaxonomy.cdm.api.service.dto.TaxonRowWrapperDTO;
 import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
 import eu.etaxonomy.cdm.common.monitor.IRemotingProgressMonitor;
 import eu.etaxonomy.cdm.common.monitor.RemotingProgressMonitorThread;
 import eu.etaxonomy.cdm.filter.TaxonNodeFilter;
 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
 import eu.etaxonomy.cdm.model.common.Language;
+import eu.etaxonomy.cdm.model.common.MarkerType;
 import eu.etaxonomy.cdm.model.description.CategoricalData;
 import eu.etaxonomy.cdm.model.description.DescriptionBase;
 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
@@ -31,13 +36,15 @@ import eu.etaxonomy.cdm.model.description.DescriptiveSystemRole;
 import eu.etaxonomy.cdm.model.description.Feature;
 import eu.etaxonomy.cdm.model.description.QuantitativeData;
 import eu.etaxonomy.cdm.model.description.SpecimenDescription;
+import eu.etaxonomy.cdm.model.description.StatisticalMeasure;
+import eu.etaxonomy.cdm.model.description.TaxonDescription;
 import eu.etaxonomy.cdm.model.description.TextData;
 import eu.etaxonomy.cdm.model.location.NamedArea;
 import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
 import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
+import eu.etaxonomy.cdm.model.taxon.Classification;
 import eu.etaxonomy.cdm.model.taxon.Taxon;
-import eu.etaxonomy.cdm.model.taxon.TaxonBase;
 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
 import eu.etaxonomy.cdm.persistence.dao.description.IDescriptiveDataSetDao;
 import eu.etaxonomy.cdm.persistence.dto.SpecimenNodeWrapper;
@@ -55,6 +62,9 @@ public class DescriptiveDataSetService
     @Autowired
     private IOccurrenceService occurrenceService;
 
+    @Autowired
+    private ITaxonService taxonService;
+
     @Autowired
     private IDescriptionService descriptionService;
 
@@ -112,7 +122,16 @@ public class DescriptiveDataSetService
             if(monitor.isCanceled()){
                 return new ArrayList<>();
             }
-            wrappers.add(createRowWrapper(null, description, null, descriptiveDataSet));
+            RowWrapperDTO rowWrapper = null;
+            if(HibernateProxyHelper.isInstanceOf(description, TaxonDescription.class)){
+                rowWrapper = createTaxonRowWrapper(HibernateProxyHelper.deproxy(description, TaxonDescription.class), descriptiveDataSet);
+            }
+            else if (HibernateProxyHelper.isInstanceOf(description, SpecimenDescription.class)){
+                rowWrapper = createSpecimenRowWrapper(HibernateProxyHelper.deproxy(description, SpecimenDescription.class), descriptiveDataSet, false);
+            }
+            if(rowWrapper!=null){
+                wrappers.add(rowWrapper);
+            }
             monitor.worked(1);
         }
            return wrappers;
@@ -120,69 +139,112 @@ public class DescriptiveDataSetService
 
     @Override
     public Collection<SpecimenNodeWrapper> loadSpecimens(DescriptiveDataSet descriptiveDataSet){
-        //set filter parameters
+        List<UUID> filteredNodes = findFilteredTaxonNodes(descriptiveDataSet);
+        return occurrenceService.listUuidAndTitleCacheByAssociatedTaxon(filteredNodes, null, null);
+    }
+
+    @Override
+    public List<UUID> findFilteredTaxonNodes(DescriptiveDataSet descriptiveDataSet){
         TaxonNodeFilter filter = TaxonNodeFilter.NewRankInstance(descriptiveDataSet.getMinRank(), descriptiveDataSet.getMaxRank());
         descriptiveDataSet.getGeoFilter().forEach(area -> filter.orArea(area.getUuid()));
         descriptiveDataSet.getTaxonSubtreeFilter().forEach(node -> filter.orSubtree(node));
         filter.setIncludeUnpublished(true);
 
-        List<UUID> filteredNodes = taxonNodeService.uuidList(filter);
-        return occurrenceService.listUuidAndTitleCacheByAssociatedTaxon(filteredNodes, null, null);
+        return taxonNodeService.uuidList(filter);
     }
 
     @Override
-    public RowWrapperDTO createRowWrapper(DescriptionBase description, DescriptiveDataSet descriptiveDataSet){
-        return createRowWrapper(null, description, null, descriptiveDataSet);
+    public List<TaxonNode> loadFilteredTaxonNodes(DescriptiveDataSet descriptiveDataSet, List<String> propertyPaths){
+        return taxonNodeService.load(findFilteredTaxonNodes(descriptiveDataSet), propertyPaths);
+    }
+
+    private TaxonNode findTaxonNodeForDescription(TaxonNode taxonNode, SpecimenOrObservationBase specimen){
+        Collection<SpecimenNodeWrapper> nodeWrapper = occurrenceService.listUuidAndTitleCacheByAssociatedTaxon(Arrays.asList(taxonNode.getUuid()), null, null);
+        for (SpecimenNodeWrapper specimenNodeWrapper : nodeWrapper) {
+            if(specimenNodeWrapper.getUuidAndTitleCache().getId().equals(specimen.getId())){
+                return taxonNode;
+            }
+        }
+        return null;
     }
 
     @Override
-    public RowWrapperDTO createRowWrapper(SpecimenOrObservationBase specimen, DescriptiveDataSet descriptiveDataSet){
-        return createRowWrapper(specimen, null, null, descriptiveDataSet);
+    public TaxonRowWrapperDTO createTaxonRowWrapper(TaxonDescription description,
+            DescriptiveDataSet descriptiveDataSet) {
+        TaxonNode taxonNode = null;
+        Classification classification = null;
+        Optional<TaxonNode> first = descriptiveDataSet.getTaxonSubtreeFilter().stream()
+                .filter(node->node.getClassification()!=null).findFirst();
+        Optional<Classification> classificationOptional = first.map(node->node.getClassification());
+        if(classificationOptional.isPresent()){
+            classification = classificationOptional.get();
+            Taxon taxon = (Taxon) taxonService.load(description.getTaxon().getId(), Arrays.asList("taxonNodes", "taxonNodes.classification"));
+            taxonNode = taxon.getTaxonNode(classification);
+        }
+        return new TaxonRowWrapperDTO(description, taxonNode);
     }
 
-       private RowWrapperDTO createRowWrapper(SpecimenOrObservationBase specimen, DescriptionBase description, TaxonNode taxonNode, DescriptiveDataSet descriptiveDataSet){
-           if(description!=null){
-               specimen = description.getDescribedSpecimenOrObservation();
-           }
+    @Override
+    public SpecimenRowWrapperDTO createSpecimenRowWrapper(SpecimenDescription description, DescriptiveDataSet descriptiveDataSet,
+            boolean createDefaultTaxonDescription){
+           SpecimenOrObservationBase specimen = description.getDescribedSpecimenOrObservation();
+           TaxonNode taxonNode = null;
         FieldUnit fieldUnit = null;
         String identifier = null;
         NamedArea country = null;
         //supplemental information
-        if(specimen!=null){
-            if(taxonNode==null){
-                Collection<TaxonBase<?>> associatedTaxa = occurrenceService.listAssociatedTaxa(specimen, null, null, null,
-                        Arrays.asList(new String[]{
-                                "taxonNodes",
-                                "taxonNodes.classification",
-                        }));
-                if(associatedTaxa!=null && !associatedTaxa.isEmpty()){
-                    //FIXME: what about multiple associated taxa
-                    Set<TaxonNode> taxonSubtreeFilter = descriptiveDataSet.getTaxonSubtreeFilter();
-                    if(taxonSubtreeFilter!=null && !taxonSubtreeFilter.isEmpty()){
-                        Taxon taxon = HibernateProxyHelper.deproxy(associatedTaxa.iterator().next(), Taxon.class);
-                        taxonNode = taxon.getTaxonNode(taxonSubtreeFilter.iterator().next().getClassification());
-                    }
-                }
-            }
-            Collection<FieldUnit> fieldUnits = occurrenceService.findFieldUnits(specimen.getUuid(),
-                    Arrays.asList(new String[]{
-                            "gatheringEvent",
-                            "gatheringEvent.country"
-                            }));
-            if(fieldUnits.size()!=1){
-                logger.error("More than one or no field unit found for specimen"); //$NON-NLS-1$
+        //get taxon node
+        Set<TaxonNode> taxonSubtreeFilter = descriptiveDataSet.getTaxonSubtreeFilter();
+        for (TaxonNode node : taxonSubtreeFilter) {
+            //check for node
+            node = taxonNodeService.load(node.getId(), Arrays.asList("taxon"));
+            taxonNode = findTaxonNodeForDescription(node, specimen);
+            if(taxonNode!=null){
+                break;
             }
             else{
-                fieldUnit = fieldUnits.iterator().next();
-            }
-            if(specimen instanceof DerivedUnit){
-                identifier = occurrenceService.getMostSignificantIdentifier(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class));
-            }
-            if(fieldUnit!=null && fieldUnit.getGatheringEvent()!=null){
-                country = fieldUnit.getGatheringEvent().getCountry();
+                //check for child nodes
+                List<TaxonNode> allChildren = taxonNodeService.loadChildNodesOfTaxonNode(node, Arrays.asList("taxon"), true, true, null);
+                for (TaxonNode child : allChildren) {
+                    taxonNode = findTaxonNodeForDescription(child, specimen);
+                    if(taxonNode!=null){
+                        break;
+                    }
+                }
             }
         }
-        return new RowWrapperDTO(description, specimen, taxonNode, fieldUnit, identifier, country);
+        if(taxonNode==null){
+            return null;
+        }
+        //taxon node was found
+
+        //get field unit
+        Collection<FieldUnit> fieldUnits = occurrenceService.findFieldUnits(specimen.getUuid(),
+                Arrays.asList(new String[]{
+                        "gatheringEvent",
+                        "gatheringEvent.country"
+                }));
+        if(fieldUnits.size()!=1){
+            logger.error("More than one or no field unit found for specimen"); //$NON-NLS-1$
+            return null;
+        }
+        else{
+            fieldUnit = fieldUnits.iterator().next();
+        }
+        //get identifier
+        if(specimen instanceof DerivedUnit){
+            identifier = occurrenceService.getMostSignificantIdentifier(HibernateProxyHelper.deproxy(specimen, DerivedUnit.class));
+        }
+        //get country
+        if(fieldUnit!=null && fieldUnit.getGatheringEvent()!=null){
+            country = fieldUnit.getGatheringEvent().getCountry();
+        }
+        //get default taxon description
+        TaxonDescription defaultTaxonDescription = findDefaultTaxonDescription(descriptiveDataSet.getUuid(),
+                taxonNode.getUuid(), createDefaultTaxonDescription);
+        TaxonRowWrapperDTO taxonRowWrapper = defaultTaxonDescription != null
+                ? createTaxonRowWrapper(defaultTaxonDescription, descriptiveDataSet) : null;
+        return new SpecimenRowWrapperDTO(description, taxonNode, fieldUnit, identifier, country, taxonRowWrapper);
        }
 
     @Override
@@ -196,7 +258,41 @@ public class DescriptiveDataSetService
     }
 
     @Override
-    public SpecimenDescription findDescriptionForDescriptiveDataSet(UUID descriptiveDataSetUuid, UUID specimenUuid){
+    public TaxonDescription findDefaultTaxonDescription(UUID descriptiveDataSetUuid, UUID taxonNodeUuid, boolean create){
+        DescriptiveDataSet dataSet = load(descriptiveDataSetUuid);
+        TaxonNode taxonNode = taxonNodeService.load(taxonNodeUuid, Arrays.asList("taxon", "taxon.descriptions", "taxon.descriptions.markers"));
+        Set<DescriptionBase> dataSetDescriptions = dataSet.getDescriptions();
+        //filter out COMPUTED descriptions
+        List<TaxonDescription> nonComputedDescriptions = taxonNode.getTaxon().getDescriptions().stream()
+                .filter(desc -> desc.getMarkers().stream()
+                        .anyMatch(marker -> marker.getMarkerType().equals(MarkerType.COMPUTED())))
+                .collect(Collectors.toList());
+        for (TaxonDescription taxonDescription : nonComputedDescriptions) {
+            for (DescriptionBase description : dataSetDescriptions) {
+                if(description.getUuid().equals(taxonDescription.getUuid())){
+                    return taxonDescription;
+                }
+            }
+        }
+        if(!create){
+            return null;
+        }
+        //description not yet added to dataset -> create a new one
+        TaxonDescription newTaxonDescription = TaxonDescription.NewInstance(taxonNode.getTaxon());
+        newTaxonDescription.setTitleCache("Dataset "+dataSet.getLabel()+": "+newTaxonDescription.generateTitle(), true); //$NON-NLS-2$
+        dataSet.getDescriptiveSystem().getDistinctFeatures().forEach(wsFeature->{
+            if(wsFeature.isSupportsCategoricalData()){
+                newTaxonDescription.addElement(CategoricalData.NewInstance(wsFeature));
+            }
+            else if(wsFeature.isSupportsQuantitativeData()){
+                newTaxonDescription.addElement(QuantitativeData.NewInstance(wsFeature));
+            }
+        });
+        return newTaxonDescription;
+    }
+
+    @Override
+    public SpecimenDescription findSpecimenDescription(UUID descriptiveDataSetUuid, UUID specimenUuid){
         DescriptiveDataSet dataSet = load(descriptiveDataSetUuid);
         SpecimenOrObservationBase specimen = occurrenceService.load(specimenUuid);
 
@@ -205,8 +301,14 @@ public class DescriptiveDataSetService
 
         for (SpecimenDescription specimenDescription : (Set<SpecimenDescription>) specimen.getDescriptions()) {
             specimenDescription = (SpecimenDescription) descriptionService.load(specimenDescription.getUuid());
-            Set<Feature> specimenDescriptionFeatures = new HashSet<>();
+
+            //check if description is already added to data set
+            if(dataSet.getDescriptions().contains(specimenDescription)){
+                return specimenDescription;
+            }
+
             //gather specimen description features and check for match with dataset features
+            Set<Feature> specimenDescriptionFeatures = new HashSet<>();
             for (DescriptionElementBase specimenDescriptionElement : specimenDescription.getElements()) {
                 Feature feature = specimenDescriptionElement.getFeature();
                 specimenDescriptionFeatures.add(feature);
@@ -214,14 +316,10 @@ public class DescriptiveDataSetService
                     matchingDescriptionElements.add(specimenDescriptionElement);
                 }
             }
-            //if description with the exact same features is found return the description
-            if(specimenDescriptionFeatures.equals(datasetFeatures)){
-                return specimenDescription;
-            }
         }
-        //Create new specimen description if no match was found
+        //Create new specimen description if description has not already been added to the dataset
         SpecimenDescription newDesription = SpecimenDescription.NewInstance(specimen);
-        newDesription.setTitleCache("Dataset"+dataSet.getLabel()+": "+newDesription.generateTitle(), true); //$NON-NLS-2$
+        newDesription.setTitleCache("Dataset "+dataSet.getLabel()+": "+newDesription.generateTitle(), true); //$NON-NLS-2$
 
         //check for equals description element (same feature and same values)
         Map<Feature, List<DescriptionElementBase>> featureToElementMap = new HashMap<>();
@@ -302,12 +400,21 @@ public class DescriptiveDataSetService
 
     }
 
+    //TODO: this should either be solved in the model class itself
+    //OR this should cover all possibilities including modifiers for example
     private class DescriptionElementCompareWrapper {
 
         private DescriptionElementBase element;
         private Set<UUID> stateUuids = new HashSet<>();
-        private Float min = null;
-        private Float max = null;
+        private Set<Float> avgs = new HashSet<>();
+        private Set<Float> exacts = new HashSet<>();
+        private Set<Float> maxs = new HashSet<>();
+        private Set<Float> mins = new HashSet<>();
+        private Set<Float> sampleSizes = new HashSet<>();
+        private Set<Float> standardDevs = new HashSet<>();
+        private Set<Float> lowerBounds = new HashSet<>();
+        private Set<Float> upperBounds = new HashSet<>();
+        private Set<Float> variances = new HashSet<>();
 
         public DescriptionElementCompareWrapper(DescriptionElementBase element) {
             this.element = element;
@@ -317,22 +424,57 @@ public class DescriptiveDataSetService
             }
             else if(element.isInstanceOf(QuantitativeData.class)){
                 QuantitativeData elementData = (QuantitativeData)element;
-                min = elementData.getMin();
-                max = elementData.getMax();
-            }
-        }
+                elementData.getStatisticalValues().forEach(value->{
+                    if(value.getType().equals(StatisticalMeasure.AVERAGE())){
+                        avgs.add(value.getValue());
+                    }
+                    else if(value.getType().equals(StatisticalMeasure.EXACT_VALUE())){
+                        exacts.add(value.getValue());
 
-        public DescriptionElementBase unwrap() {
-            return element;
+                    }
+                    else if(value.getType().equals(StatisticalMeasure.MAX())){
+                        maxs.add(value.getValue());
+                    }
+                    else if(value.getType().equals(StatisticalMeasure.MIN())){
+                        mins.add(value.getValue());
+                    }
+                    else if(value.getType().equals(StatisticalMeasure.SAMPLE_SIZE())){
+                        sampleSizes.add(value.getValue());
+
+                    }
+                    else if(value.getType().equals(StatisticalMeasure.STANDARD_DEVIATION())){
+                        standardDevs.add(value.getValue());
+                    }
+                    else if(value.getType().equals(StatisticalMeasure.TYPICAL_LOWER_BOUNDARY())){
+                        lowerBounds.add(value.getValue());
+
+                    }
+                    else if(value.getType().equals(StatisticalMeasure.TYPICAL_UPPER_BOUNDARY())){
+                        upperBounds.add(value.getValue());
+                    }
+                    else if(value.getType().equals(StatisticalMeasure.VARIANCE())){
+                        variances.add(value.getValue());
+                    }
+                });
+            }
         }
 
         @Override
         public int hashCode() {
             final int prime = 31;
             int result = 1;
-            result = prime * result + ((max == null) ? 0 : max.hashCode());
-            result = prime * result + ((min == null) ? 0 : min.hashCode());
+            result = prime * result + getOuterType().hashCode();
+            result = prime * result + ((avgs == null) ? 0 : avgs.hashCode());
+            result = prime * result + ((element == null) ? 0 : element.hashCode());
+            result = prime * result + ((exacts == null) ? 0 : exacts.hashCode());
+            result = prime * result + ((lowerBounds == null) ? 0 : lowerBounds.hashCode());
+            result = prime * result + ((maxs == null) ? 0 : maxs.hashCode());
+            result = prime * result + ((mins == null) ? 0 : mins.hashCode());
+            result = prime * result + ((sampleSizes == null) ? 0 : sampleSizes.hashCode());
+            result = prime * result + ((standardDevs == null) ? 0 : standardDevs.hashCode());
             result = prime * result + ((stateUuids == null) ? 0 : stateUuids.hashCode());
+            result = prime * result + ((upperBounds == null) ? 0 : upperBounds.hashCode());
+            result = prime * result + ((variances == null) ? 0 : variances.hashCode());
             return result;
         }
 
@@ -348,18 +490,63 @@ public class DescriptiveDataSetService
                 return false;
             }
             DescriptionElementCompareWrapper other = (DescriptionElementCompareWrapper) obj;
-            if (max == null) {
-                if (other.max != null) {
+            if (!getOuterType().equals(other.getOuterType())) {
+                return false;
+            }
+            if (avgs == null) {
+                if (other.avgs != null) {
+                    return false;
+                }
+            } else if (!avgs.equals(other.avgs)) {
+                return false;
+            }
+            if (element == null) {
+                if (other.element != null) {
+                    return false;
+                }
+            } else if (!element.equals(other.element)) {
+                return false;
+            }
+            if (exacts == null) {
+                if (other.exacts != null) {
                     return false;
                 }
-            } else if (!max.equals(other.max)) {
+            } else if (!exacts.equals(other.exacts)) {
                 return false;
             }
-            if (min == null) {
-                if (other.min != null) {
+            if (lowerBounds == null) {
+                if (other.lowerBounds != null) {
                     return false;
                 }
-            } else if (!min.equals(other.min)) {
+            } else if (!lowerBounds.equals(other.lowerBounds)) {
+                return false;
+            }
+            if (maxs == null) {
+                if (other.maxs != null) {
+                    return false;
+                }
+            } else if (!maxs.equals(other.maxs)) {
+                return false;
+            }
+            if (mins == null) {
+                if (other.mins != null) {
+                    return false;
+                }
+            } else if (!mins.equals(other.mins)) {
+                return false;
+            }
+            if (sampleSizes == null) {
+                if (other.sampleSizes != null) {
+                    return false;
+                }
+            } else if (!sampleSizes.equals(other.sampleSizes)) {
+                return false;
+            }
+            if (standardDevs == null) {
+                if (other.standardDevs != null) {
+                    return false;
+                }
+            } else if (!standardDevs.equals(other.standardDevs)) {
                 return false;
             }
             if (stateUuids == null) {
@@ -369,8 +556,27 @@ public class DescriptiveDataSetService
             } else if (!stateUuids.equals(other.stateUuids)) {
                 return false;
             }
+            if (upperBounds == null) {
+                if (other.upperBounds != null) {
+                    return false;
+                }
+            } else if (!upperBounds.equals(other.upperBounds)) {
+                return false;
+            }
+            if (variances == null) {
+                if (other.variances != null) {
+                    return false;
+                }
+            } else if (!variances.equals(other.variances)) {
+                return false;
+            }
             return true;
         }
+
+        private DescriptiveDataSetService getOuterType() {
+            return DescriptiveDataSetService.this;
+        }
+
     }
 
 }