← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 15297: Support attribute categories in validation

 

------------------------------------------------------------
revno: 15297
committer: jimgrace@xxxxxxxxx
branch nick: dhis2
timestamp: Sat 2014-05-17 20:49:40 -0400
message:
  Support attribute categories in validation
renamed:
  dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/MapMap.java => dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/MapMap.java
modified:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/ListMap.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/SetMap.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueService.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueStore.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationResult.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleService.java
  dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DataQueryParams.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueStore.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/OrganisationUnitExtended.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/PeriodTypeExtended.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRuleExtended.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRunContext.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/Validator.java
  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/resources/META-INF/dhis/beans.xml
  dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/validation/ValidationRuleServiceTest.java
  dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/ValidationAction.java
  dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/META-INF/dhis/beans.xml
  dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/javascript/form.js
  dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/validationResult.vm
  dhis-2/dhis-web/dhis-web-light/src/main/java/org/hisp/dhis/light/utils/FormUtilsImpl.java
  dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/RunValidationAction.java
  dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/META-INF/dhis/beans.xml
  dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/org/hisp/dhis/validationrule/i18n_module.properties
  dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultForm.vm
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/MapMap.java


--
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/common/ListMap.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/ListMap.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/ListMap.java	2014-05-18 00:49:40 +0000
@@ -31,6 +31,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * @author Lars Helge Overland
@@ -38,6 +39,11 @@
 public class ListMap<T, V>
     extends HashMap<T, List<V>>
 {
+    /**
+     * Determines if a de-serialized file is compatible with this class.
+     */
+    private static final long serialVersionUID = 4880664228933342003L;
+
     public ListMap()
     {
         super();
@@ -56,4 +62,12 @@
         super.put( key, list );        
         return null;
     }
+
+    public void putValueMap( Map<T, V> map )
+    {
+        for ( Map.Entry<T, V> entry : map.entrySet() )
+        {
+            putValue( entry.getKey(), entry.getValue() );
+        }
+    }
 }

=== renamed file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/MapMap.java' => 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/MapMap.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/MapMap.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/MapMap.java	2014-05-18 00:49:40 +0000
@@ -1,4 +1,4 @@
-package org.hisp.dhis.system.util;
+package org.hisp.dhis.common;
 
 /*
  * Copyright (c) 2004-2014, University of Oslo
@@ -52,4 +52,9 @@
         map.putAll( m );
         this.put( key, map );
     }
+
+    public V getValue( T key, U valueKey )
+    {
+        return this.get( key ) == null ? null : this.get( key ).get( valueKey );
+    }
 }

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/SetMap.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/SetMap.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/SetMap.java	2014-05-18 00:49:40 +0000
@@ -55,4 +55,11 @@
         set.add( value );
         return super.put( key, set );
     }
+
+    public Set<V> getSet( T key )
+    {
+        Set<V> set = this.get( key );
+        set = set == null ? new HashSet<V>() : set;
+        return super.put( key, set );
+    }
 }

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueService.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueService.java	2014-04-17 14:19:13 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueService.java	2014-05-18 00:49:40 +0000
@@ -32,6 +32,7 @@
 import java.util.Date;
 import java.util.Map;
 
+import org.hisp.dhis.common.MapMap;
 import org.hisp.dhis.dataelement.DataElement;
 import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
 import org.hisp.dhis.dataelement.DataElementOperand;
@@ -296,33 +297,25 @@
      * @return the number of DataValues.
      */
     int getDataValueCount( int days );
-    
-    /**
-     * Returns a map of values indexed by DataElementOperand.
-     * 
-     * @param dataElements collection of DataElements to fetch for
-     * @param period period for which to fetch the values
-     * @param unit OrganisationUnit for which to fetch the values
-     * @return
-     */
-    Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit source );
 
     /**
-     * Returns a map of values indexed by DataElementOperand.
-     * 
+     * Returns a map of values for each attribute option combo found.
+     * <p>
      * In the (unlikely) event that the same dataElement/optionCombo is found in
-     * more than one period for the same organisationUnit and date, the value
-     * is returned from the period with the shortest duration.
-     * 
+     * more than one period for the same organisationUnit, date, and attribute
+     * combo, the value is returned from the period with the shortest duration.
+     *
      * @param dataElements collection of DataElements to fetch for
      * @param date date which must be present in the period
-     * @param unit OrganisationUnit for which to fetch the values
+     * @param source OrganisationUnit for which to fetch the values
      * @param periodTypes allowable period types in which to find the data
+     * @param attributeCombo the attribute combo to check (if restricted)
      * @param lastUpdatedMap map in which to return the lastUpdated date for each value
-     * @return
+     * @return map of values by attribute option combo id, then DataElementOperand
      */
-    Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Date date, OrganisationUnit source,
-    		Collection<PeriodType> periodTypes, Map<DataElementOperand, Date> lastUpdatedMap );
+    MapMap<Integer, DataElementOperand, Double> getDataValueMapByAttributeCombo( Collection<DataElement> dataElements, Date date,
+            OrganisationUnit source, Collection<PeriodType> periodTypes, DataElementCategoryOptionCombo attributeCombo,
+            MapMap<Integer, DataElementOperand, Date> lastUpdatedMap );
 
     /**
      * Gets a Collection of DeflatedDataValues.

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueStore.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueStore.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueStore.java	2014-05-18 00:49:40 +0000
@@ -30,8 +30,8 @@
 
 import java.util.Collection;
 import java.util.Date;
-import java.util.Map;
 
+import org.hisp.dhis.common.MapMap;
 import org.hisp.dhis.dataelement.DataElement;
 import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
 import org.hisp.dhis.dataelement.DataElementOperand;
@@ -229,19 +229,19 @@
      * collection of Periods, and collection of Sources.
      * 
      * @param dataElement the DataElements of the DataValues.
-     * @param optionCombo the DataElementCategoryOptionCombo of the DataValues.
+     * @param categoryOptionCombo the DataElementCategoryOptionCombo of the DataValues.
      * @param periods the Periods of the DataValues.
      * @param sources the Sources of the DataValues.
      * @return a collection of all DataValues which match the given DataElement,
      *         Periods, and Sources.
      */
-    Collection<DataValue> getDataValues( DataElement dataElement, DataElementCategoryOptionCombo categoryOptionCombos, 
+    Collection<DataValue> getDataValues( DataElement dataElement, DataElementCategoryOptionCombo categoryOptionCombo,
         Collection<Period> periods, Collection<OrganisationUnit> sources );
     
     /**
      * Returns all DataValues for a given collection of DataElementCategoryOptionCombos.
      * 
-     * @param optionCombos the DataElementCategoryOptionCombos of the DataValue.
+     * @param categoryOptionCombos the DataElementCategoryOptionCombos of the DataValue.
      * @return a collection of all DataValues which match the given collection of
      *         DataElementCategoryOptionCombos.
      */
@@ -273,34 +273,25 @@
      * @return the number of DataValues.
      */
     int getDataValueCount( Date date );
-    
-    /**
-     * Returns a map of values indexed by DataElementOperand.
-     * 
-     * @param dataElements collection of DataElements to fetch for
-     * @param period period for which to fetch the values
-     * @param unit OrganisationUnit for which to fetch the values
-     * @param lastUpdatedMap optional map in which to return the lastUpdated date for each value
-     * @return
-     */
-    Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit source );
 
     /**
-     * Returns a map of values indexed by DataElementOperand.
-     * 
+     * Returns a map of values for each attribute option combo found.
+     * <p>
      * In the (unlikely) event that the same dataElement/optionCombo is found in
-     * more than one period for the same organisationUnit and date, the value
-     * is returned from the period with the shortest duration.
+     * more than one period for the same organisationUnit, date, and attribute
+     * combo, the value is returned from the period with the shortest duration.
      * 
      * @param dataElements collection of DataElements to fetch for
      * @param date date which must be present in the period
-     * @param unit OrganisationUnit for which to fetch the values
+     * @param source OrganisationUnit for which to fetch the values
      * @param periodTypes allowable period types in which to find the data
+     * @param attributeCombo the attribute combo to check (if restricted)
      * @param lastUpdatedMap map in which to return the lastUpdated date for each value
-     * @return
+     * @return map of values by attribute option combo id, then DataElementOperand
      */
-    Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Date date, OrganisationUnit source,
-    		Collection<PeriodType> periodTypes, Map<DataElementOperand, Date> lastUpdatedMap );
+    MapMap<Integer, DataElementOperand, Double> getDataValueMapByAttributeCombo( Collection<DataElement> dataElements, Date date,
+            OrganisationUnit source, Collection<PeriodType> periodTypes, DataElementCategoryOptionCombo attributeCombo,
+            MapMap<Integer, DataElementOperand, Date> lastUpdatedMap );
     
     /**
      * Gets a Collection of DeflatedDataValues.

=== 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-03-18 08:10:10 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationResult.java	2014-05-18 00:49:40 +0000
@@ -28,6 +28,7 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
 import org.hisp.dhis.organisationunit.OrganisationUnit;
 import org.hisp.dhis.period.Period;
 
@@ -48,6 +49,8 @@
 
     private Period period;
 
+    private DataElementCategoryOptionCombo attributeOptionCombo;
+
     private ValidationRule validationRule;
 
     private Double leftsideValue;
@@ -62,11 +65,13 @@
     {
     }
 
-    public ValidationResult( Period period, OrganisationUnit source, ValidationRule validationRule,
+    public ValidationResult( Period period, OrganisationUnit source,
+        DataElementCategoryOptionCombo attributeOptionCombo, ValidationRule validationRule,
         Double leftsideValue, Double rightsideValue )
     {
         this.source = source;
         this.period = period;
+        this.attributeOptionCombo = attributeOptionCombo;
         this.validationRule = validationRule;
         this.leftsideValue = leftsideValue;
         this.rightsideValue = rightsideValue;
@@ -90,6 +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.
+    //
     @Override
     public boolean equals( Object object )
     {
@@ -122,6 +132,18 @@
             return false;
         }
 
+        if ( attributeOptionCombo == null )
+        {
+            if ( other.attributeOptionCombo != null )
+            {
+                return false;
+            }
+        }
+        else if ( attributeOptionCombo.getId() != other.attributeOptionCombo.getId() )
+        {
+            return false;
+        }
+
         if ( source == null )
         {
             if ( other.source != null )
@@ -157,7 +179,7 @@
         {
             return false;
         }
-        else if ( Math.round( 100.0 * leftsideValue ) != Math.round( 100 * other.leftsideValue ) )
+        else if ( Math.round( 100.0 * leftsideValue ) != Math.round( 100.0 * other.leftsideValue ) )
         {
             return false;
         }
@@ -173,7 +195,7 @@
         {
             return false;
         }
-        else if ( Math.round( 100.0 * leftsideValue ) != Math.round( 100 * other.leftsideValue ) )
+        else if ( Math.round( 100.0 * leftsideValue ) != Math.round( 100.0 * other.leftsideValue ) )
         {
             return false;
         }
@@ -181,14 +203,19 @@
         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.
+    //
     public int compareTo( ValidationResult other )
     {
     	int result = source.getName().compareTo( other.source.getName() );
     	
     	if ( result != 0 )
-	{
+    	{
     	    return result;
-	}
+    	}
     	
     	result = period.getStartDate().compareTo( other.period.getStartDate() );
     	
@@ -204,6 +231,13 @@
     	    return result;
     	}
 
+        result = attributeOptionCombo.getId() - other.attributeOptionCombo.getId();
+
+        if ( result != 0 )
+        {
+            return result;
+        }
+
     	result = validationImportanceOrder( validationRule.getImportance() ) - validationImportanceOrder( other.validationRule.getImportance() );
     	
     	if ( result != 0 )
@@ -257,7 +291,7 @@
     @Override
     public String toString()
     {
-        return source + " - " + period + " - " + validationRule + " - " + leftsideValue + " - " + rightsideValue;
+        return source + " - " + period + " - " + attributeOptionCombo.getName() + " - " + validationRule + " - " + leftsideValue + " - " + rightsideValue;
     }
 
     // -------------------------------------------------------------------------
@@ -284,6 +318,16 @@
         this.period = period;
     }
 
+    public DataElementCategoryOptionCombo getAttributeOptionCombo()
+    {
+        return attributeOptionCombo;
+    }
+
+    public void setAttributeOptionCombo( DataElementCategoryOptionCombo attributeOptionCombo )
+    {
+        this.attributeOptionCombo = attributeOptionCombo;
+    }
+
     public ValidationRule getValidationRule()
     {
         return validationRule;

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleService.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleService.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleService.java	2014-05-18 00:49:40 +0000
@@ -32,6 +32,7 @@
 import java.util.Date;
 
 import org.hisp.dhis.dataelement.DataElement;
+import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
 import org.hisp.dhis.dataset.DataSet;
 import org.hisp.dhis.i18n.I18nFormat;
 import org.hisp.dhis.organisationunit.OrganisationUnit;
@@ -55,37 +56,27 @@
     /**
      * Validate DataValues.
      *
-     * @param startDate  the start date.
-     * @param endDate    the end date.
-     * @param sources    a collection of Sources.
-     * @param sendAlerts whether to send alerts for surveillance.
-     * @param format     the i18n format.
-     * @return a collection of ValidationResults for each validation violation.
-     */
-    Collection<ValidationResult> validate( Date startDate, Date endDate, Collection<OrganisationUnit> sources, boolean sendAlerts, I18nFormat format );
-
-    /**
-     * Validate DataValues.
-     *
-     * @param startDate  the start date.
-     * @param endDate    the end date.
-     * @param sources    a collection of Sources.
-     * @param group      a group of ValidationRules.
-     * @param sendAlerts whether to send alerts for surveillance.
-     * @param format     the i18n format.
-     * @return a collection of ValidationResults for each validation violation.
-     */
-    Collection<ValidationResult> validate( Date startDate, Date endDate, Collection<OrganisationUnit> sources, ValidationRuleGroup group, boolean sendAlerts, I18nFormat format );
+     * @param startDate the start date.
+     * @param endDate the end date.
+     * @param sources a collection of Sources.
+     * @param attributeCombo attribute category option combo (null for all).
+     * @param group validation rule group (null for all validationRules).
+     * @param sendAlerts whether to send alerts for surveillance.
+     * @param format the i18n format.
+     * @return a collection of ValidationResults for each validation violation.
+     */
+    Collection<ValidationResult> validate( Date startDate, Date endDate, Collection<OrganisationUnit> sources, DataElementCategoryOptionCombo attributeCombo, ValidationRuleGroup group, boolean sendAlerts, I18nFormat format );
 
     /**
      * Validate DataValues.
      *
      * @param dataSet the DataSet.
-     * @param period  the Period.
-     * @param source  the Source.
+     * @param period the Period.
+     * @param source the Organisation unit.
+     * @param attributeCombo attribute category option combo (null for all).
      * @return a collection of ValidationResults for each validation violation.
      */
