← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 16150: Impl support for data element totals in validation rules

 

------------------------------------------------------------
revno: 16150
committer: Lars Helge Overland <larshelge@xxxxxxxxx>
branch nick: dhis2
timestamp: Wed 2014-07-16 17:29:27 +0200
message:
  Impl support for data element totals in validation rules
modified:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/Expression.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/indicator/Indicator.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationResult.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidatorThread.java
  dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/expressionBuilder.js


--
lp:dhis2
https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk

Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/Expression.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/Expression.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/Expression.java	2014-07-16 15:29:27 +0000
@@ -28,12 +28,14 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonView;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
 import org.apache.commons.lang.Validate;
 import org.hisp.dhis.common.BaseIdentifiableObject;
 import org.hisp.dhis.common.DxfNamespaces;
@@ -113,6 +115,12 @@
     private Set<DataElementCategoryOptionCombo> optionCombosInExpression = new HashSet<DataElementCategoryOptionCombo>();
 
     // -------------------------------------------------------------------------
+    // Transient properties
+    // -------------------------------------------------------------------------
+
+    private transient String explodedExpression;
+    
+    // -------------------------------------------------------------------------
     // Constructors
     // -------------------------------------------------------------------------
 
@@ -140,6 +148,18 @@
     }
 
     // -------------------------------------------------------------------------
+    // Logic
+    // -------------------------------------------------------------------------
+
+    /**
+     * Returns exploded expression, if null returns expression.
+     */
+    public String getExplodedExpressionFallback()
+    {
+        return explodedExpression != null ? explodedExpression : expression;
+    }
+    
+    // -------------------------------------------------------------------------
     // Equals and hashCode
     // -------------------------------------------------------------------------
 
@@ -195,8 +215,8 @@
     {
         final int PRIME = 31;
         int result = 1;
-        result = PRIME * result + ((description == null) ? 0 : description.hashCode());
-        result = PRIME * result + ((expression == null) ? 0 : expression.hashCode());
+        result = PRIME * result + ( ( description == null ) ? 0 : description.hashCode() );
+        result = PRIME * result + ( ( expression == null ) ? 0 : expression.hashCode() );
 
         return result;
     }
@@ -207,6 +227,7 @@
         return "Expression{" +
             "id=" + id +
             ", expression='" + expression + '\'' +
+            ", explodedExpression='" + explodedExpression + '\'' +
             ", description='" + description + '\'' +
             ", dataElementsInExpression=" + dataElementsInExpression.size() +
             ", optionCombosInExpression=" + optionCombosInExpression.size() +
@@ -296,6 +317,17 @@
         this.nullIfBlank = nullIfBlank;
     }
 