-    Collection<ValidationResult> validate( DataSet dataSet, Period period, OrganisationUnit source );
+    Collection<ValidationResult> validate( DataSet dataSet, Period period, OrganisationUnit source, DataElementCategoryOptionCombo attributeCombo );
 
     /**
      * Validate DataValues.

=== modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DataQueryParams.java'
--- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DataQueryParams.java	2014-04-30 17:30:27 +0000
+++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DataQueryParams.java	2014-05-18 00:49:40 +0000
@@ -75,7 +75,7 @@
 import org.hisp.dhis.period.PeriodType;
 import org.hisp.dhis.system.util.CollectionUtils;
 import org.hisp.dhis.system.util.ListUtils;
-import org.hisp.dhis.system.util.MapMap;
+import org.hisp.dhis.common.MapMap;
 import org.hisp.dhis.system.util.MathUtils;
 
 /**

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.java	2014-04-17 14:19:13 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.java	2014-05-18 00:49:40 +0000
@@ -37,6 +37,7 @@
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.hisp.dhis.common.MapMap;
 import org.hisp.dhis.dataelement.DataElement;
 import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
 import org.hisp.dhis.dataelement.DataElementCategoryService;
@@ -241,15 +242,11 @@
         return dataValueStore.getDataValueCount( cal.getTime() );
     }
     
-    public Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit source )
-    {
-        return dataValueStore.getDataValueMap( dataElements, period, source );
-    }
-    
-    public Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Date date, OrganisationUnit source,
-    		Collection<PeriodType> periodTypes, Map<DataElementOperand, Date> lastUpdatedMap )
-    {
-    	return dataValueStore.getDataValueMap( dataElements, date, source, periodTypes, lastUpdatedMap );
+    public MapMap<Integer, DataElementOperand, Double> getDataValueMapByAttributeCombo( Collection<DataElement> dataElements, Date date,
+            OrganisationUnit source, Collection<PeriodType> periodTypes, DataElementCategoryOptionCombo attributeCombo,
+            MapMap<Integer, DataElementOperand, Date> lastUpdatedMap )
+    {
+        return dataValueStore.getDataValueMapByAttributeCombo( dataElements, date, source, periodTypes, attributeCombo, lastUpdatedMap );
     }
     
     public Collection<DeflatedDataValue> getDeflatedDataValues( int dataElementId, int periodId, Collection<Integer> sourceIds )

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueStore.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueStore.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueStore.java	2014-05-18 00:49:40 +0000
@@ -28,15 +28,14 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+import static org.hisp.dhis.system.util.ConversionUtils.getIdentifiers;
 import static org.hisp.dhis.system.util.TextUtils.getCommaDelimitedString;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Map;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -47,7 +46,10 @@
 import org.hibernate.criterion.Projections;
 import org.hibernate.criterion.Restrictions;
 import org.hisp.dhis.dataelement.DataElement;
+import org.hisp.dhis.dataelement.DataElementCategoryCombo;
+import org.hisp.dhis.dataelement.DataElementCategoryOption;
 import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
+import org.hisp.dhis.dataelement.DataElementCategoryService;
 import org.hisp.dhis.dataelement.DataElementOperand;
 import org.hisp.dhis.datavalue.DataValue;
 import org.hisp.dhis.datavalue.DataValueStore;
@@ -60,6 +62,7 @@
 import org.hisp.dhis.system.objectmapper.DeflatedDataValueRowMapper;
 import org.hisp.dhis.system.util.ConversionUtils;
 import org.hisp.dhis.system.util.DateUtils;
+import org.hisp.dhis.common.MapMap;
 import org.hisp.dhis.system.util.MathUtils;
 import org.hisp.dhis.system.util.TextUtils;
 import org.springframework.dao.EmptyResultDataAccessException;
@@ -99,6 +102,13 @@
         this.jdbcTemplate = jdbcTemplate;
     }
 
+    private DataElementCategoryService dataElementCategoryService;
+
+    public void setDataElementCategoryService( DataElementCategoryService dataElementCategoryService )
+    {
+        this.dataElementCategoryService = dataElementCategoryService;
+    }
+
     // -------------------------------------------------------------------------
     // Basic DataValue
     // -------------------------------------------------------------------------
@@ -459,76 +469,47 @@
 
         return rs != null ? rs.intValue() : 0;
     }
-    
-    public Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit source )
-    {
-        Map<DataElementOperand, Double> map = new HashMap<DataElementOperand, Double>();
-        
-        if ( dataElements.isEmpty() )
-        {
-            return map;
-        }
-        
-        final String sql = 
-            "select de.uid, coc.uid, dv.value " +
-            "from datavalue dv " +
-            "join dataelement de on dv.dataelementid = de.dataelementid " +
-            "join categoryoptioncombo coc on dv.categoryoptioncomboid = coc.categoryoptioncomboid " +
-            "where dv.dataelementid in (" + TextUtils.getCommaDelimitedString( ConversionUtils.getIdentifiers( DataElement.class, dataElements ) ) + ") " +
-            "and dv.periodid = " + period.getId() + " " +
-            "and dv.sourceid = " + source.getId();
-        
-        SqlRowSet rowSet = jdbcTemplate.queryForRowSet( sql );
-        
-        while ( rowSet.next() )
-        {
-            String dataElement = rowSet.getString( 1 );
-            String categoryOptionCombo = rowSet.getString( 2 );
-            Double value = MathUtils.parseDouble( rowSet.getString( 3 ) );
-            
-            if ( value != null )
-            {
-                map.put( new DataElementOperand( dataElement, categoryOptionCombo ), value );
-            }
-        }
-        
-        return map; 
-    }
-
-    public Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Date date, OrganisationUnit source,
-    		Collection<PeriodType> periodTypes, Map<DataElementOperand, Date> lastUpdatedMap )
-    {
-        Map<DataElementOperand, Double> map = new HashMap<DataElementOperand, Double>();
-        
+
+    public MapMap<Integer, DataElementOperand, Double> getDataValueMapByAttributeCombo( Collection<DataElement> dataElements, Date date, OrganisationUnit source,
+        Collection<PeriodType> periodTypes, DataElementCategoryOptionCombo attributeCombo, MapMap<Integer, DataElementOperand, Date> lastUpdatedMap )
+    {
+        MapMap<Integer, DataElementOperand, Double> map = new MapMap<Integer, DataElementOperand, Double>();
+
         if ( dataElements.isEmpty() || periodTypes.isEmpty() )
         {
             return map;
         }
-        
-        final String sql = 
-            "select de.uid, coc.uid, dv.value, dv.lastupdated, p.startdate, p.enddate " +
-            "from datavalue dv " +
-            "join dataelement de on dv.dataelementid = de.dataelementid " +
-            "join categoryoptioncombo coc on dv.categoryoptioncomboid = coc.categoryoptioncomboid " +
-            "join period p on p.periodid = dv.periodid " +
-            "where dv.dataelementid in (" + TextUtils.getCommaDelimitedString( ConversionUtils.getIdentifiers( DataElement.class, dataElements ) ) + ") " +
-            "and dv.sourceid = " + source.getId() + " " +
-            "and p.startdate <= '" + DateUtils.getMediumDateString( date ) + "' " +
-            "and p.enddate >= '" + DateUtils.getMediumDateString( date ) + "' " +
-        	"and p.periodtypeid in (" + TextUtils.getCommaDelimitedString( ConversionUtils.getIdentifiers( PeriodType.class, periodTypes ) ) + ") ";
+
+        String sql =
+                "select de.uid, coc.uid, dv.attributeoptioncomboid, dv.value, dv.lastupdated, p.startdate, p.enddate " +
+                        "from datavalue dv " +
+                        "join dataelement de on dv.dataelementid = de.dataelementid " +
+                        "join categoryoptioncombo coc on dv.categoryoptioncomboid = coc.categoryoptioncomboid " +
+                        "join period p on p.periodid = dv.periodid " +
+                        "where dv.dataelementid in (" + TextUtils.getCommaDelimitedString( ConversionUtils.getIdentifiers( DataElement.class, dataElements ) ) + ") " +
+                        "and dv.sourceid = " + source.getId() + " " +
+                        "and p.startdate <= '" + DateUtils.getMediumDateString( date ) + "' " +
+                        "and p.enddate >= '" + DateUtils.getMediumDateString( date ) + "' " +
+                        "and p.periodtypeid in (" + TextUtils.getCommaDelimitedString( ConversionUtils.getIdentifiers( PeriodType.class, periodTypes ) ) + ") ";
+
+        if ( attributeCombo != null )
+        {
+            sql += " and dv.attributeoptioncomboid = " + attributeCombo.getId();
+        }
 
         SqlRowSet rowSet = jdbcTemplate.queryForRowSet( sql );
-        
-        Map<DataElementOperand, Long> checkForDuplicates = new HashMap<DataElementOperand, Long>();
+
+        MapMap<Integer, DataElementOperand, Long> checkForDuplicates = new MapMap<Integer, DataElementOperand, Long>();
 
         while ( rowSet.next() )
         {
             String dataElement = rowSet.getString( 1 );
             String categoryOptionCombo = rowSet.getString( 2 );
-            Double value = MathUtils.parseDouble( rowSet.getString( 3 ) );
-            Date lastUpdated = rowSet.getDate( 4 );
-            Date periodStartDate = rowSet.getDate( 5 );
-            Date periodEndDate = rowSet.getDate( 6 );
+            Integer attributeOptionComboId = rowSet.getInt( 3 );
+            Double value = MathUtils.parseDouble( rowSet.getString( 4 ) );
+            Date lastUpdated = rowSet.getDate( 5 );
+            Date periodStartDate = rowSet.getDate( 6 );
+            Date periodEndDate = rowSet.getDate( 7 );
             long periodInterval = periodEndDate.getTime() - periodStartDate.getTime();
 
             log.trace( "row: " + dataElement + " = " + value + " [" + periodStartDate + " : " + periodEndDate + "]");
@@ -536,22 +517,22 @@
             if ( value != null )
             {
                 DataElementOperand dataElementOperand = new DataElementOperand( dataElement, categoryOptionCombo );
-                Long existingPeriodInterval = checkForDuplicates.get( dataElementOperand );
-                
+
+                Long existingPeriodInterval = checkForDuplicates.getValue( attributeOptionComboId, dataElementOperand );
+
                 if ( existingPeriodInterval != null && existingPeriodInterval < periodInterval )
                 {
                     // Don't overwrite the previous value if for a shorter interval
-                    continue; 
+                    continue;
                 }
-                
-                map.put( dataElementOperand, value );
-                
+                map.putEntry( attributeOptionComboId, dataElementOperand, value );
+
                 if ( lastUpdatedMap != null )
                 {
-                    lastUpdatedMap.put( dataElementOperand, lastUpdated );
+                    lastUpdatedMap.putEntry( attributeOptionComboId, dataElementOperand, lastUpdated );
                 }
-                
-                checkForDuplicates.put( dataElementOperand, periodInterval );
+
+                checkForDuplicates.putEntry( attributeOptionComboId, dataElementOperand, periodInterval );
             }
         }
 

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java	2014-03-31 10:24:23 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java	2014-05-18 00:49:40 +0000
@@ -51,6 +51,8 @@
 import org.hisp.dhis.common.GenericIdentifiableObjectStore;
 import org.hisp.dhis.constant.ConstantService;
 import org.hisp.dhis.dataelement.DataElement;
+import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
+import org.hisp.dhis.dataelement.DataElementCategoryService;
 import org.hisp.dhis.dataelement.DataElementOperand;
 import org.hisp.dhis.dataentryform.DataEntryFormService;
 import org.hisp.dhis.dataset.DataSet;
@@ -129,6 +131,13 @@
         this.dataValueService = dataValueService;
     }
 
+    private DataElementCategoryService dataElementCategoryService;
+
+    public void setDataElementCategoryService( DataElementCategoryService dataElementCategoryService )
+    {
+        this.dataElementCategoryService = dataElementCategoryService;
+    }
+
     private ConstantService constantService;
 
     public void setConstantService( ConstantService constantService )
@@ -169,22 +178,16 @@
     // -------------------------------------------------------------------------
 
     @Override
-    public Collection<ValidationResult> validate( Date startDate, Date endDate, Collection<OrganisationUnit> sources, boolean sendAlerts, I18nFormat format )
-    {
-        return validate( startDate, endDate, sources, null, sendAlerts, format );
-    }
-
-    @Override
     public Collection<ValidationResult> validate( Date startDate, Date endDate, Collection<OrganisationUnit> sources,
-        ValidationRuleGroup group, boolean sendAlerts, I18nFormat format )
+        DataElementCategoryOptionCombo attributeCombo, ValidationRuleGroup group, boolean sendAlerts, I18nFormat format )
     {
     	log.info( "Validate start:" + startDate + " end: " + endDate + " sources: " + sources.size() + " group: " + group );
     	
         Collection<Period> periods = periodService.getPeriodsBetweenDates( startDate, endDate );
         Collection<ValidationRule> rules = group != null ? group.getMembers() : getAllValidationRules();
         
-        Collection<ValidationResult> results = Validator.validate( sources, periods, rules, null,
-            constantService, expressionService, periodService, dataValueService );
+        Collection<ValidationResult> results = Validator.validate( sources, periods, rules, attributeCombo, null,
+            constantService, expressionService, periodService, dataValueService, dataElementCategoryService );
         
         formatPeriods( results, format );
         
@@ -207,17 +210,19 @@
         Collection<ValidationRule> rules = getAllValidationRules();
         Collection<OrganisationUnit> sources = new HashSet<OrganisationUnit>();
         sources.add( source );
-        
-        return Validator.validate( sources, periods, rules, null,
-            constantService, expressionService, periodService, dataValueService );
+
+        return Validator.validate( sources, periods, rules, null, null,
+            constantService, expressionService, periodService, dataValueService, dataElementCategoryService );
     }
 
     @Override
-    public Collection<ValidationResult> validate( DataSet dataSet, Period period, OrganisationUnit source )
+    public Collection<ValidationResult> validate( DataSet dataSet, Period period, OrganisationUnit source,
+        DataElementCategoryOptionCombo attributeCombo )
     {
     	log.info( "Validate data set: " + dataSet.getName() + " period: " + period.getPeriodType().getName() + " "
-            + period.getStartDate() + " " + period.getEndDate() + " source: " + source.getName() );
-    	
+            + period.getStartDate() + " " + period.getEndDate() + " source: " + source.getName()
+            + " attribute combo: " + ( attributeCombo == null ? "(null)" : attributeCombo.getName() ) );
+
         Collection<Period> periods = new ArrayList<Period>();
         periods.add( period );
 
@@ -237,8 +242,8 @@
         Collection<OrganisationUnit> sources = new HashSet<OrganisationUnit>();
         sources.add( source );
         
-        return Validator.validate( sources, periods, rules, null,
-            constantService, expressionService, periodService, dataValueService );
+        return Validator.validate( sources, periods, rules, attributeCombo, null,
+            constantService, expressionService, periodService, dataValueService, dataElementCategoryService );
     }
 
     @Override
@@ -263,8 +268,8 @@
         log.info( "Scheduled monitoring run sources: " + sources.size() + ", periods: " + periods.size() + ", rules:" + rules.size()
             + ", last run: " + ( lastScheduledRun == null ? "[none]" : lastScheduledRun ) );
         
-        Collection<ValidationResult> results = Validator.validate( sources, periods, rules,
-            lastScheduledRun, constantService, expressionService, periodService, dataValueService );
+        Collection<ValidationResult> results = Validator.validate( sources, periods, rules, null, lastScheduledRun,
+            constantService, expressionService, periodService, dataValueService, dataElementCategoryService );
         
         log.info( "Validation run result count: " + results.size() );
         
@@ -505,7 +510,8 @@
         {
             ValidationRule rule = result.getValidationRule();
             
-            builder.append( result.getSource().getName() ).append( " " ).append( result.getPeriod().getName() ).append( LN ).
+            builder.append( result.getSource().getName() ).append( " " ).append( result.getPeriod().getName() ).
+            append( result.getAttributeOptionCombo().isDefault() ? "" : " " + result.getAttributeOptionCombo().getName() ).append( LN ).
             append( rule.getName() ).append( " (" ).append( rule.getImportance() ).append( ") " ).append( LN ).
             append( rule.getLeftSide().getDescription() ).append( ": " ).append( result.getLeftsideValue() ).append( LN ).
             append( rule.getRightSide().getDescription() ).append( ": " ).append( result.getRightsideValue() ).append( LN ).append( LN );

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/OrganisationUnitExtended.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/OrganisationUnitExtended.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/OrganisationUnitExtended.java	2014-05-18 00:49:40 +0000
@@ -37,7 +37,7 @@
 
 /**
  * Holds information for each organisation unit that is needed during a
- * validation run (either interactive or an alert run).
+ * validation run (either interactive or a scheduled run).
  * 
  * It is important that they should be copied from Hibernate lazy collections
  * before the multithreaded part of the run starts, otherwise the threads may

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/PeriodTypeExtended.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/PeriodTypeExtended.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/PeriodTypeExtended.java	2014-05-18 00:49:40 +0000
@@ -43,7 +43,7 @@
 
 /**
  * Holds information for each period type that is needed during
- * a validation run (either interactive or an alert run).
+ * a validation run (either interactive or a scheduled run).
  * 
  * By computing these values once at the start of a validation run, we avoid
  * the overhead of having to compute them during the processing of every

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRuleExtended.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRuleExtended.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRuleExtended.java	2014-05-18 00:49:40 +0000
@@ -34,7 +34,7 @@
 
 /**
  * Holds information for each validation rule that is needed during a validation
- * run (either interactive or an alert run).
+ * run (either interactive or a scheduled run).
  * 
  * By computing these values once at the start of a validation run, we avoid the
  * overhead of having to compute them during the processing of every

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRunContext.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRunContext.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRunContext.java	2014-05-18 00:49:40 +0000
@@ -42,6 +42,8 @@
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.hisp.dhis.dataelement.DataElement;
+import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
+import org.hisp.dhis.dataelement.DataElementCategoryService;
 import org.hisp.dhis.dataset.DataSet;
 import org.hisp.dhis.datavalue.DataValueService;
 import org.hisp.dhis.expression.ExpressionService;
@@ -81,6 +83,8 @@
     private Map<ValidationRule, ValidationRuleExtended> ruleXMap;
 
     private Collection<OrganisationUnitExtended> sourceXs;
+
+    private DataElementCategoryOptionCombo attributeCombo;
     
     private int countOfSourcesToValidate;
 
@@ -92,6 +96,8 @@
 
     private DataValueService dataValueService;
 
+    private DataElementCategoryService dataElementCategoryService;
+
     private ValidationRunContext()
     {
     }
@@ -113,15 +119,17 @@
      * 
      * @param sources organisation units for validation
      * @param periods periods for validation
+     * @param attributeCombo the attribute combo to check (if restricted)
      * @param rules validation rules for validation
      * @param runType whether this is an INTERACTIVE or SCHEDULED run
      * @param lastScheduledRun (for SCHEDULED runs) date/time of previous run
      * @return context object for this run
      */
     public static ValidationRunContext getNewValidationRunContext( Collection<OrganisationUnit> sources,
-        Collection<Period> periods, Collection<ValidationRule> rules, Map<String, Double> constantMap,
-        ValidationRunType runType, Date lastScheduledRun, ExpressionService expressionService, PeriodService periodService,
-        DataValueService dataValueService )
+        Collection<Period> periods, DataElementCategoryOptionCombo attributeCombo, Collection<ValidationRule> rules,
+        Map<String, Double> constantMap, ValidationRunType runType, Date lastScheduledRun,
+        ExpressionService expressionService, PeriodService periodService,
+        DataValueService dataValueService, DataElementCategoryService dataElementCategoryService )
     {
         ValidationRunContext context = new ValidationRunContext();
         context.runType = runType;
@@ -134,6 +142,8 @@
         context.expressionService = expressionService;
         context.periodService = periodService;
         context.dataValueService = dataValueService;
+        context.dataElementCategoryService = dataElementCategoryService;
+        context.attributeCombo = attributeCombo;
         context.initialize( sources, periods, rules );
         return context;
     }
@@ -328,7 +338,6 @@
      * creates a new PeriodTypeExtended object, puts it into the context object,
      * and returns it.
      * 
-     * @param context validation run context
      * @param periodType period type to search for
      * @return period type extended from the context object
      */
@@ -405,6 +414,11 @@
         return sourceXs;
     }
 
+    public DataElementCategoryOptionCombo getAttributeCombo()
+    {
+        return attributeCombo;
+    }
+
     public int getCountOfSourcesToValidate()
     {
         return countOfSourcesToValidate;
@@ -429,4 +443,9 @@
     {
         return dataValueService;
     }
+
+    public DataElementCategoryService getDataElementCategoryService()
+    {
+        return dataElementCategoryService;
+    }
 }

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/Validator.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/Validator.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/Validator.java	2014-05-18 00:49:40 +0000
@@ -35,6 +35,9 @@
 import java.util.concurrent.TimeUnit;
 
 import org.hisp.dhis.constant.ConstantService;
+import org.hisp.dhis.dataelement.DataElementCategoryOption;
+import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
+import org.hisp.dhis.dataelement.DataElementCategoryService;
 import org.hisp.dhis.datavalue.DataValueService;
 import org.hisp.dhis.expression.ExpressionService;
 import org.hisp.dhis.organisationunit.OrganisationUnit;
@@ -57,23 +60,25 @@
      * 
      * @param sources the organisation units in which to run the validation rules
      * @param periods the periods of data to check
+     * @param attributeCombo the attribute combo to check (if restricted)
      * @param rules the ValidationRules to evaluate
-     * @param runType whether this is an INTERACTIVE or SCHEDULED run
      * @param lastScheduledRun date/time of the most recent successful
      *        scheduled monitoring run (needed only for scheduled runs)
      * @param constantService Constant Service reference
      * @param expressionService Expression Service reference
      * @param periodService Period Service reference
      * @param dataValueService Data Value Service reference
+     * @param dataElementCategoryService Data Element Category Service reference
      * @return a collection of any validations that were found
      */
-    public static Collection<ValidationResult> validate( Collection<OrganisationUnit> sources,
-        Collection<Period> periods, Collection<ValidationRule> rules, Date lastScheduledRun,
-        ConstantService constantService, ExpressionService expressionService, PeriodService periodService, DataValueService dataValueService )
+    public static Collection<ValidationResult> validate( Collection<OrganisationUnit> sources, Collection<Period> periods,
+        Collection<ValidationRule> rules, DataElementCategoryOptionCombo attributeCombo, Date lastScheduledRun,
+        ConstantService constantService, ExpressionService expressionService, PeriodService periodService,
+        DataValueService dataValueService, DataElementCategoryService dataElementCategoryService )
     {
-        ValidationRunContext context = ValidationRunContext.getNewValidationRunContext( sources, periods, rules,
-            constantService.getConstantMap(), ValidationRunType.SCHEDULED, lastScheduledRun,
-            expressionService, periodService, dataValueService );
+        ValidationRunContext context = ValidationRunContext.getNewValidationRunContext( sources, periods,
+            attributeCombo, rules, constantService.getConstantMap(), ValidationRunType.SCHEDULED, lastScheduledRun,
+            expressionService, periodService, dataValueService, dataElementCategoryService );
 
         int threadPoolSize = getThreadPoolSize( context );
         ExecutorService executor = Executors.newFixedThreadPool( threadPoolSize );
@@ -97,7 +102,9 @@
         {
             executor.shutdownNow();
         }
-        
+
+        reloadAttributeOptionCombos( context.getValidationResults(), dataElementCategoryService );
+
         return context.getValidationResults();
     }
     
@@ -123,4 +130,18 @@
 
     	return threadPoolSize;
     }
+
+    /**
+     * Reload attribute category option combos into this Hibernate context.
+     *
+     * @param results
+     * @param dataElementCategoryService
+     */
+    private static void reloadAttributeOptionCombos( Collection<ValidationResult> results, DataElementCategoryService dataElementCategoryService )
+    {
+        for ( ValidationResult result : results )
+        {
+            result.setAttributeOptionCombo( dataElementCategoryService.getDataElementCategoryOptionCombo( result.getAttributeOptionCombo().getId() ) );
+        }
+    }
 }

=== 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-03-18 08:10:10 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidatorThread.java	2014-05-18 00:49:40 +0000
@@ -32,7 +32,6 @@
 import static org.hisp.dhis.system.util.MathUtils.roundSignificant;
 import static org.hisp.dhis.system.util.MathUtils.zeroIfNull;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Collection;
@@ -46,8 +45,12 @@
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.hisp.dhis.common.ListMap;
+import org.hisp.dhis.common.MapMap;
+import org.hisp.dhis.common.SetMap;
 import org.hisp.dhis.dataelement.DataElement;
 import org.hisp.dhis.dataelement.DataElementOperand;
+import org.hisp.dhis.expression.Expression;
 import org.hisp.dhis.expression.Operator;
 import org.hisp.dhis.organisationunit.OrganisationUnit;
 import org.hisp.dhis.period.CalendarPeriodType;
@@ -76,84 +79,88 @@
         this.context = context;
     }
 