+    @JsonIgnore
+    public String getExplodedExpression()
+    {
+        return explodedExpression;
+    }
+    
+    public void setExplodedExpression( String explodedExpression )
+    {
+        this.explodedExpression = explodedExpression;
+    }
+    
     public void mergeWith( Expression other )
     {
         Validate.notNull( other );

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java	2014-07-16 15:29:27 +0000
@@ -39,6 +39,7 @@
 import org.hisp.dhis.indicator.Indicator;
 import org.hisp.dhis.organisationunit.OrganisationUnitGroup;
 import org.hisp.dhis.period.Period;
+import org.hisp.dhis.validation.ValidationRule;
 
 /**
  * Expressions are mathematical formulas and can contain references to various
@@ -277,6 +278,9 @@
      * Populates the explodedNumerator and explodedDenominator property on all
      * indicators in the given collection. This method uses
      * explodeExpression( String ) internally to generate the exploded expressions.
+     * Replaces references to data element totals with references to all
+     * category option combos in the category combo for that data element.
+     * 
      * This method will perform better compared to calling explodeExpression( String )
      * multiple times outside a transactional context as the transactional
      * overhead is avoided.
@@ -290,12 +294,25 @@
      * Populates the explodedNumerator and explodedDenominator property on all
      * indicators in the given collection. This method uses
      * explodeExpression( String ) internally to generate the exploded expressions.
+     * Replaces references to data element totals with references to all
+     * category option combos in the category combo for that data element.
      * 
      * @param indicators the collection of indicators.
      */    
     void explodeExpressions( Collection<Indicator> indicators );
     
     /**
+     * Populates the explodedExpression property on the Expression object of all
+     * validation rules in the given collection. This method uses
+     * explodeExpression( String ) internally to generate the exploded expressions.
+     * Replaces references to data element totals with references to all
+     * category option combos in the category combo for that data element.
+     * 
+     * @param validationRules the collection of validation rules.
+     */
+    void explodeValidationRuleExpressions( Collection<ValidationRule> validationRules );
+    
+    /**
      * Replaces references to data element totals with references to all
      * category option combos in the category combo for that data element.
      * 

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/indicator/Indicator.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/indicator/Indicator.java	2014-04-25 11:22:12 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/indicator/Indicator.java	2014-07-16 15:29:27 +0000
@@ -41,6 +41,7 @@
 import org.hisp.dhis.dataset.DataSet;
 import org.hisp.dhis.mapping.MapLegendSet;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonView;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@@ -68,13 +69,13 @@
 
     private String numeratorDescription;
 
-    private String explodedNumerator;
+    private transient String explodedNumerator;
 
     private String denominator;
 
     private String denominatorDescription;
 
-    private String explodedDenominator;
+    private transient String explodedDenominator;
 
     private Integer sortOrder;
 
@@ -222,9 +223,7 @@
         this.numeratorDescription = numeratorDescription;
     }
 
-    @JsonProperty
-    @JsonView( {DetailedView.class, ExportView.class} )
-    @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0)
+    @JsonIgnore
     public String getExplodedNumerator()
     {
         return explodedNumerator;
@@ -261,9 +260,7 @@
         this.denominatorDescription = denominatorDescription;
     }
 
-    @JsonProperty
-    @JsonView( {DetailedView.class, ExportView.class} )
-    @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0)
+    @JsonIgnore
     public String getExplodedDenominator()
     {
         return explodedDenominator;

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationResult.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationResult.java	2014-05-18 00:49:40 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationResult.java	2014-07-16 15:29:27 +0000
@@ -95,11 +95,11 @@
         return result;
     }
 
-    //
-    // Note: this method is called from threads in which it may not be possible
-    // to initialize lazy Hibernate properties. So object properties to compare
-    // must be chosen accordingly.
-    //
+    /**
+     * Note: this method is called from threads in which it may not be possible
+     * to initialize lazy Hibernate properties. So object properties to compare
+     * must be chosen accordingly.
+     */
     @Override
     public boolean equals( Object object )
     {
@@ -203,11 +203,11 @@
         return true;
     }
 
-    //
-    // Note: this method is called from threads in which it may not be possible
-    // to initialize lazy Hibernate properties. So object properties to compare
-    // must be chosen accordingly.
-    //
+    /**
+     * Note: this method is called from threads in which it may not be possible
+     * to initialize lazy Hibernate properties. So object properties to compare
+     * must be chosen accordingly.
+     */
     public int compareTo( ValidationResult other )
     {
     	int result = source.getName().compareTo( other.source.getName() );
@@ -291,7 +291,11 @@
     @Override
     public String toString()
     {
-        return source + " - " + period + " - " + attributeOptionCombo.getName() + " - " + validationRule + " - " + leftsideValue + " - " + rightsideValue;
+        return "[Source: " + source + 
+            ", period: " + period + 
+            ", validation rule: " + validationRule + 
+            ", left side value: " + leftsideValue + 
+            ", right side value: " + rightsideValue + "]";
     }
 
     // -------------------------------------------------------------------------

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java	2014-07-16 15:29:27 +0000
@@ -63,6 +63,7 @@
 import org.hisp.dhis.period.Period;
 import org.hisp.dhis.system.util.DateUtils;
 import org.hisp.dhis.system.util.MathUtils;
+import org.hisp.dhis.validation.ValidationRule;
 import org.springframework.transaction.annotation.Transactional;
 
 /**
@@ -196,7 +197,7 @@
     public Double getExpressionValue( Expression expression, Map<DataElementOperand, Double> valueMap,
         Map<String, Double> constantMap, Map<String, Integer> orgUnitCountMap, Integer days )
     {
-        final String expressionString = generateExpression( expression.getExpression(), valueMap, constantMap, 
+        final String expressionString = generateExpression( expression.getExplodedExpressionFallback(), valueMap, constantMap, 
             orgUnitCountMap, days, expression.isNullIfBlank() );
 
         return expressionString != null ? calculateExpression( expressionString ) : null;
@@ -205,7 +206,7 @@
     public Double getExpressionValue( Expression expression, Map<DataElementOperand, Double> valueMap,
         Map<String, Double> constantMap, Map<String, Integer> orgUnitCountMap, Integer days, Set<DataElementOperand> incompleteValues )
     {
-        final String expressionString = generateExpression( expression.getExpression(), valueMap, constantMap, orgUnitCountMap, days,
+        final String expressionString = generateExpression( expression.getExplodedExpressionFallback(), valueMap, constantMap, orgUnitCountMap, days,
             expression.isNullIfBlank(), incompleteValues );
 
         return expressionString != null ? calculateExpression( expressionString ) : null;
@@ -603,7 +604,7 @@
             explodeExpressions( indicators );
         }
     }
-
+    
     @Transactional
     public void explodeExpressions( Collection<Indicator> indicators )
     {
@@ -632,7 +633,36 @@
             }
         }
     }
-    
+
+    @Transactional
+    public void explodeValidationRuleExpressions( Collection<ValidationRule> validationRules )
+    {
+        if ( validationRules != null && !validationRules.isEmpty() )
+        {
+            Set<String> dataElementTotals = new HashSet<String>();
+            
+            for ( ValidationRule rule : validationRules )
+            {
+                dataElementTotals.addAll( getDataElementTotalUids( rule.getLeftSide().getExpression() ) );
+                dataElementTotals.addAll( getDataElementTotalUids( rule.getRightSide().getExpression() ) );
+            }
+
+            if ( !dataElementTotals.isEmpty() )
+            {
+                final ListMap<String, String> dataElementMap = dataElementService.getDataElementCategoryOptionComboMap( dataElementTotals );
+                
+                if ( !dataElementMap.isEmpty() )
+                {
+                    for ( ValidationRule rule : validationRules )
+                    {
+                        rule.getLeftSide().setExplodedExpression( explodeExpression( rule.getLeftSide().getExplodedExpressionFallback(), dataElementMap ) );
+                        rule.getRightSide().setExplodedExpression( explodeExpression( rule.getRightSide().getExplodedExpressionFallback(), dataElementMap ) );
+                    }
+                }
+            }            
+        }
+    }
+
     private String explodeExpression( String expression, ListMap<String, String> dataElementOptionComboMap )
     {
         if ( expression == null || expression.isEmpty() )
@@ -655,7 +685,7 @@
                 
                 for ( String coc : cocs )
                 {
-                    replace.append( EXP_OPEN ).append( matcher.group( 1 ) ).append( SEPARATOR ).append(
+                    replace.append( EXP_OPEN ).append( de ).append( SEPARATOR ).append(
                         coc ).append( EXP_CLOSE ).append( "+" );
                 }
 

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidatorThread.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidatorThread.java	2014-07-16 12:15:52 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidatorThread.java	2014-07-16 15:29:27 +0000
@@ -92,7 +92,8 @@
             for ( PeriodTypeExtended periodTypeX : context.getPeriodTypeExtendedMap().values() )
             {
                 Collection<DataElement> sourceDataElements = periodTypeX.getSourceDataElements().get( sourceX.getSource() );
-                Set<ValidationRule> rules = getRulesBySourceAndPeriodType( sourceX, periodTypeX, sourceDataElements );
+                Set<ValidationRule> rules = getRulesBySourceAndPeriodType( sourceX, periodTypeX, sourceDataElements );                
+                context.getExpressionService().explodeValidationRuleExpressions( rules );
 
                 if ( !rules.isEmpty() )
                 {
@@ -112,7 +113,7 @@
 
                         for ( ValidationRule rule : rules )
                         {
-                            if ( evaluateCheck( currentValueMap, lastUpdatedMap, rule ) )
+                            if ( evaluateValidationCheck( currentValueMap, lastUpdatedMap, rule ) )
                             {
                                 Map<Integer, Double> leftSideValues = getExpressionValueMap( rule.getLeftSide(),
                                         currentValueMap, incompleteValuesMap );
@@ -120,19 +121,19 @@
                                 if ( !leftSideValues.isEmpty() || Operator.compulsory_pair.equals( rule.getOperator() ) )
                                 {
                                     Map<Integer, Double> rightSideValues = getRightSideValue( sourceX.getSource(), periodTypeX, period, rule,
-                                            currentValueMap, sourceDataElements );
+                                        currentValueMap, sourceDataElements );
 
                                     if ( !rightSideValues.isEmpty() || Operator.compulsory_pair.equals( rule.getOperator() ) )
                                     {
-                                        Set<Integer> combos = leftSideValues.keySet();
+                                        Set<Integer> attributeOptionCombos = leftSideValues.keySet();
                                         
                                         if ( Operator.compulsory_pair.equals( rule.getOperator() ) )
                                         {
-                                            combos = new HashSet<Integer>( combos );
-                                            combos.addAll( rightSideValues.keySet() );
+                                            attributeOptionCombos = new HashSet<Integer>( attributeOptionCombos );
+                                            attributeOptionCombos.addAll( rightSideValues.keySet() );
                                         }
 
-                                        for ( int combo : combos )
+                                        for ( int combo : attributeOptionCombos )
                                         {
                                             Double leftSide = leftSideValues.get ( combo );
                                             Double rightSide = rightSideValues.get ( combo );
@@ -197,6 +198,7 @@
                 // But if this is some funny kind of rule with no elements
                 // (like for testing), include it also.
                 Collection<DataElement> elements = rule.getCurrentDataElements();
+                
                 if ( elements == null || elements.size() == 0 || sourceDataElements.containsAll( elements ) )
                 {
                     periodTypeRules.add( rule );
@@ -233,7 +235,7 @@
      * @param rule the rule that may be evaluated
      * @return true if the rule should be evaluated with this data, false if not
      */
-    private boolean evaluateCheck( MapMap<Integer, DataElementOperand, Double> currentValueMapMap,
+    private boolean evaluateValidationCheck( MapMap<Integer, DataElementOperand, Double> currentValueMapMap,
         MapMap<Integer, DataElementOperand, Date> lastUpdatedMapMap, ValidationRule rule )
     {
         boolean evaluate = true; // Assume true for now.
@@ -268,6 +270,7 @@
                         for ( DataElementOperand deo : deos )
                         {
                             Date lastUpdated = entry.getValue().get( deo );
+                            
                             if ( lastUpdated != null && lastUpdated.after( context.getLastScheduledRun() ) )
                             {
                                 saveThisCombo = true; // True if new/updated data.
@@ -454,17 +457,17 @@
      * @return map of values.
      */
     private Map<Integer, Double> getExpressionValueMap( Expression expression,
-                                                       MapMap<Integer, DataElementOperand, Double> valueMap,
-                                                       SetMap<Integer, DataElementOperand> incompleteValuesMap )
+        MapMap<Integer, DataElementOperand, Double> valueMap, 
+        SetMap<Integer, DataElementOperand> incompleteValuesMap )
     {
         Map<Integer, Double> expressionValueMap = new HashMap<Integer, Double>();
 
-        for ( Map.Entry<Integer, Map<DataElementOperand, Double>> e : valueMap.entrySet() )
+        for ( Map.Entry<Integer, Map<DataElementOperand, Double>> entry : valueMap.entrySet() )
         {
-            expressionValueMap.put( e.getKey(),
+            expressionValueMap.put( entry.getKey(),
                 context.getExpressionService().getExpressionValue( expression,
-                e.getValue(), context.getConstantMap(), null, null, 
-                incompleteValuesMap.getSet( e.getKey() ) ) );
+                entry.getValue(), context.getConstantMap(), null, null, 
+                incompleteValuesMap.getSet( entry.getKey() ) ) );
         }
 
         return expressionValueMap;
@@ -480,8 +483,8 @@
      * @param sequentialSampleCount number of sequential samples tried for
      * @return average right-side sample value
      */
-    private Double rightSideAverage( ValidationRule rule, List<Double> sampleValues, int annualSampleCount,
-        int sequentialSampleCount )
+    private Double rightSideAverage( ValidationRule rule, List<Double> sampleValues, 
+        int annualSampleCount, int sequentialSampleCount )
     {
         // Find the expected sample count for the last period of its type in the
         // database: sequentialSampleCount for the immediately preceding periods 
@@ -546,10 +549,10 @@
      * @return map of attribute option combo to map of values found.
      */
     private MapMap<Integer, DataElementOperand, Double> getValueMap( PeriodTypeExtended periodTypeX,
-            Collection<DataElement> ruleDataElements, Collection<DataElement> sourceDataElements,
-            Set<DataElement> recursiveDataElements, Collection<PeriodType> allowedPeriodTypes, Period period,
-            OrganisationUnit source, MapMap<Integer, DataElementOperand, Date> lastUpdatedMap,
-            SetMap<Integer, DataElementOperand> incompleteValuesMap )
+        Collection<DataElement> ruleDataElements, Collection<DataElement> sourceDataElements,
+        Set<DataElement> recursiveDataElements, Collection<PeriodType> allowedPeriodTypes, Period period,
+        OrganisationUnit source, MapMap<Integer, DataElementOperand, Date> lastUpdatedMap,
+        SetMap<Integer, DataElementOperand> incompleteValuesMap )
     {
         Set<DataElement> dataElementsToGet = new HashSet<DataElement>( ruleDataElements );
         dataElementsToGet.retainAll( sourceDataElements );

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/expressionBuilder.js'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/expressionBuilder.js	2013-10-16 12:39:47 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/expressionBuilder.js	2014-07-16 15:29:27 +0000
@@ -73,7 +73,7 @@
 	var periodTypeAllowAverage = ( ruleType && ruleType == "surveillance" ) ? true : false;
 
 	dataDictionary.loadOperands( "#expression-container select[id=dataElementId]", 
-		{usePaging: true, key: key, periodType: periodType, periodTypeAllowAverage: periodTypeAllowAverage } );	
+		{usePaging: true, key: key, periodType: periodType, includeTotals: true, periodTypeAllowAverage: periodTypeAllowAverage } );	
 }
 
 function clearSearchText()