+    /**
+     * Evaluates validation rules for a single organisation unit. This is the
+     * central method in validation rule evaluation.
+     */
     @Override
     public void run()
     {
-        validateSource( sourceX, context );
-    }
-    
-    /**
-     * Evaluates validation rules for a single organisation unit. This is the
-     * central method in validation rule evaluation.
-     * 
-     * @param sourceX extended object of the organisation unit in which to run
-     *        the validation rules
-     * @param context the validation run context
-     */
-    private void validateSource( OrganisationUnitExtended sourceX, ValidationRunContext context )
-    {
         if ( context.getValidationResults().size() < ( ValidationRunType.INTERACTIVE == context.getRunType() ?
             ValidationRuleService.MAX_INTERACTIVE_ALERTS : ValidationRuleService.MAX_SCHEDULED_ALERTS) )
         {
             for ( PeriodTypeExtended periodTypeX : context.getPeriodTypeExtendedMap().values() )
             {
                 Collection<DataElement> sourceDataElements = periodTypeX.getSourceDataElements().get( sourceX.getSource() );
-                Set<ValidationRule> rules = getRulesBySourceAndPeriodType( sourceX, periodTypeX, context,
-                    sourceDataElements );
+                Set<ValidationRule> rules = getRulesBySourceAndPeriodType( sourceX, periodTypeX, sourceDataElements );
 
                 if ( !rules.isEmpty() )
                 {
                     Set<DataElement> recursiveCurrentDataElements = getRecursiveCurrentDataElements( rules );
                     for ( Period period : periodTypeX.getPeriods() )
                     {
-                        Map<DataElementOperand, Date> lastUpdatedMap = new HashMap<DataElementOperand, Date>();
-                        Set<DataElementOperand> incompleteValues = new HashSet<DataElementOperand>();
-                        Map<DataElementOperand, Double> currentValueMap = getDataValueMapRecursive( periodTypeX,
-                            periodTypeX.getDataElements(), sourceDataElements, recursiveCurrentDataElements,
-                            periodTypeX.getAllowedPeriodTypes(), period, sourceX.getSource(), lastUpdatedMap, incompleteValues );
+                        MapMap<Integer, DataElementOperand, Date> lastUpdatedMap2 = new MapMap<Integer, DataElementOperand, Date>();
+                        SetMap<Integer, DataElementOperand> incompleteValuesMap2 = new SetMap<Integer, DataElementOperand>();
+                        MapMap<Integer, DataElementOperand, Double> currentValueMap2 = getValueMap2( periodTypeX,
+                                periodTypeX.getDataElements(), sourceDataElements, recursiveCurrentDataElements,
+                                periodTypeX.getAllowedPeriodTypes(), period, sourceX.getSource(), lastUpdatedMap2, incompleteValuesMap2 );
                         
                         log.trace( "Source " + sourceX.getSource().getName()
                             + " [" + period.getStartDate() + " - " + period.getEndDate() + "]"
-                            + " valueMap[" + currentValueMap.size() + "]" );
+                            + " currentValueMap2[" + currentValueMap2.size() + "]" );
 
                         for ( ValidationRule rule : rules )
                         {
-                            if ( evaluateCheck( lastUpdatedMap, rule, context ) )
+                            if ( evaluateCheck( currentValueMap2, lastUpdatedMap2, rule ) )
                             {
-                                Double leftSide = context.getExpressionService().getExpressionValue( rule.getLeftSide(),
-                                    currentValueMap, context.getConstantMap(), null, null, incompleteValues );
+                                Map<Integer, Double> leftSideValues = getExpressionValueMap( rule.getLeftSide(),
+                                        currentValueMap2, incompleteValuesMap2 );
 
-                                if ( leftSide != null || Operator.compulsory_pair.equals( rule.getOperator() ) )
+                                if ( !leftSideValues.isEmpty() || Operator.compulsory_pair.equals( rule.getOperator() ) )
                                 {
-                                    Double rightSide = getRightSideValue( sourceX.getSource(), periodTypeX, period, rule,
-                                        currentValueMap, sourceDataElements, context );
+                                    Map<Integer, Double> rightSideValues = getRightSideValue( sourceX.getSource(), periodTypeX, period, rule,
+                                            currentValueMap2, sourceDataElements );
 
-                                    if ( rightSide != null || Operator.compulsory_pair.equals( rule.getOperator() ) )
+                                    if ( !rightSideValues.isEmpty() || Operator.compulsory_pair.equals( rule.getOperator() ) )
                                     {
-                                        boolean violation = false;
-
+                                        Set<Integer> combos = leftSideValues.keySet();
                                         if ( Operator.compulsory_pair.equals( rule.getOperator() ) )
                                         {
-                                            violation = (leftSide != null && rightSide == null)
-                                                || (leftSide == null && rightSide != null);
-                                        }
-                                        else if ( leftSide != null && rightSide != null )
-                                        {
-                                            violation = !expressionIsTrue( leftSide, rule.getOperator(), rightSide );
-                                        }
-
-                                        if ( violation )
-                                        {
-                                            context.getValidationResults().add( new ValidationResult(
-                                            	period, sourceX.getSource(), rule,
-                                            	roundSignificant( zeroIfNull( leftSide ) ),
-                                            	roundSignificant( zeroIfNull( rightSide ) ) ) );
-                                        }
-
-                                        log.trace( "-->Evaluated " + rule.getName() + ": "
-                                            + (violation ? "violation" : "OK") + " " + leftSide.toString() + " "
-                                            + rule.getOperator() + " " + rightSide.toString() + " ("
-                                            + context.getValidationResults().size() + " results)" );
+                                            combos = new HashSet<Integer>( combos );
+                                            combos.addAll( rightSideValues.keySet() );
+                                        }
+
+                                        for ( int combo : combos )
+                                        {
+                                            Double leftSide = leftSideValues.get ( combo );
+                                            Double rightSide = rightSideValues.get ( combo );
+                                            boolean violation = false;
+
+                                            if ( Operator.compulsory_pair.equals( rule.getOperator() ) )
+                                            {
+                                                violation = (leftSide != null && rightSide == null)
+                                                    || (leftSide == null && rightSide != null);
+                                            }
+                                            else if ( leftSide != null && rightSide != null )
+                                            {
+                                                violation = !expressionIsTrue( leftSide, rule.getOperator(), rightSide );
+                                            }
+
+                                            if ( violation )
+                                            {
+                                                context.getValidationResults().add( new ValidationResult(
+                                                    period, sourceX.getSource(),
+                                                    context.getDataElementCategoryService().getDataElementCategoryOptionCombo( combo ), rule,
+                                                    roundSignificant( zeroIfNull( leftSide ) ),
+                                                    roundSignificant( zeroIfNull( rightSide ) ) ) );
+                                            }
+
+                                            log.trace( "-->Evaluated " + rule.getName()
+                                                + ", combo id " + combo + ": "
+                                                + (violation ? "violation" : "OK") + " " + ( leftSide == null ? "(null)" : leftSide.toString() )
+                                                + " " + rule.getOperator() + " " + ( rightSide == null ? "(null)" : rightSide.toString() )
+                                                + " (" + context.getValidationResults().size() + " results)" );
+                                        }
                                     }
                                 }
                             }
@@ -170,13 +177,12 @@
      * 
      * @param sourceX the organisation unit extended information
      * @param periodTypeX the period type extended information
-     * @param context the validation run context
      * @param sourceDataElements all data elements collected for this
      *        organisation unit
-     * @return
+     * @return set of rules for this org unit and period type
      */
     private Set<ValidationRule> getRulesBySourceAndPeriodType( OrganisationUnitExtended sourceX,
-        PeriodTypeExtended periodTypeX, ValidationRunContext context, Collection<DataElement> sourceDataElements )
+        PeriodTypeExtended periodTypeX, Collection<DataElement> sourceDataElements )
     {
         Set<ValidationRule> periodTypeRules = new HashSet<ValidationRule>();
 
@@ -210,22 +216,24 @@
         return periodTypeRules;
     }
 
-
     /**
      * Checks to see if the evaluation should go further for this
      * evaluationRule, after the "current" data to evaluate has been fetched.
      * For INTERACTIVE runs, we always go further (always return true.) For
      * SCHEDULED runs, we go further only if something has changed since the
      * last successful scheduled run -- either the rule definition or one of
-     * the "current" data element / option values.
-     * 
-     * @param lastUpdatedMap when each data value was last updated
+     * the "current" data element / option values on the left or right sides.
+     *
+     * For scheduled runs, remove all values for any attribute option combos
+     * where nothing has changed since the last run.
+     *
+     * @param lastUpdatedMapMap when each data value was last updated
      * @param rule the rule that may be evaluated
-     * @param context the evaluation run context
      * @return true if the rule should be evaluated with this data, false if not
      */
-    private boolean evaluateCheck( Map<DataElementOperand, Date> lastUpdatedMap, ValidationRule rule,
-        ValidationRunContext context )
+    private boolean evaluateCheck( MapMap<Integer, DataElementOperand, Double> currentValueMapMap,
+                                   MapMap<Integer, DataElementOperand, Date> lastUpdatedMapMap,
+                                   ValidationRule rule )
     {
         boolean evaluate = true; // Assume true for now.
 
@@ -237,7 +245,7 @@
                 {
                     // Get the "current" DataElementOperands from this rule:
                     // Left+Right sides for VALIDATION, Left side only for
-                    // SURVEILLANCE
+                    // SURVEILLANCE.
                     Collection<DataElementOperand> deos = context.getExpressionService().getOperandsInExpression(
                         rule.getLeftSide().getExpression() );
                     
@@ -251,13 +259,25 @@
                     // Return true if any data is more recent than the last
                     // scheduled run, otherwise return false.
                     evaluate = false;
-                    for ( DataElementOperand deo : deos )
+
+                    for ( Map.Entry<Integer, Map<DataElementOperand, Date>> entry : lastUpdatedMapMap.entrySet() )
                     {
-                        Date lastUpdated = lastUpdatedMap.get( deo );
-                        if ( lastUpdated != null && lastUpdated.after( context.getLastScheduledRun() ) )
-                        {
-                            evaluate = true; // True if new/updated data.
-                            break;
+                        boolean saveThisCombo = false;
+
+                        for ( DataElementOperand deo : deos )
+                        {
+                            Date lastUpdated = entry.getValue().get( deo );
+                            if ( lastUpdated != null && lastUpdated.after( context.getLastScheduledRun() ) )
+                            {
+                                saveThisCombo = true; // True if new/updated data.
+                                evaluate = true;
+                                break;
+                            }
+                        }
+
+                        if ( !saveThisCombo )
+                        {
+                            currentValueMapMap.remove( entry.getKey() );
                         }
                     }
                 }
@@ -266,7 +286,6 @@
         return evaluate;
     }
 
-
     /**
      * Gets the data elements for which values should be fetched recursively if
      * they are not collected for an organisation unit.
@@ -297,17 +316,16 @@
      * @param periodTypeX period type being evaluated
      * @param period period being evaluated
      * @param rule ValidationRule being evaluated
-     * @param currentValueMap current values already fetched
+     * @param currentValueMap2 current values already fetched
      * @param sourceDataElements the data elements collected by the organisation
      *        unit
-     * @param context the validation run context
-     * @return the right-side value
+     * @return the right-side values, map by attribute category combo
      */
-    private Double getRightSideValue( OrganisationUnit source, PeriodTypeExtended periodTypeX, Period period,
-        ValidationRule rule, Map<DataElementOperand, Double> currentValueMap,
-        Collection<DataElement> sourceDataElements, ValidationRunContext context )
+    private Map<Integer, Double> getRightSideValue( OrganisationUnit source, PeriodTypeExtended periodTypeX, Period period,
+        ValidationRule rule, MapMap<Integer, DataElementOperand, Double> currentValueMap2,
+        Collection<DataElement> sourceDataElements )
     {
-        Double rightSideValue = null;
+        Map<Integer, Double> rightSideValues;
 
         // If ruleType is VALIDATION, the right side is evaluated using the same
         // (current) data values. If ruleType is SURVEILLANCE but there are no
@@ -318,8 +336,7 @@
         if ( ValidationRule.RULE_TYPE_VALIDATION.equals( rule.getRuleType() )
             || rule.getRightSide().getDataElementsInExpression().isEmpty() )
         {
-            rightSideValue = context.getExpressionService().getExpressionValue( rule.getRightSide(),
-            		currentValueMap, context.getConstantMap(), null, null );
+            rightSideValues = getExpressionValueMap( rule.getRightSide(), currentValueMap2, new SetMap<Integer, DataElementOperand>() );
         }
         else
         // ruleType equals SURVEILLANCE, and there are some data elements in the
@@ -327,7 +344,7 @@
         {
             CalendarPeriodType calendarPeriodType = ( CalendarPeriodType ) period.getPeriodType();
             Collection<PeriodType> rightSidePeriodTypes = context.getRuleXMap().get( rule ).getAllowedPastPeriodTypes();
-            List<Double> sampleValues = new ArrayList<Double>();
+            ListMap<Integer, Double> sampleValuesMap = new ListMap<Integer, Double>();
             Calendar yearlyCalendar = PeriodType.createCalendarInstance( period.getStartDate() );
             int annualSampleCount = rule.getAnnualSampleCount() == null ? 0 : rule.getAnnualSampleCount();
             int sequentialSampleCount = rule.getSequentialSampleCount() == null ? 0 : rule
@@ -350,8 +367,8 @@
                 {
                     // Fetch the period at the same time of year as the
                     // starting period.
-                    evaluateRightSidePeriod( periodTypeX, sampleValues, source, rightSidePeriodTypes, yearlyPeriod,
-                        rule, sourceDataElements, context );
+                    evaluateRightSidePeriod( periodTypeX, sampleValuesMap, source, rightSidePeriodTypes, yearlyPeriod,
+                        rule, sourceDataElements );
 
                     // Fetch the sequential periods after this prior-year
                     // period.
@@ -359,8 +376,8 @@
                     for ( int sequentialCount = 0; sequentialCount < sequentialSampleCount; sequentialCount++ )
                     {
                         sequentialPeriod = calendarPeriodType.getNextPeriod( sequentialPeriod );
-                        evaluateRightSidePeriod( periodTypeX, sampleValues, source, rightSidePeriodTypes,
-                            sequentialPeriod, rule, sourceDataElements, context );
+                        evaluateRightSidePeriod( periodTypeX, sampleValuesMap, source, rightSidePeriodTypes,
+                            sequentialPeriod, rule, sourceDataElements );
                     }
                 }
 
@@ -370,17 +387,22 @@
                 for ( int sequentialCount = 0; sequentialCount < sequentialSampleCount; sequentialCount++ )
                 {
                     sequentialPeriod = calendarPeriodType.getPreviousPeriod( sequentialPeriod );
-                    evaluateRightSidePeriod( periodTypeX, sampleValues, source, rightSidePeriodTypes,
-                        sequentialPeriod, rule, sourceDataElements, context );
+                    evaluateRightSidePeriod( periodTypeX, sampleValuesMap, source, rightSidePeriodTypes,
+                        sequentialPeriod, rule, sourceDataElements );
                 }
 
                 // Move to the previous year:
                 yearlyCalendar.set( Calendar.YEAR, yearlyCalendar.get( Calendar.YEAR ) - 1 );
             }
-            
-            rightSideValue = rightSideAverage( rule, sampleValues, annualSampleCount, sequentialSampleCount );
+
+            rightSideValues = new HashMap<Integer, Double>();
+            for ( Map.Entry<Integer, List<Double>> e : sampleValuesMap.entrySet() )
+            {
+                rightSideValues.put( e.getKey(), rightSideAverage( rule, e.getValue(), annualSampleCount, sequentialSampleCount) );
+            }
+
         }
-        return rightSideValue;
+        return rightSideValues;
     }
 
     /**
@@ -393,18 +415,17 @@
      * organisation units.
      * 
      * @param periodTypeX the period type extended information
-     * @param sampleValues the list of sample values to add to
+     * @param sampleValuesMap the lists of sample values to add to
      * @param source the organisation unit
      * @param allowedPeriodTypes the period types in which the data may exist
      * @param period the main period for the validation rule evaluation
      * @param rule the surveillance-type rule being evaluated
      * @param sourceDataElements the data elements configured for this
      *        organisation unit
-     * @param context the evaluation run context
      */
-    private void evaluateRightSidePeriod( PeriodTypeExtended periodTypeX, List<Double> sampleValues,
+    private void evaluateRightSidePeriod( PeriodTypeExtended periodTypeX, ListMap<Integer, Double> sampleValuesMap,
         OrganisationUnit source, Collection<PeriodType> allowedPeriodTypes, Period period, ValidationRule rule,
-        Collection<DataElement> sourceDataElements, ValidationRunContext context )
+        Collection<DataElement> sourceDataElements )
     {
         Period periodInstance = context.getPeriodService().getPeriod( period.getStartDate(), period.getEndDate(),
             period.getPeriodType() );
@@ -412,24 +433,41 @@
         if ( periodInstance != null )
         {
             Set<DataElement> dataElements = rule.getRightSide().getDataElementsInExpression();
-            Set<DataElementOperand> incompleteValues = new HashSet<DataElementOperand>();
-            Map<DataElementOperand, Double> dataValueMap = getDataValueMapRecursive( periodTypeX, dataElements,
-                sourceDataElements, dataElements, allowedPeriodTypes, period, source, null, incompleteValues );
-            Double value = context.getExpressionService().getExpressionValue( rule.getRightSide(), dataValueMap,
-                context.getConstantMap(), null, null, incompleteValues );
-            
-            if ( value != null )
-            {
-                sampleValues.add( value );
-            }
-
-            log.trace( "ValidationRightSide[" + dataValueMap.size()+ "] - sample "
-                + (value == null ? "(null)" : value) + " [" + period.getStartDate() + " - " + period.getEndDate() + "]" );
+            SetMap<Integer, DataElementOperand> incompleteValuesMap = new SetMap<Integer, DataElementOperand>();
+            MapMap<Integer, DataElementOperand, Double> dataValueMapByAttributeCombo = getValueMap2( periodTypeX, dataElements,
+                    sourceDataElements, dataElements, allowedPeriodTypes, period, source, null, incompleteValuesMap );
+            sampleValuesMap.putValueMap( getExpressionValueMap( rule.getRightSide(), dataValueMapByAttributeCombo, incompleteValuesMap ) );
         }
-        else
+    }
+
+    /**
+     * Evaluates an expresssion, returning a map of values by attribute option
+     * combo.
+     *
+     * @param expression expression to evaluate.
+     * @param valueMap2 Map of value maps, by attribute option combo.
+     * @param incompleteValuesMap map of values that were incomplete.
+     * @return map of values.
+     */
+    private Map<Integer, Double> getExpressionValueMap( Expression expression,
+                                                       MapMap<Integer, DataElementOperand, Double> valueMap2,
+                                                       SetMap<Integer, DataElementOperand> incompleteValuesMap )
+    {
+        Map<Integer, Double> expressionValueMap = new HashMap<Integer, Double>();
+
+        for ( Map.Entry<Integer, Map<DataElementOperand, Double>> e : valueMap2.entrySet() )
         {
-            log.trace( "ValidationRightSide - no period [" + period.getStartDate() + " - " + period.getEndDate() + "]" );
+            expressionValueMap.put(
+                    e.getKey(),
+                    context.getExpressionService()
+                            .getExpressionValue(                                    expression,
+                                    e.getValue(),
+                                    context.getConstantMap(),
+                                    null, null, incompleteValuesMap.getSet(
+                                    e.getKey() ) ) );
         }
+
+        return expressionValueMap;
     }
 
     /**
@@ -442,7 +480,7 @@
      * @param sequentialSampleCount number of sequential samples tried for
      * @return average right-side sample value
      */
-    Double rightSideAverage( ValidationRule rule, List<Double> sampleValues, int annualSampleCount,
+    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
@@ -500,14 +538,15 @@
      * @param period period in which we are looking for values
      * @param source organisation unit for which we are looking for values
      * @param lastUpdatedMap map showing when each data values was last updated
-     * @param incompleteValues ongoing list showing which values were found but
-     *        not from all children
-     * @return the map of values found
+     * @param incompleteValuesMap ongoing set showing which values were found
+     *        but not from all children, mapped by attribute option combo.
+     * @return map of attribute option combo to map of values found.
      */
-    private Map<DataElementOperand, Double> getDataValueMapRecursive( PeriodTypeExtended periodTypeX,
-        Collection<DataElement> ruleDataElements, Collection<DataElement> sourceDataElements,
-        Set<DataElement> recursiveDataElements, Collection<PeriodType> allowedPeriodTypes, Period period,
-        OrganisationUnit source, Map<DataElementOperand, Date> lastUpdatedMap, Set<DataElementOperand> incompleteValues )
+    private MapMap<Integer, DataElementOperand, Double> getValueMap2( 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 )
     {
         Set<DataElement> dataElementsToGet = new HashSet<DataElement>( ruleDataElements );
         dataElementsToGet.retainAll( sourceDataElements );
@@ -518,17 +557,17 @@
             + "] recursiveDataElements[" + recursiveDataElements.size()
             + "] allowedPeriodTypes[" + allowedPeriodTypes.size() + "]" );
 
-        Map<DataElementOperand, Double> dataValueMap;
+        MapMap<Integer, DataElementOperand, Double> dataValueMap2;
         
         if ( dataElementsToGet.isEmpty() )
         {
             // We still might get something recursively
-            dataValueMap = new HashMap<DataElementOperand, Double>();
+            dataValueMap2 = new MapMap<Integer, DataElementOperand, Double>();
         }
         else
         {
-            dataValueMap = context.getDataValueService().getDataValueMap( dataElementsToGet, period.getStartDate(), source,
-                allowedPeriodTypes, lastUpdatedMap );
+            dataValueMap2 = context.getDataValueService().getDataValueMapByAttributeCombo( dataElementsToGet,
+                period.getStartDate(), source, allowedPeriodTypes, context.getAttributeCombo(), lastUpdatedMap );
         }
 
         // See if there are any data elements we need to get recursively:
@@ -537,38 +576,54 @@
         if ( !recursiveDataElementsNeeded.isEmpty() )
         {
             int childCount = 0;
-            Map<DataElementOperand, Integer> childValueCounts = new HashMap<DataElementOperand, Integer>();
+            MapMap<Integer, DataElementOperand, Integer> childValueCounts = new MapMap<Integer, DataElementOperand, Integer>();
             
             for ( OrganisationUnit child : source.getChildren() )
             {
                 Collection<DataElement> childDataElements = periodTypeX.getSourceDataElements().get( child );
-                Map<DataElementOperand, Double> childMap = getDataValueMapRecursive( periodTypeX,
-                    recursiveDataElementsNeeded, childDataElements, recursiveDataElementsNeeded, allowedPeriodTypes,
-                    period, child, lastUpdatedMap, incompleteValues );
+                MapMap<Integer, DataElementOperand, Double> childMap = getValueMap2( periodTypeX,
+                        recursiveDataElementsNeeded, childDataElements, recursiveDataElementsNeeded, allowedPeriodTypes,
+                        period, child, lastUpdatedMap, incompleteValuesMap );
 
-                for ( DataElementOperand deo : childMap.keySet() )
+                for ( Map.Entry<Integer, Map<DataElementOperand, Double>> entry : childMap.entrySet() )
                 {
-                    Double baseValue = dataValueMap.get( deo );
-                    dataValueMap.put( deo, baseValue == null ? childMap.get( deo ) : baseValue + childMap.get( deo ) );
-
-                    Integer childValueCount = childValueCounts.get( deo );
-                    childValueCounts.put( deo, childValueCount == null ? 1 : childValueCount + 1 );
+                    int combo = entry.getKey();
+
+                    for ( Map.Entry<DataElementOperand, Double> e : entry.getValue().entrySet() )
+                    {
+                        DataElementOperand deo = e.getKey();
+                        Double childValue = e.getValue();
+
+                        Double baseValue = dataValueMap2.getValue( combo, deo );
+                        dataValueMap2.putEntry( combo, deo, baseValue == null ? childValue : baseValue + childValue );
+
+                        Integer childValueCount = childValueCounts.getValue( combo, deo );
+                        childValueCounts.putEntry( combo, deo, childValueCount == null ? 1 : childValueCount + 1 );
+                    }
                 }
 
                 childCount++;
             }
-            
-            for ( Map.Entry<DataElementOperand, Integer> entry : childValueCounts.entrySet() )
+
+            for ( Map.Entry<Integer, Map<DataElementOperand, Integer>> entry : childValueCounts.entrySet() )
             {
-                if ( childCount != entry.getValue() )
+                int combo = entry.getKey();
+
+                for ( Map.Entry<DataElementOperand, Integer> e : entry.getValue().entrySet() )
                 {
-                    // Remember that we found this DataElementOperand value
-                    // in some but not all children
-                    incompleteValues.add( entry.getKey() );
+                    DataElementOperand deo = e.getKey();
+                    Integer childValueCount = e.getValue();
+
+                    if ( childValueCount != childCount )
+                    {
+                        // Remember that we found this DataElementOperand value
+                        // in some but not all children
+                        incompleteValuesMap.putValue( combo, deo );
+                    }
                 }
             }
         }
 
-        return dataValueMap;
+        return dataValueMap2;
     }
 }

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml'
--- dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml	2014-05-15 16:58:37 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml	2014-05-18 00:49:40 +0000
@@ -504,6 +504,7 @@
     <property name="periodService" ref="org.hisp.dhis.period.PeriodService" />
     <property name="constantService" ref="org.hisp.dhis.constant.ConstantService" />
     <property name="dataValueService" ref="org.hisp.dhis.datavalue.DataValueService" />
+    <property name="dataElementCategoryService" ref="org.hisp.dhis.dataelement.DataElementCategoryService" />
     <property name="i18nService" ref="org.hisp.dhis.i18n.I18nService" />
     <property name="messageService" ref="org.hisp.dhis.message.MessageService" />
     <property name="organisationUnitService" ref="org.hisp.dhis.organisationunit.OrganisationUnitService" />

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/validation/ValidationRuleServiceTest.java'
--- dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/validation/ValidationRuleServiceTest.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/validation/ValidationRuleServiceTest.java	2014-05-18 00:49:40 +0000
@@ -48,6 +48,9 @@
 
 import org.hisp.dhis.DhisTest;
 import org.hisp.dhis.dataelement.DataElement;
+import org.hisp.dhis.dataelement.DataElementCategory;
+import org.hisp.dhis.dataelement.DataElementCategoryCombo;
+import org.hisp.dhis.dataelement.DataElementCategoryOption;
 import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
 import org.hisp.dhis.dataelement.DataElementCategoryService;
 import org.hisp.dhis.dataelement.DataElementService;
@@ -179,6 +182,8 @@
 
     private ValidationRule validationRuleD;
 
+    private ValidationRule validationRuleX;
+
     private ValidationRule monitoringRuleE;
 
     private ValidationRule monitoringRuleF;
@@ -203,6 +208,8 @@
     
     private PeriodType periodTypeYearly;
 
+    private DataElementCategoryOptionCombo defaultCombo;
+
     // -------------------------------------------------------------------------
     // Fixture
     // -------------------------------------------------------------------------
@@ -233,6 +240,8 @@
 
         periodService = (PeriodService) getBean( PeriodService.ID );
 
+        categoryService = (DataElementCategoryService) getBean( DataElementCategoryService.ID );
+
         periodTypeWeekly = new WeeklyPeriodType();
         periodTypeMonthly = new MonthlyPeriodType();
         periodTypeYearly = new YearlyPeriodType();
@@ -379,11 +388,12 @@
         dataElementService.updateDataElement( dataElementD );
         dataElementService.updateDataElement( dataElementE );
 
-        validationRuleA = createValidationRule( 'A', equal_to, expressionA, expressionB, periodTypeMonthly );
-        validationRuleB = createValidationRule( 'B', greater_than, expressionB, expressionC, periodTypeMonthly );
-        validationRuleC = createValidationRule( 'C', less_than_or_equal_to, expressionB, expressionA, periodTypeMonthly );
-        validationRuleD = createValidationRule( 'D', less_than, expressionA, expressionC, periodTypeMonthly );
-        
+        validationRuleA = createValidationRule( 'A', equal_to, expressionA, expressionB, periodTypeMonthly ); // deA + deB = deC - deD
+        validationRuleB = createValidationRule( 'B', greater_than, expressionB, expressionC, periodTypeMonthly ); // deC - deD > deB * 2
+        validationRuleC = createValidationRule( 'C', less_than_or_equal_to, expressionB, expressionA, periodTypeMonthly ); // deC - deD <= deA + deB
+        validationRuleD = createValidationRule( 'D', less_than, expressionA, expressionC, periodTypeMonthly ); // deA + deB < deB * 2
+        validationRuleX = createValidationRule( 'X', equal_to, expressionA, expressionC, periodTypeMonthly ); // deA + deB = deB * 2
+
         // Compare dataElementB with 1.5 times itself for one sequential previous period.
         monitoringRuleE = createMonitoringRule( 'E', less_than_or_equal_to, expressionD, expressionE, periodTypeMonthly, 1, 1, 0, 0, 0 );
 
@@ -409,6 +419,8 @@
         monitoringRuleL = createMonitoringRule( 'L', less_than_or_equal_to, expressionF, expressionG, periodTypeMonthly, 1, 0, 1, 0, 0 );
 
         group = createValidationRuleGroup( 'A' );
+
+        defaultCombo = categoryService.getDefaultDataElementCategoryOptionCombo();
     }
 
     @Override
@@ -479,19 +491,19 @@
         // insures that if they are the same as the reference results, they will appear in the same order.
         
         Collection<ValidationResult> results = validationRuleService.validate(
-        		getDate( 2000, 2, 1 ), getDate( 2000, 6, 1 ), sourcesA, false, null );
+        		getDate( 2000, 2, 1 ), getDate( 2000, 6, 1 ), sourcesA, null, null, false, null );
 
         Collection<ValidationResult> reference = new HashSet<ValidationResult>();
 
-        reference.add( new ValidationResult( periodA, sourceA, validationRuleA, 3.0, -1.0 ) );
-        reference.add( new ValidationResult( periodB, sourceA, validationRuleA, 3.0, -1.0 ) );
-        reference.add( new ValidationResult( periodA, sourceB, validationRuleA, 3.0, -1.0 ) );
-        reference.add( new ValidationResult( periodB, sourceB, validationRuleA, 3.0, -1.0 ) );
+        reference.add( new ValidationResult( periodA, sourceA, defaultCombo, validationRuleA, 3.0, -1.0 ) );
+        reference.add( new ValidationResult( periodB, sourceA, defaultCombo, validationRuleA, 3.0, -1.0 ) );
+        reference.add( new ValidationResult( periodA, sourceB, defaultCombo, validationRuleA, 3.0, -1.0 ) );
+        reference.add( new ValidationResult( periodB, sourceB, defaultCombo, validationRuleA, 3.0, -1.0 ) );
 
-        reference.add( new ValidationResult( periodA, sourceA, validationRuleB, -1.0, 4.0 ) );
-        reference.add( new ValidationResult( periodB, sourceA, validationRuleB, -1.0, 4.0 ) );
-        reference.add( new ValidationResult( periodA, sourceB, validationRuleB, -1.0, 4.0 ) );
-        reference.add( new ValidationResult( periodB, sourceB, validationRuleB, -1.0, 4.0 ) );
+        reference.add( new ValidationResult( periodA, sourceA, defaultCombo, validationRuleB, -1.0, 4.0 ) );
+        reference.add( new ValidationResult( periodB, sourceA, defaultCombo, validationRuleB, -1.0, 4.0 ) );
+        reference.add( new ValidationResult( periodA, sourceB, defaultCombo, validationRuleB, -1.0, 4.0 ) );
+        reference.add( new ValidationResult( periodB, sourceB, defaultCombo, validationRuleB, -1.0, 4.0 ) );
 
         for ( ValidationResult result : results )
         {
@@ -537,14 +549,14 @@
         validationRuleService.addValidationRuleGroup( group );
 
         Collection<ValidationResult> results = validationRuleService.validate(
-        		getDate( 2000, 2, 1 ), getDate( 2000, 6, 1 ), sourcesA, group, false, null );
+        		getDate( 2000, 2, 1 ), getDate( 2000, 6, 1 ), sourcesA, null, group, false, null );
 
         Collection<ValidationResult> reference = new HashSet<ValidationResult>();
 
-        reference.add( new ValidationResult( periodA, sourceA, validationRuleA, 3.0, -1.0 ) );
-        reference.add( new ValidationResult( periodB, sourceA, validationRuleA, 3.0, -1.0 ) );
-        reference.add( new ValidationResult( periodA, sourceB, validationRuleA, 3.0, -1.0 ) );
-        reference.add( new ValidationResult( periodB, sourceB, validationRuleA, 3.0, -1.0 ) );
+        reference.add( new ValidationResult( periodA, sourceA, defaultCombo, validationRuleA, 3.0, -1.0 ) );
+        reference.add( new ValidationResult( periodB, sourceA, defaultCombo, validationRuleA, 3.0, -1.0 ) );
+        reference.add( new ValidationResult( periodA, sourceB, defaultCombo, validationRuleA, 3.0, -1.0 ) );
+        reference.add( new ValidationResult( periodB, sourceB, defaultCombo, validationRuleA, 3.0, -1.0 ) );
 
         for ( ValidationResult result : results )
         {
@@ -579,10 +591,10 @@
 
         Collection<ValidationResult> reference = new HashSet<ValidationResult>();
 
-        reference.add( new ValidationResult( periodA, sourceA, validationRuleA, 3.0, -1.0 ) );
-        reference.add( new ValidationResult( periodB, sourceA, validationRuleA, 3.0, -1.0 ) );
-        reference.add( new ValidationResult( periodA, sourceA, validationRuleB, -1.0, 4.0 ) );
-        reference.add( new ValidationResult( periodB, sourceA, validationRuleB, -1.0, 4.0 ) );
+        reference.add( new ValidationResult( periodA, sourceA, defaultCombo, validationRuleA, 3.0, -1.0 ) );
+        reference.add( new ValidationResult( periodB, sourceA, defaultCombo, validationRuleA, 3.0, -1.0 ) );
+        reference.add( new ValidationResult( periodA, sourceA, defaultCombo, validationRuleB, -1.0, 4.0 ) );
+        reference.add( new ValidationResult( periodB, sourceA, defaultCombo, validationRuleB, -1.0, 4.0 ) );
 
         for ( ValidationResult result : results )
         {
@@ -608,12 +620,12 @@
         validationRuleService.saveValidationRule( validationRuleD );
 
         Collection<ValidationResult> results = validationRuleService.validate(
-        		dataSetMonthly, periodA, sourceA );
+        		dataSetMonthly, periodA, sourceA, null );
 
         Collection<ValidationResult> reference = new HashSet<ValidationResult>();
 
-        reference.add( new ValidationResult( periodA, sourceA, validationRuleA, 3.0, -1.0 ) );
-        reference.add( new ValidationResult( periodA, sourceA, validationRuleB, -1.0, 4.0 ) );
+        reference.add( new ValidationResult( periodA, sourceA, defaultCombo, validationRuleA, 3.0, -1.0 ) );
+        reference.add( new ValidationResult( periodA, sourceA, defaultCombo, validationRuleB, -1.0, 4.0 ) );
 
         for ( ValidationResult result : results )
         {
@@ -655,9 +667,9 @@
         
         Collection<ValidationResult> reference = new HashSet<ValidationResult>();
 
-        reference.add( new ValidationResult( periodL, sourceA, monitoringRuleE, 200.0, 150.0 /* 1.5 * 100 */ ) );
-        reference.add( new ValidationResult( periodM, sourceA, monitoringRuleE, 400.0, 300.0 /* 1.5 * 200 */ ) );
-        reference.add( new ValidationResult( periodN, sourceA, monitoringRuleE, 700.0, 600.0 /* 1.5 * 400 */ ) );
+        reference.add( new ValidationResult( periodL, sourceA, defaultCombo, monitoringRuleE, 200.0, 150.0 /* 1.5 * 100 */ ) );
+        reference.add( new ValidationResult( periodM, sourceA, defaultCombo, monitoringRuleE, 400.0, 300.0 /* 1.5 * 200 */ ) );
+        reference.add( new ValidationResult( periodN, sourceA, defaultCombo, monitoringRuleE, 700.0, 600.0 /* 1.5 * 400 */ ) );
 
         for ( ValidationResult result : results )
         {
@@ -697,9 +709,9 @@
         
         Collection<ValidationResult> reference = new HashSet<ValidationResult>();
 
-        reference.add( new ValidationResult( periodK, sourceA, monitoringRuleF, 100.0, 75.0 /* 1.5 * 50 */ ) );
-        reference.add( new ValidationResult( periodM, sourceA, monitoringRuleF, 400.0, 300.0 /* 1.5 * 200 */ ) );
-        reference.add( new ValidationResult( periodO, sourceA, monitoringRuleF, 800.0, 600.0 /* 1.5 * 400 */ ) );
+        reference.add( new ValidationResult( periodK, sourceA, defaultCombo, monitoringRuleF, 100.0, 75.0 /* 1.5 * 50 */ ) );
+        reference.add( new ValidationResult( periodM, sourceA, defaultCombo, monitoringRuleF, 400.0, 300.0 /* 1.5 * 200 */ ) );
+        reference.add( new ValidationResult( periodO, sourceA, defaultCombo, monitoringRuleF, 800.0, 600.0 /* 1.5 * 400 */ ) );
 
         for ( ValidationResult result : results )
         {
@@ -739,11 +751,11 @@
         
         Collection<ValidationResult> reference = new HashSet<ValidationResult>();
 
-        reference.add( new ValidationResult( periodK, sourceA, monitoringRuleG, 100.0, 83.6 /* 1.5 * ( 11 + 12 + 50 + 150 ) / 4 */  ) );
-        reference.add( new ValidationResult( periodL, sourceA, monitoringRuleG, 200.0, 114.9 /* 1.5 * ( 11 + 12 + 13 + 50 + 150 + 200 + 100 ) / 7 */  ) );
-        reference.add( new ValidationResult( periodM, sourceA, monitoringRuleG, 400.0, 254.8 /* 1.5 * ( 12 + 13 + 14 + 150 + 200 + 600 + 200 ) / 7 */  ) );
-        reference.add( new ValidationResult( periodN, sourceA, monitoringRuleG, 700.0, 351.9 /* 1.5 * ( 13 + 14 + 15 + 200 + 600 + 400 + 400 ) / 7 */  ) );
-        reference.add( new ValidationResult( periodO, sourceA, monitoringRuleG, 800.0, 518.7 /* 1.5 * ( 14 + 15 + 600 + 400 + 700 ) / 5 */  ) );
+        reference.add( new ValidationResult( periodK, sourceA, defaultCombo, monitoringRuleG, 100.0, 83.6 /* 1.5 * ( 11 + 12 + 50 + 150 ) / 4 */  ) );
+        reference.add( new ValidationResult( periodL, sourceA, defaultCombo, monitoringRuleG, 200.0, 114.9 /* 1.5 * ( 11 + 12 + 13 + 50 + 150 + 200 + 100 ) / 7 */  ) );
+        reference.add( new ValidationResult( periodM, sourceA, defaultCombo, monitoringRuleG, 400.0, 254.8 /* 1.5 * ( 12 + 13 + 14 + 150 + 200 + 600 + 200 ) / 7 */  ) );
+        reference.add( new ValidationResult( periodN, sourceA, defaultCombo, monitoringRuleG, 700.0, 351.9 /* 1.5 * ( 13 + 14 + 15 + 200 + 600 + 400 + 400 ) / 7 */  ) );
+        reference.add( new ValidationResult( periodO, sourceA, defaultCombo, monitoringRuleG, 800.0, 518.7 /* 1.5 * ( 14 + 15 + 600 + 400 + 700 ) / 5 */  ) );
 
         for ( ValidationResult result : results )
         {
@@ -784,10 +796,10 @@
         Collection<ValidationResult> reference = new HashSet<ValidationResult>();
 
         // Not in results: reference.add( new ValidationResult( periodK, sourceA, monitoringRuleH, 100.0, 109.0 /* 1.5 * ( 11 + 12 + 13 + 50 + 150 + 200 ) / 6 */  ) );
-        reference.add( new ValidationResult( periodL, sourceA, monitoringRuleH, 200.0, 191.7 /* 1.5 * ( 11 + 12 + 13 + 14 + 50 + 150 + 200 + 600 + 100 ) / 9 */  ) );
-        reference.add( new ValidationResult( periodM, sourceA, monitoringRuleH, 400.0, 220.6 /* 1.5 * ( 11 + 12 + 13 + 14 + 15 + 50 + 150 + 200 + 600 + 400 + 100 + 200 ) / 12 */  ) );
-        reference.add( new ValidationResult( periodN, sourceA, monitoringRuleH, 700.0, 300.6 /* 1.5 * ( 12 + 13 + 14 + 15 + 150 + 200 + 600 + 400 + 200 + 400 ) / 10 */  ) );
-        reference.add( new ValidationResult( periodO, sourceA, monitoringRuleH, 800.0, 439.1 /* 1.5 * ( 13 + 14 + 15 + 200 + 600 + 400 + 400 + 700 ) / 8 */  ) );
+        reference.add( new ValidationResult( periodL, sourceA, defaultCombo, monitoringRuleH, 200.0, 191.7 /* 1.5 * ( 11 + 12 + 13 + 14 + 50 + 150 + 200 + 600 + 100 ) / 9 */  ) );
+        reference.add( new ValidationResult( periodM, sourceA, defaultCombo, monitoringRuleH, 400.0, 220.6 /* 1.5 * ( 11 + 12 + 13 + 14 + 15 + 50 + 150 + 200 + 600 + 400 + 100 + 200 ) / 12 */  ) );
+        reference.add( new ValidationResult( periodN, sourceA, defaultCombo, monitoringRuleH, 700.0, 300.6 /* 1.5 * ( 12 + 13 + 14 + 15 + 150 + 200 + 600 + 400 + 200 + 400 ) / 10 */  ) );
+        reference.add( new ValidationResult( periodO, sourceA, defaultCombo, monitoringRuleH, 800.0, 439.1 /* 1.5 * ( 13 + 14 + 15 + 200 + 600 + 400 + 400 + 700 ) / 8 */  ) );
         
         for ( ValidationResult result : results )
         {
@@ -827,11 +839,11 @@
         
         Collection<ValidationResult> reference = new HashSet<ValidationResult>();
 
-        reference.add( new ValidationResult( periodK, sourceA, monitoringRuleI, 100.0, 32.3 /* 1.5 * ( 11 + 12 + 13 + 50 ) / 4 */  ) );
-        reference.add( new ValidationResult( periodL, sourceA, monitoringRuleI, 200.0, 75.0 /* 1.5 * ( 11 + 12 + 13 + 14 + 50 + 150 + 100 ) / 7 */  ) );
-        reference.add( new ValidationResult( periodM, sourceA, monitoringRuleI, 400.0, 114.8 /* 1.5 * ( 11 + 12 + 13 + 14 + 15 + 50 + 150 + 200 + 100 + 200 ) / 10 */  ) );
-        reference.add( new ValidationResult( periodN, sourceA, monitoringRuleI, 700.0, 188.3 /* 1.5 * ( 12 + 13 + 14 + 15 + 150 + 200 + 200 + 400 ) / 8 */  ) );
-        reference.add( new ValidationResult( periodO, sourceA, monitoringRuleI, 800.0, 260.5 /* 1.5 * ( 13 + 14 + 15 + 200 + 400 + 400 ) / 6 */  ) );
+        reference.add( new ValidationResult( periodK, sourceA, defaultCombo, monitoringRuleI, 100.0, 32.3 /* 1.5 * ( 11 + 12 + 13 + 50 ) / 4 */  ) );
+        reference.add( new ValidationResult( periodL, sourceA, defaultCombo, monitoringRuleI, 200.0, 75.0 /* 1.5 * ( 11 + 12 + 13 + 14 + 50 + 150 + 100 ) / 7 */  ) );
+        reference.add( new ValidationResult( periodM, sourceA, defaultCombo, monitoringRuleI, 400.0, 114.8 /* 1.5 * ( 11 + 12 + 13 + 14 + 15 + 50 + 150 + 200 + 100 + 200 ) / 10 */  ) );
+        reference.add( new ValidationResult( periodN, sourceA, defaultCombo, monitoringRuleI, 700.0, 188.3 /* 1.5 * ( 12 + 13 + 14 + 15 + 150 + 200 + 200 + 400 ) / 8 */  ) );
+        reference.add( new ValidationResult( periodO, sourceA, defaultCombo, monitoringRuleI, 800.0, 260.5 /* 1.5 * ( 13 + 14 + 15 + 200 + 400 + 400 ) / 6 */  ) );
         
         for ( ValidationResult result : results )
         {
@@ -873,9 +885,9 @@
 
         // Not in results: reference.add( new ValidationResult( periodK, sourceA, monitoringRuleH, 100.0, 154.9 /* 1.5 * ( 13 + 50 + 150 + 200 ) / 4 */  ) );
         // Not in results: reference.add( new ValidationResult( periodL, sourceA, monitoringRuleJ, 200.0, 241.5 /* 1.5 * ( 13 + 14 + 50 + 150 + 200 + 600 + 100 ) / 7 */  ) );
-        reference.add( new ValidationResult( periodM, sourceA, monitoringRuleJ, 400.0, 261.3 /* 1.5 * ( 13 + 14 + 15 + 50 + 150 + 200 + 600 + 400 + 100 + 200 ) / 10 */  ) );
-        reference.add( new ValidationResult( periodN, sourceA, monitoringRuleJ, 700.0, 371.1 /* 1.5 * ( 14 + 15 + 150 + 200 + 600 + 400 + 200 + 400 ) / 8 */  ) );
-        reference.add( new ValidationResult( periodO, sourceA, monitoringRuleJ, 800.0, 578.8 /* 1.5 * ( 15 + 200 + 600 + 400 + 400 + 700 ) / 6 */  ) );
+        reference.add( new ValidationResult( periodM, sourceA, defaultCombo, monitoringRuleJ, 400.0, 261.3 /* 1.5 * ( 13 + 14 + 15 + 50 + 150 + 200 + 600 + 400 + 100 + 200 ) / 10 */  ) );
+        reference.add( new ValidationResult( periodN, sourceA, defaultCombo, monitoringRuleJ, 700.0, 371.1 /* 1.5 * ( 14 + 15 + 150 + 200 + 600 + 400 + 200 + 400 ) / 8 */  ) );
+        reference.add( new ValidationResult( periodO, sourceA, defaultCombo, monitoringRuleJ, 800.0, 578.8 /* 1.5 * ( 15 + 200 + 600 + 400 + 400 + 700 ) / 6 */  ) );
         
         for ( ValidationResult result : results )
         {
@@ -915,11 +927,11 @@
         
         Collection<ValidationResult> reference = new HashSet<ValidationResult>();
 
-        reference.add( new ValidationResult( periodK, sourceA, monitoringRuleK, 100.0, 47.3 /* 1.5 * ( 13 + 50 ) / 2 */  ) );
-        reference.add( new ValidationResult( periodL, sourceA, monitoringRuleK, 200.0, 98.1 /* 1.5 * ( 13 + 14 + 50 + 150 + 100 ) / 5 */  ) );
-        reference.add( new ValidationResult( periodM, sourceA, monitoringRuleK, 400.0, 139.1 /* 1.5 * ( 13 + 14 + 15 + 50 + 150 + 200 + 100 + 200 ) / 8 */  ) );
-        reference.add( new ValidationResult( periodN, sourceA, monitoringRuleK, 700.0, 244.8 /* 1.5 * ( 14 + 15 + 150 + 200 + 200 + 400 ) / 6 */  ) );
-        reference.add( new ValidationResult( periodO, sourceA, monitoringRuleK, 800.0, 380.6 /* 1.5 * ( 15 + 200 + 400 + 400 ) / 4 */  ) );
+        reference.add( new ValidationResult( periodK, sourceA, defaultCombo, monitoringRuleK, 100.0, 47.3 /* 1.5 * ( 13 + 50 ) / 2 */  ) );
+        reference.add( new ValidationResult( periodL, sourceA, defaultCombo, monitoringRuleK, 200.0, 98.1 /* 1.5 * ( 13 + 14 + 50 + 150 + 100 ) / 5 */  ) );
+        reference.add( new ValidationResult( periodM, sourceA, defaultCombo, monitoringRuleK, 400.0, 139.1 /* 1.5 * ( 13 + 14 + 15 + 50 + 150 + 200 + 100 + 200 ) / 8 */  ) );
+        reference.add( new ValidationResult( periodN, sourceA, defaultCombo, monitoringRuleK, 700.0, 244.8 /* 1.5 * ( 14 + 15 + 150 + 200 + 200 + 400 ) / 6 */  ) );
+        reference.add( new ValidationResult( periodO, sourceA, defaultCombo, monitoringRuleK, 800.0, 380.6 /* 1.5 * ( 15 + 200 + 400 + 400 ) / 4 */  ) );
         
         for ( ValidationResult result : results )
         {
@@ -966,11 +978,11 @@
         
         Collection<ValidationResult> reference = new HashSet<ValidationResult>();
 
-        reference.add( new ValidationResult( periodK, sourceB, monitoringRuleL, 10.0 /* 100 / 10 */, 1.5 /* 1.5 * 50 / 50 */ ) );
-        reference.add( new ValidationResult( periodL, sourceB, monitoringRuleL, 20.0 /* 200 / 10 */, 4.5 /* 1.5 * 150 / 50 */ ) );
-        reference.add( new ValidationResult( periodM, sourceB, monitoringRuleL, 40.0 /* 400 / 10 */, 6.0 /* 1.5 * 200 / 50 */ ) );
-        reference.add( new ValidationResult( periodN, sourceB, monitoringRuleL, 70.0 /* 700 / 10 */, 18.0 /* 1.5 * 600 / 50 */ ) );
-        reference.add( new ValidationResult( periodO, sourceB, monitoringRuleL, 80.0 /* 800 / 10 */, 12.0 /* 1.5 * 400 / 50 */ ) );
+        reference.add( new ValidationResult( periodK, sourceB, defaultCombo, monitoringRuleL, 10.0 /* 100 / 10 */, 1.5 /* 1.5 * 50 / 50 */ ) );
+        reference.add( new ValidationResult( periodL, sourceB, defaultCombo, monitoringRuleL, 20.0 /* 200 / 10 */, 4.5 /* 1.5 * 150 / 50 */ ) );
+        reference.add( new ValidationResult( periodM, sourceB, defaultCombo, monitoringRuleL, 40.0 /* 400 / 10 */, 6.0 /* 1.5 * 200 / 50 */ ) );
+        reference.add( new ValidationResult( periodN, sourceB, defaultCombo, monitoringRuleL, 70.0 /* 700 / 10 */, 18.0 /* 1.5 * 600 / 50 */ ) );
+        reference.add( new ValidationResult( periodO, sourceB, defaultCombo, monitoringRuleL, 80.0 /* 800 / 10 */, 12.0 /* 1.5 * 400 / 50 */ ) );
 
         for ( ValidationResult result : results )
         {
@@ -982,6 +994,92 @@
         assertEquals( orderedList( reference ), orderedList( results ) );
     }
 
+    @Test
+    public void testValidateWithAttributeOptions()
+    {
+        DataElementCategoryOption optionA = new DataElementCategoryOption( "CategoryOptionA" );
+        DataElementCategoryOption optionB = new DataElementCategoryOption( "CategoryOptionB" );
+        DataElementCategoryOption optionC = new DataElementCategoryOption( "CategoryOptionC" );
+
+        categoryService.addDataElementCategoryOption( optionA );
+        categoryService.addDataElementCategoryOption( optionB );
+        categoryService.addDataElementCategoryOption( optionC );
+
+        DataElementCategory categoryA = createDataElementCategory( 'A', optionA, optionB );
+        DataElementCategory categoryB = createDataElementCategory( 'B', optionC );
+        categoryA.setDataDimension( true );
+        categoryB.setDataDimension( true );
+
+        categoryService.addDataElementCategory( categoryA );
+        categoryService.addDataElementCategory( categoryB );
+
+        DataElementCategoryCombo categoryComboAB = createCategoryCombo( 'A', categoryA, categoryB );
+
+        categoryService.addDataElementCategoryCombo( categoryComboAB );
+
+        DataElementCategoryOptionCombo optionComboAC = createCategoryOptionCombo( 'A', categoryComboAB, optionA, optionC );
+        DataElementCategoryOptionCombo optionComboBC = createCategoryOptionCombo( 'A', categoryComboAB, optionB, optionC );
+
+        categoryService.addDataElementCategoryOptionCombo( optionComboAC );
+        categoryService.addDataElementCategoryOptionCombo( optionComboBC );
+
+        dataValueService.addDataValue( createDataValue( dataElementA, periodA, sourceA, "4", optionCombo, optionComboAC ) );
+        dataValueService.addDataValue( createDataValue( dataElementB, periodA, sourceA, "3", optionCombo, optionComboAC ) );
+
+        dataValueService.addDataValue( createDataValue( dataElementA, periodA, sourceA, "2", optionCombo, optionComboBC ) );
+        dataValueService.addDataValue( createDataValue( dataElementB, periodA, sourceA, "1", optionCombo, optionComboBC ) );
+
+        validationRuleService.saveValidationRule( validationRuleD ); // deA + deB < deB * 2
+        validationRuleService.saveValidationRule( validationRuleX ); // deA + deB = deB * 2
+
+        //
+        // optionComboAC
+        //
+        Collection<ValidationResult> results = validationRuleService.validate( dataSetMonthly, periodA, sourceA, optionComboAC );
+
+        Collection<ValidationResult> reference = new HashSet<ValidationResult>();
+
+        reference.add( new ValidationResult( periodA, sourceA, optionComboAC, validationRuleD, 7.0, 6.0 ) );
+        reference.add( new ValidationResult( periodA, sourceA, optionComboAC, validationRuleX, 7.0, 6.0 ) );
+
+        for ( ValidationResult result : results )
+        {
+            assertFalse( MathUtils.expressionIsTrue( result.getLeftsideValue(), result.getValidationRule()
+                    .getOperator(), result.getRightsideValue() ) );
+        }
+
+        assertEquals( 2, results.size() );
+        assertEquals( orderedList( reference ), orderedList( results ) );
+
+        //
+        // All optionCombos
+        //
+        results = validationRuleService.validate( dataSetMonthly, periodA, sourceA, null );
+
+        reference = new HashSet<ValidationResult>();
+
+        reference.add( new ValidationResult( periodA, sourceA, optionComboAC, validationRuleD, 7.0, 6.0 ) );
+        reference.add( new ValidationResult( periodA, sourceA, optionComboAC, validationRuleX, 7.0, 6.0 ) );
+        reference.add( new ValidationResult( periodA, sourceA, optionComboBC, validationRuleD, 3.0, 2.0 ) );
+        reference.add( new ValidationResult( periodA, sourceA, optionComboBC, validationRuleX, 3.0, 2.0 ) );
+
+        for ( ValidationResult result : results )
+        {
+            assertFalse( MathUtils.expressionIsTrue( result.getLeftsideValue(), result.getValidationRule()
+                    .getOperator(), result.getRightsideValue() ) );
+        }
+
+        assertEquals( 4, results.size() );
+        assertEquals( orderedList( reference ), orderedList( results ) );
+
+        //
+        // Default optionCombo
+        //
+        results = validationRuleService.validate( dataSetMonthly, periodA, sourceA, optionCombo );
+
+        assertEquals( 0, results.size() );
+    }
+
     // -------------------------------------------------------------------------
     // CURD functionality tests
     // -------------------------------------------------------------------------

=== modified file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/ValidationAction.java'
--- dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/ValidationAction.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-web/dhis-web-dataentry/src/main/java/org/hisp/dhis/de/action/ValidationAction.java	2014-05-18 00:49:40 +0000
@@ -38,10 +38,14 @@
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.apache.struts2.ServletActionContext;
+import org.hisp.dhis.api.utils.InputUtils;
 import org.hisp.dhis.common.comparator.IdentifiableObjectNameComparator;
 import org.hisp.dhis.dataanalysis.DataAnalysisService;
 import org.hisp.dhis.dataelement.DataElement;
+import org.hisp.dhis.dataelement.DataElementCategoryOption;
 import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
+import org.hisp.dhis.dataelement.DataElementCategoryService;
 import org.hisp.dhis.dataelement.DataElementOperand;
 import org.hisp.dhis.dataset.DataSet;
 import org.hisp.dhis.dataset.DataSetService;
@@ -54,9 +58,11 @@
 import org.hisp.dhis.period.PeriodService;
 import org.hisp.dhis.period.PeriodType;
 import org.hisp.dhis.validation.ValidationResult;
+import org.hisp.dhis.validation.ValidationRule;
 import org.hisp.dhis.validation.ValidationRuleService;
 
 import com.opensymphony.xwork2.Action;
+import org.springframework.beans.factory.annotation.Autowired;
 
 /**
  * @author Margrethe Store
@@ -105,7 +111,14 @@
     {
         this.organisationUnitService = organisationUnitService;
     }
-    
+
+    private DataElementCategoryService dataElementCategoryService;
+
+    public void setDataElementCategoryService( DataElementCategoryService dataElementCategoryService )
+    {
+        this.dataElementCategoryService = dataElementCategoryService;
+    }
+
     private DataValueService dataValueService;
 
     public void setDataValueService( DataValueService dataValueService )
@@ -113,6 +126,9 @@
         this.dataValueService = dataValueService;
     }
 
+    @Autowired
+    private InputUtils inputUtils;
+
     // -------------------------------------------------------------------------
     // Input
     // -------------------------------------------------------------------------
@@ -138,6 +154,20 @@
         this.ou = ou;
     }
 
+    private String cc;
+
+    public void setCc( String cc )
+    {
+        this.cc = cc;
+    }
+
+    private String cp;
+
+    public void setCp( String cp )
+    {
+        this.cp = cp;
+    }
+
     private boolean multiOu;
 
     public boolean isMultiOu()
@@ -188,6 +218,13 @@
 
         Period selectedPeriod = PeriodType.getPeriodFromIsoString( pe );
 
+        DataElementCategoryOptionCombo attributeOptionCombo = inputUtils.getAttributeOptionCombo( ServletActionContext.getResponse(), cc, cp );
+
+        if ( attributeOptionCombo == null )
+        {
+            attributeOptionCombo = dataElementCategoryService.getDefaultDataElementCategoryOptionCombo();
+        }
+
         if ( selectedPeriod == null || orgUnit == null || ( multiOu && !orgUnit.hasChild() ) )
         {
             return SUCCESS;
@@ -218,7 +255,7 @@
                 dataValues.put( organisationUnit, values );
             }
 
-            List<ValidationResult> results = validationRuleAnalysis( organisationUnit, dataSet, period );
+            List<ValidationResult> results = validationRuleAnalysis( organisationUnit, dataSet, period, attributeOptionCombo );
 
             if ( !results.isEmpty() )
             {
@@ -254,9 +291,9 @@
     // Validation rule analysis
     // -------------------------------------------------------------------------
     
-    private List<ValidationResult> validationRuleAnalysis( OrganisationUnit organisationUnit, DataSet dataSet, Period period )
+    private List<ValidationResult> validationRuleAnalysis( OrganisationUnit organisationUnit, DataSet dataSet, Period period, DataElementCategoryOptionCombo attributeOptionCombo )
     {
-        List<ValidationResult> validationResults = new ArrayList<ValidationResult>( validationRuleService.validate( dataSet, period, organisationUnit ) );
+        List<ValidationResult> validationResults = new ArrayList<ValidationResult>( validationRuleService.validate( dataSet, period, organisationUnit, attributeOptionCombo ) );
 
         log.debug( "Number of validation violations: " + validationResults.size() );
 

=== modified file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/META-INF/dhis/beans.xml'
--- dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/META-INF/dhis/beans.xml	2014-03-04 00:59:32 +0000
+++ dhis-2/dhis-web/dhis-web-dataentry/src/main/resources/META-INF/dhis/beans.xml	2014-05-18 00:49:40 +0000
@@ -66,6 +66,7 @@
     <property name="minMaxOutlierAnalysisService" ref="org.hisp.dhis.dataanalysis.MinMaxOutlierAnalysisService" />
     <property name="dataSetService" ref="org.hisp.dhis.dataset.DataSetService" />
     <property name="organisationUnitService" ref="org.hisp.dhis.organisationunit.OrganisationUnitService" />
+    <property name="dataElementCategoryService" ref="org.hisp.dhis.dataelement.DataElementCategoryService" />
     <property name="dataValueService" ref="org.hisp.dhis.datavalue.DataValueService" />
   </bean>
 

=== modified file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/javascript/form.js'
--- dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/javascript/form.js	2014-05-06 10:29:22 +0000
+++ dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/javascript/form.js	2014-05-18 00:49:40 +0000
@@ -1711,8 +1711,17 @@
 
 	var validCompleteOnly = dhis2.de.dataSets[dhis2.de.currentDataSetId].validCompleteOnly;
 
+    var cc = dhis2.de.getCurrentCategoryCombo();
+    var cp = dhis2.de.getCurrentCategoryOptionsQueryValue();
+
     var params = dhis2.de.storageManager.getCurrentCompleteDataSetParams();
 
+    if ( cc && cp )
+    {
+        params.cc = dhis2.de.getCurrentCategoryCombo();
+        params.cp = dhis2.de.getCurrentCategoryOptionsQueryValue();
+    }
+
     $( '#validationDiv' ).load( 'validate.action', params, function( response, status, xhr ) {
     	var success = null;
     	

=== modified file 'dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/validationResult.vm'
--- dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/validationResult.vm	2014-03-04 00:59:32 +0000
+++ dhis-2/dhis-web/dhis-web-dataentry/src/main/webapp/dhis-web-dataentry/validationResult.vm	2014-05-18 00:49:40 +0000
@@ -22,9 +22,9 @@
             #set ( $rightFormula = $rightSideFormulaMap.get( $key ).get( $id ) )
             <tr>
                 <td style="height:32px"#alternate( $mark )>$!encoder.htmlEncode( $validationResult.validationRule.getInstructionFallback() )</td>
-                <td#alternate( $mark )>$validationResult.leftsideValue</td>
-                <td#alternate( $mark )>$encoder.htmlEncode( $i18n.getString( $validationResult.validationRule.operator.mathematicalOperator ) )</td>
-                <td#alternate( $mark )>$validationResult.rightsideValue</td>
+                <td #alternate( $mark )>$validationResult.leftsideValue</td>
+                <td #alternate( $mark )>$encoder.htmlEncode( $i18n.getString( $validationResult.validationRule.operator.mathematicalOperator ) )</td>
+                <td #alternate( $mark )>$validationResult.rightsideValue</td>
             </tr>
         #if( $mark )
             #set( $mark = false )

=== modified file 'dhis-2/dhis-web/dhis-web-light/src/main/java/org/hisp/dhis/light/utils/FormUtilsImpl.java'
--- dhis-2/dhis-web/dhis-web-light/src/main/java/org/hisp/dhis/light/utils/FormUtilsImpl.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-web/dhis-web-light/src/main/java/org/hisp/dhis/light/utils/FormUtilsImpl.java	2014-05-18 00:49:40 +0000
@@ -174,7 +174,7 @@
     public List<String> getValidationRuleViolations( OrganisationUnit organisationUnit, DataSet dataSet, Period period )
     {
         List<ValidationResult> validationRuleResults = new ArrayList<ValidationResult>( validationRuleService.validate(
-            dataSet, period, organisationUnit ) );
+            dataSet, period, organisationUnit, null ) );
 
         List<String> validationRuleViolations = new ArrayList<String>( validationRuleResults.size() );
 

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/RunValidationAction.java'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/RunValidationAction.java	2014-03-18 08:10:10 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/RunValidationAction.java	2014-05-18 00:49:40 +0000
@@ -32,6 +32,8 @@
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.hisp.dhis.common.Grid;
+import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
+import org.hisp.dhis.dataelement.DataElementCategoryService;
 import org.hisp.dhis.i18n.I18nFormat;
 import org.hisp.dhis.organisationunit.OrganisationUnit;
 import org.hisp.dhis.organisationunit.OrganisationUnitService;
@@ -82,7 +84,14 @@
     {
         this.organisationUnitService = organisationUnitService;
     }
-    
+
+    private DataElementCategoryService dataElementCategoryService;
+
+    public void setDataElementCategoryService( DataElementCategoryService dataElementCategoryService )
+    {
+        this.dataElementCategoryService = dataElementCategoryService;
+    }
+
     // -------------------------------------------------------------------------
     // Input/output
     // -------------------------------------------------------------------------
@@ -118,13 +127,20 @@
         this.endDate = endDate;
     }
 
+    private Integer attributeOptionComboId;
+
+    public void setAttributeOptionComboId( Integer attributeOptionComboId )
+    {
+        this.attributeOptionComboId = attributeOptionComboId;
+    }
+
     private Integer validationRuleGroupId;
 
     public void setValidationRuleGroupId( Integer validationRuleGroupId )
     {
         this.validationRuleGroupId = validationRuleGroupId;
     }
-    
+
     private boolean sendAlerts;
 
     public void setSendAlerts( boolean sendAlerts )
@@ -153,6 +169,13 @@
         return maxExceeded;
     }
 
+    private boolean showAttributeCombos;
+
+    public boolean isShowAttributeCombos()
+    {
+        return showAttributeCombos;
+    }
+
     private OrganisationUnit organisationUnit;
 
     public OrganisationUnit getOrganisationUnit()
@@ -170,22 +193,14 @@
 
         Collection<OrganisationUnit> organisationUnits = organisationUnitService.getOrganisationUnitWithChildren( organisationUnit.getId() );
 
-        if ( validationRuleGroupId == -1 )
-        {
-            log.info( "Validating captured data for all rules" );
-
-            validationResults = new ArrayList<ValidationResult>( validationRuleService.validate( format
-                .parseDate( startDate ), format.parseDate( endDate ), organisationUnits, sendAlerts, format ) );
-        }
-        else
-        {
-            ValidationRuleGroup group = validationRuleService.getValidationRuleGroup( validationRuleGroupId );
-
-            log.info( "Validating captured data for rules for group: '" + group.getName() + "'" );
-
-            validationResults = new ArrayList<ValidationResult>( validationRuleService.validate( format
-                .parseDate( startDate ), format.parseDate( endDate ), organisationUnits, group, sendAlerts, format ) );
-        }
+        ValidationRuleGroup group = validationRuleGroupId == -1 ? null : validationRuleService.getValidationRuleGroup( validationRuleGroupId );
+
+        DataElementCategoryOptionCombo attributeOptionCombo = attributeOptionComboId == null || attributeOptionComboId == -1 ? null : dataElementCategoryService.getDataElementCategoryOptionCombo( attributeOptionComboId );
+
+        log.info( "Validating data for " + ( group == null ? "all rules" : "group: " + group.getName() ) );
+
+        validationResults = new ArrayList<ValidationResult>( validationRuleService.validate( format
+                .parseDate( startDate ), format.parseDate( endDate ), organisationUnits, attributeOptionCombo, group, sendAlerts, format ) );
 
         maxExceeded = validationResults.size() > ValidationRuleService.MAX_INTERACTIVE_ALERTS;
 
@@ -193,8 +208,25 @@
 
         SessionUtils.setSessionVar( KEY_VALIDATIONRESULT, validationResults );
 
+        computeShowAttributeCombos();
+
         log.info( "Validation done" );
 
         return SUCCESS;
     }
+
+    private void computeShowAttributeCombos()
+    {
+        showAttributeCombos = false;
+
+        for ( ValidationResult result : validationResults )
+        {
+            if ( !result.getAttributeOptionCombo().isDefault() )
+            {
+                showAttributeCombos = true;
+
+                break;
+            }
+        }
+    }
 }

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/META-INF/dhis/beans.xml'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/META-INF/dhis/beans.xml	2014-03-28 01:51:34 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/META-INF/dhis/beans.xml	2014-05-18 00:49:40 +0000
@@ -146,6 +146,7 @@
     scope="prototype">
     <property name="validationRuleService" ref="org.hisp.dhis.validation.ValidationRuleService" />
     <property name="organisationUnitService" ref="org.hisp.dhis.organisationunit.OrganisationUnitService" />
+    <property name="dataElementCategoryService" ref="org.hisp.dhis.dataelement.DataElementCategoryService" />
   </bean>
 
   <bean id="org.hisp.dhis.validationrule.action.GetValidationResultDetailsAction"

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/org/hisp/dhis/validationrule/i18n_module.properties'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/org/hisp/dhis/validationrule/i18n_module.properties	2014-03-28 01:51:34 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/org/hisp/dhis/validationrule/i18n_module.properties	2014-05-18 00:49:40 +0000
@@ -141,6 +141,7 @@
 medium=Medium
 low=Low
 rule_type=Rule type
+attributes=Attributes
 validation=Validation
 surveillance=Surveillance
 organisation_unit_level=Organisation unit level

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultForm.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultForm.vm	2014-03-04 00:59:32 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultForm.vm	2014-05-18 00:49:40 +0000
@@ -84,6 +84,9 @@
 			<!-- Note: &nbsp; in the headers below keeps the jquery.tablesorter icons from overlapping the labels. -->
 			<th>$i18n.getString( "organisation_unit" )&nbsp;&nbsp;&nbsp;&nbsp;</th>
 			<th>$i18n.getString( "period" )&nbsp;&nbsp;&nbsp;&nbsp;</th>
+            #if( $showAttributeCombos )
+                <th>$i18n.getString( "attributes" )&nbsp;&nbsp;&nbsp;&nbsp;</th>
+            #end
 			<!-- Note: column-level sortInitialOrder only works in tablesorter 2.0.8 and later (an earlier version in use as of this writing.) --->
 			<th class="{sorter: 'importanceSorter', sortInitialOrder: 'desc'}">$i18n.getString( "importance" )&nbsp;&nbsp;&nbsp;&nbsp;</th>
 			<th class="{sorter: false}">$i18n.getString( "validation_rule" )</th>
@@ -98,6 +101,9 @@
 		<tr id="tr${result.id}">
 		  <td>$encoder.htmlEncode( $!result.source.name )</td>
 		  <td>$!format.formatPeriod( $result.period )</td>
+          #if( $showAttributeCombos )
+              <td>#if($result.attributeOptionCombo.isDefault()=='false') $encoder.htmlEncode( $!result.attributeOptionCombo.name ) #end</td>
+          #end
 		  <td>$encoder.htmlEncode( $i18n.getString( $!result.validationRule.importance ) )</td>
 		  <td>$encoder.htmlEncode( $!result.validationRule.getInstructionFallback() )</td>
 		  <td>$encoder.htmlEncode( $!result.leftsideValue )</td>