← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 21370: Code style fixes

 

------------------------------------------------------------
revno: 21370
committer: Ken Haase <kh@xxxxxxxxxxxxx>
branch nick: bzr.trunk
timestamp: Wed 2016-01-13 06:52:57 -0500
message:
  Code style fixes
modified:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRule.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/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/test/java/org/hisp/dhis/expression/ExpressionServiceTest.java
  dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/ArithmeticMean.java
  dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/Count.java
  dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/CustomFunctions.java
  dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MaxValue.java
  dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MedianValue.java
  dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MinValue.java
  dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/StandardDeviation.java
  dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/VectorSum.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/expression/ExpressionService.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java	2016-01-05 12:41:21 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java	2016-01-13 11:52:57 +0000
@@ -72,7 +72,6 @@
     String DAYS_SYMBOL = "[days]";
 
     String VARIABLE_EXPRESSION = "(#|D|A|I)\\{(([a-zA-Z]\\w{10})\\.?(\\w*))\\}";
-    String AGGREGATE_EXPRESSION = "#\\[([^\\]]*)\\]";
     String OPERAND_EXPRESSION = "#\\{([a-zA-Z]\\w{10})\\.?(\\w*)\\}";
     String PROGRAM_DATA_ELEMENT_EXPRESSION = "D\\{([a-zA-Z]\\w{10})\\.?([a-zA-Z]\\w{10})\\}";
     String OPERAND_UID_EXPRESSION = "([a-zA-Z]\\w{10})\\.?(\\w*)";
@@ -83,7 +82,6 @@
     String DAYS_EXPRESSION = "\\[days\\]";
 
     Pattern VARIABLE_PATTERN = Pattern.compile( VARIABLE_EXPRESSION );
-    Pattern AGGREGATE_PATTERN = Pattern.compile(AGGREGATE_EXPRESSION);
     Pattern OPERAND_PATTERN = Pattern.compile( OPERAND_EXPRESSION );
     Pattern OPERAND_UID_PATTERN = Pattern.compile( OPERAND_UID_EXPRESSION );
     Pattern PROGRAM_DATA_ELEMENT_PATTERN = Pattern.compile( PROGRAM_DATA_ELEMENT_EXPRESSION );

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRule.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRule.java	2016-01-12 00:42:41 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRule.java	2016-01-13 11:52:57 +0000
@@ -82,7 +82,8 @@
     private RuleType ruleType = RuleType.VALIDATION;
 
     /**
-     * The comparison operator to compare left and right expressions in the rule.
+     * The comparison operator to compare left and right expressions in the
+     * rule.
      */
     private Operator operator;
 
@@ -107,20 +108,22 @@
     private Set<ValidationRuleGroup> groups = new HashSet<>();
 
     /**
-     * The organisation unit level at which this rule is evaluated (Monitoring-type rules only).
+     * The organisation unit level at which this rule is evaluated
+     * (Monitoring-type rules only).
      */
     private Integer organisationUnitLevel;
 
     /**
      * The number of sequential right-side periods from which to collect samples
      * to average (Monitoring-type rules only). Sequential periods are those
-     * immediately preceding (or immediately following in previous years) the selected period.
+     * immediately preceding (or immediately following in previous years) the
+     * selected period.
      */
     private Integer sequentialSampleCount;
 
     /**
-     * The number of annual right-side periods from which to collect samples
-     * to average (Monitoring-type rules only). Annual periods are from previous
+     * The number of annual right-side periods from which to collect samples to
+     * average (Monitoring-type rules only). Annual periods are from previous
      * years. Samples collected from previous years can also include sequential
      * periods adjacent to the equivalent period in previous years.
      */
@@ -135,8 +138,8 @@
 
     }
 
-    public ValidationRule( String name, String description,
-        Operator operator, Expression leftSide, Expression rightSide )
+    public ValidationRule( String name, String description, Operator operator, Expression leftSide,
+        Expression rightSide )
     {
         this.name = name;
         this.description = description;
@@ -150,9 +153,9 @@
     // -------------------------------------------------------------------------
 
     /**
-     * Clears the left-side and right-side expressions. This can be useful, for example,
-     * before changing the validation rule period type, because the data elements
-     * allowed in the expressions depend on the period type.
+     * Clears the left-side and right-side expressions. This can be useful, for
+     * example, before changing the validation rule period type, because the
+     * data elements allowed in the expressions depend on the period type.
      */
     public void clearExpressions()
     {
@@ -183,8 +186,8 @@
     }
 
     /**
-     * Gets the validation rule description, but returns the validation rule name
-     * if there is no description.
+     * Gets the validation rule description, but returns the validation rule
+     * name if there is no description.
      *
      * @return the description (or name).
      */
@@ -194,9 +197,9 @@
     }
 
     /**
-     * Gets the data elements to evaluate for the current period. For validation-type
-     * rules this means all data elements. For monitoring-type rules this means just
-     * the left side elements.
+     * Gets the data elements to evaluate for the current period. For
+     * validation-type rules this means all data elements. For monitoring-type
+     * rules this means just the left side elements.
      *
      * @return the data elements to evaluate for the current period.
      */
@@ -214,9 +217,9 @@
     }
 
     /**
-     * Gets the data elements to compare against for past periods. For validation-type
-     * rules this returns null. For monitoring-type rules this is just the
-     * right side elements.
+     * Gets the data elements to compare against for past periods. For
+     * validation-type rules this returns null. For monitoring-type rules this
+     * is just the right side elements.
      *
      * @return the data elements to evaluate for past periods.
      */
@@ -254,7 +257,8 @@
         }
         else if ( leftSide != null && rightSide != null )
         {
-            return leftSide.getDescription() + " " + operator.getMathematicalOperator() + " " + rightSide.getDescription();
+            return leftSide.getDescription() + " " + operator.getMathematicalOperator() + " "
+                + rightSide.getDescription();
         }
         else
         {
@@ -376,7 +380,6 @@
         this.annualSampleCount = annualSampleCount;
     }
 
-
     @JsonProperty
     @JsonView( { DetailedView.class, ExportView.class } )
     @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0 )

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java	2016-01-08 19:07:20 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java	2016-01-13 11:52:57 +0000
@@ -365,7 +365,6 @@
 				else scan=start+1;
 			}
 		}
-
 		return aggregates;
 	}
 

=== 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	2016-01-04 02:27:49 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/Validator.java	2016-01-13 11:52:57 +0000
@@ -54,17 +54,18 @@
 public class Validator
 {
     /**
-     * Evaluates validation rules for a collection of organisation units.
-     * This method breaks the job down by organisation unit. It assigns the
+     * Evaluates validation rules for a collection of organisation units. This
+     * method breaks the job down by organisation unit. It assigns the
      * evaluation for each organisation unit to a task that can be evaluated
      * independently in a multi-threaded environment.
      * 
-     * @param sources the organisation units in which to run the validation rules
+     * @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 lastScheduledRun date/time of the most recent successful
-     *        scheduled monitoring run (needed only for scheduled runs)
+     * @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
@@ -74,15 +75,16 @@
      * @param currentUserService current user service
      * @return a collection of any validations that were found
      */
-    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,
-        UserService userService, CurrentUserService currentUserService )
+    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, UserService userService,
+        CurrentUserService currentUserService )
     {
-        ValidationRunContext context = ValidationRunContext.getNewContext( sources, periods,
-            attributeCombo, rules, constantService.getConstantMap(), ValidationRunType.SCHEDULED, lastScheduledRun,
-            expressionService, periodService, dataValueService, dataElementCategoryService, userService, currentUserService );
+        ValidationRunContext context = ValidationRunContext.getNewContext( sources, periods, attributeCombo, rules,
+            constantService.getConstantMap(), ValidationRunType.SCHEDULED, lastScheduledRun, expressionService,
+            periodService, dataValueService, dataElementCategoryService, userService, currentUserService );
 
         int threadPoolSize = getThreadPoolSize( context );
         ExecutorService executor = Executors.newFixedThreadPool( threadPoolSize );
@@ -97,7 +99,7 @@
         }
 
         executor.shutdown();
-        
+
         try
         {
             executor.awaitTermination( 6, TimeUnit.HOURS );
@@ -111,7 +113,7 @@
 
         return context.getValidationResults();
     }
-    
+
     /**
      * Determines how many threads we should use for testing validation rules.
      * 
@@ -121,18 +123,18 @@
     private static int getThreadPoolSize( ValidationRunContext context )
     {
         int threadPoolSize = SystemUtils.getCpuCores();
-        
+
         if ( threadPoolSize > 2 )
         {
             threadPoolSize--;
         }
-        
+
         if ( threadPoolSize > context.getCountOfSourcesToValidate() )
         {
             threadPoolSize = context.getCountOfSourcesToValidate();
         }
 
-    	return threadPoolSize;
+        return threadPoolSize;
     }
 
     /**
@@ -141,11 +143,13 @@
      * @param results
      * @param dataElementCategoryService
      */
-    private static void reloadAttributeOptionCombos( Collection<ValidationResult> results, DataElementCategoryService dataElementCategoryService )
+    private static void reloadAttributeOptionCombos( Collection<ValidationResult> results,
+        DataElementCategoryService dataElementCategoryService )
     {
         for ( ValidationResult result : results )
         {
-            result.setAttributeOptionCombo( dataElementCategoryService.getDataElementCategoryOptionCombo( result.getAttributeOptionCombo().getId() ) );
+            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	2016-01-06 17:14:10 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidatorThread.java	2016-01-13 11:52:57 +0000
@@ -55,6 +55,7 @@
 import org.hisp.dhis.period.Period;
 import org.hisp.dhis.period.PeriodType;
 import org.hisp.dhis.system.util.MathUtils;
+
 /**
  * Runs a validation task on a thread within a multi-threaded validation run.
  * <p>
@@ -63,609 +64,597 @@
  * @author Jim Grace
  */
 public class ValidatorThread
-implements Runnable
+    implements Runnable
 {
-	private static final Log log = LogFactory.getLog( ValidatorThread.class );
-
-	private OrganisationUnitExtended sourceX;
-
-	private ValidationRunContext context;
-
-	public ValidatorThread( OrganisationUnitExtended sourceX, ValidationRunContext context )
-	{
-		this.sourceX = sourceX;
-		this.context = context;
-	}
-
-	/**
-	 * Evaluates validation rules for a single organisation unit. This is the
-	 * central method in validation rule evaluation.
-	 */
-	@Override
-	public void run()
-	{
-		try
-		{
-			runInternal();
-		}
-		catch ( RuntimeException ex )
-		{
-			log.error( DebugUtils.getStackTrace( ex ) );
-
-			throw ex;
-		}
-	}
-
-	private void runInternal()
-	{
-		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, sourceDataElements );
-				context.getExpressionService().explodeValidationRuleExpressions( rules );
-
-				if ( !rules.isEmpty() )
-				{
-					Set<DataElement> recursiveCurrentDataElements = getRecursiveCurrentDataElements( rules );
-
-					for ( Period period : periodTypeX.getPeriods() )
-					{
-						MapMap<Integer, DataElementOperand, Date> lastUpdatedMap = new MapMap<>();
-						SetMap<Integer, DataElementOperand> incompleteValuesMap = new SetMap<>();
-						MapMap<Integer, DataElementOperand, Double> currentValueMap = getValueMap
-								( periodTypeX, periodTypeX.getDataElements(), sourceDataElements, recursiveCurrentDataElements,
-										periodTypeX.getAllowedPeriodTypes(), period, sourceX.getSource(), lastUpdatedMap, incompleteValuesMap );
-
-						log.trace( "Source " + sourceX.getSource().getName()
-								+ " [" + period.getStartDate() + " - " + period.getEndDate() + "]"
-								+ " currentValueMap[" + currentValueMap.size() + "]" );
-
-						for ( ValidationRule rule : rules )
-						{
-							if ( evaluateValidationCheck( currentValueMap, lastUpdatedMap, rule ) )
-							{
-								int n_years = rule.getAnnualSampleCount() == null ? 0 : rule.getAnnualSampleCount();
-								int window = rule.getSequentialSampleCount() == null ? 0 : rule
-										.getSequentialSampleCount();
-								Collection<PeriodType> periodTypes = context.getRuleXMap().get(rule).getAllowedPastPeriodTypes();
-
-								log.debug("Rule "+rule.getName()+" @"+period.getDisplayShortName()+" & "+sourceX.getSource()+
-										" window="+window+", years="+n_years);
-								Map<Integer, Double> leftSideValues = getRuleExpressionValueMap
-										( rule.getLeftSide(),currentValueMap, incompleteValuesMap,
-												sourceX.getSource(), period, window, n_years, periodTypeX,
-												periodTypes, lastUpdatedMap, sourceDataElements );
-
-								if ( !leftSideValues.isEmpty() || Operator.compulsory_pair.equals( rule.getOperator() ) )
-								{
-									Map<Integer, Double> rightSideValues =
-											getRuleExpressionValueMap
-											( rule.getRightSide(),currentValueMap, incompleteValuesMap,
-													sourceX.getSource(), period, window, n_years, periodTypeX,
-													periodTypes, lastUpdatedMap, sourceDataElements );
-
-									if ( !rightSideValues.isEmpty() || Operator.compulsory_pair.equals( rule.getOperator() ) )
-									{
-										Set<Integer> attributeOptionCombos = leftSideValues.keySet();
-
-										if ( Operator.compulsory_pair.equals( rule.getOperator() ) )
-										{
-											attributeOptionCombos = new HashSet<>( attributeOptionCombos );
-											attributeOptionCombos.addAll( rightSideValues.keySet() );
-										}
-
-										for ( int optionCombo : attributeOptionCombos )
-										{
-											Double leftSide = leftSideValues.get( optionCombo );
-											Double rightSide = rightSideValues.get( optionCombo );
-											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( optionCombo ), rule,
-																roundSignificant( zeroIfNull( leftSide ) ),
-																roundSignificant( zeroIfNull( rightSide ) ) ) );
-											}
-
-											log.debug( "Evaluated " + rule.getName()
-											+ ", combo id " + optionCombo + ": "
-											+ (violation ? "violation" : "OK") + " " + (leftSide == null ? "(null)" : leftSide.toString())
-											+ " " + rule.getOperator() + " " + (rightSide == null ? "(null)" : rightSide.toString())
-											+ " (" + context.getValidationResults().size() + " results)" );
-
-										}
-									}
-								}
-							}
-						}
-					}
-				}
-			}
-		}
-	}
-
-	/**
-	 * Gets the rules that should be evaluated for a given organisation unit and
-	 * period type.
-	 *
-	 * @param sourceX            the organisation unit extended information
-	 * @param periodTypeX        the period type extended information
-	 * @param sourceDataElements all data elements collected for this
-	 *                           organisation unit
-	 * @return set of rules for this org unit and period type
-	 */
-	private Set<ValidationRule> getRulesBySourceAndPeriodType( OrganisationUnitExtended sourceX,
-			PeriodTypeExtended periodTypeX, Collection<DataElement> sourceDataElements )
-	{
-		Set<ValidationRule> periodTypeRules = new HashSet<>();
-
-		for ( ValidationRule rule : periodTypeX.getRules() )
-		{
-			if ( rule.getRuleType() == RuleType.VALIDATION )
-			{
-				// For validation-type rules, include only rules where the
-				// organisation collects all the data elements in the rule.
-				// But if this is some funny kind of rule with no elements
-				// (like for testing), include it also.
-				Collection<DataElement> elements = rule.getCurrentDataElements();
-
-				if ( elements == null || elements.size() == 0 || sourceDataElements.containsAll( elements ) )
-				{
-					periodTypeRules.add( rule );
-				}
-			}
-			else
-			{
-				// For surveillance-type rules, include only rules for this
-				// organisation's unit level.
-				// The organisation may not be configured for the data elements
-				// because they could be aggregated from a lower level.
-				if ( rule.getOrganisationUnitLevel() == sourceX.getLevel() )
-				{
-					periodTypeRules.add( rule );
-				}
-			}
-		}
-
-		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 on the left or right sides.
-	 * <p>
-	 * 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
-	 * @return true if the rule should be evaluated with this data, false if not
-	 */
-	private boolean evaluateValidationCheck( MapMap<Integer, DataElementOperand, Double> currentValueMapMap,
-			MapMap<Integer, DataElementOperand, Date> lastUpdatedMapMap, ValidationRule rule )
-	{
-		boolean evaluate = true; // Assume true for now.
-
-		if ( ValidationRunType.SCHEDULED == context.getRunType() )
-		{
-			if ( context.getLastScheduledRun() != null ) // True if no previous scheduled run
-			{
-				if ( rule.getLastUpdated().before( context.getLastScheduledRun() ) )
-				{
-					// Get the "current" DataElementOperands from this rule:
-					// Left+Right sides for VALIDATION, Left side only for
-					// SURVEILLANCE.
-					Collection<DataElementOperand> deos = context.getExpressionService().getOperandsInExpression(
-							rule.getLeftSide().getExpression() );
-
-					if ( rule.getRuleType() == RuleType.VALIDATION )
-					{
-						// Make a copy so we can add to it.
-						deos = new HashSet<>( deos );
-						deos.addAll( context.getExpressionService().getOperandsInExpression( rule.getRightSide().getExpression() ) );
-					}
-
-					// Return true if any data is more recent than the last
-					// scheduled run, otherwise return false.
-					evaluate = false;
-
-					for ( Map.Entry<Integer, Map<DataElementOperand, Date>> entry : lastUpdatedMapMap.entrySet() )
-					{
-						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() );
-						}
-					}
-				}
-			}
-		}
-		return evaluate;
-	}
-
-	/**
-	 * Gets the data elements for which values should be fetched recursively if
-	 * they are not collected for an organisation unit.
-	 *
-	 * @param rules ValidationRules to be evaluated
-	 * @return the data elements to fetch recursively
-	 */
-	private Set<DataElement> getRecursiveCurrentDataElements( Set<ValidationRule> rules )
-	{
-		Set<DataElement> recursiveCurrentDataElements = new HashSet<>();
-
-		for ( ValidationRule rule : rules )
-		{
-			if ( rule.getRuleType() == RuleType.SURVEILLANCE && rule.getCurrentDataElements() != null )
-			{
-				recursiveCurrentDataElements.addAll( rule.getCurrentDataElements() );
-			}
-		}
-
-		return recursiveCurrentDataElements;
-	}
-
-
-	/**
-	 * Evaluates an expression, returning a map of values by attribute option
-	 * combo.
-	 *
-	 * @param expression          expression to evaluate.
-	 * @param valueMap            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> valueMap,
-			SetMap<Integer, DataElementOperand> incompleteValuesMap )
-	{
-		Map<Integer, Double> expressionValueMap = new HashMap<>();
-
-		for ( Map.Entry<Integer, Map<DataElementOperand, Double>> entry : valueMap.entrySet() )
-		{
-			Double value = context.getExpressionService().getExpressionValue( expression,
-					entry.getValue(), context.getConstantMap(), null, null,
-					incompleteValuesMap.getSet( entry.getKey() ), null );
-
-			if ( MathUtils.isValidDouble( value ) )
-			{
-				expressionValueMap.put( entry.getKey(), value );
-			}
-		}
-
-		return expressionValueMap;
-	}
-
-	/**
-	 * Gets data values for a given organisation unit and period, recursing if
-	 * necessary to sum the values from child organisation units.
-	 *
-	 * @param periodTypeX           period type which we are evaluating
-	 * @param ruleDataElements      data elements configured for the rule
-	 * @param sourceDataElements    data elements configured for the organisation
-	 *                              unit
-	 * @param recursiveDataElements data elements for which we will recurse if
-	 *                              necessary
-	 * @param allowedPeriodTypes    all the periods in which we might find the data
-	 *                              values
-	 * @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 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 MapMap<Integer, DataElementOperand, Double> getValueMap( PeriodTypeExtended periodTypeX,
-			Collection<DataElement> ruleDataElements, Collection<DataElement> sourceDataElements,
-			Set<DataElement> recursiveDataElements, Collection<PeriodType> allowedPeriodTypes, Period period,
-			OrganisationUnit source, MapMap<Integer, DataElementOperand, Date> lastUpdatedMap,
-			SetMap<Integer, DataElementOperand> incompleteValuesMap )
-	{
-		Set<DataElement> dataElementsToGet = new HashSet<>( ruleDataElements );
-		dataElementsToGet.retainAll( sourceDataElements );
-
-		log.trace( "getDataValueMapRecursive: source:" + source.getName()
-		+ " ruleDataElements[" + ruleDataElements.size()
-		+ "] sourceDataElements[" + sourceDataElements.size()
-		+ "] elementsToGet[" + dataElementsToGet.size()
-		+ "] recursiveDataElements[" + recursiveDataElements.size()
-		+ "] allowedPeriodTypes[" + allowedPeriodTypes.size() + "]" );
-
-		MapMap<Integer, DataElementOperand, Double> dataValueMap = null;
-
-		if ( dataElementsToGet.isEmpty() )
-		{
-			// We still might get something recursively
-			dataValueMap = new MapMap<>();
-		}
-		else
-		{
-			dataValueMap = context.getDataValueService().getDataValueMapByAttributeCombo( dataElementsToGet,
-					period.getStartDate(), source, allowedPeriodTypes, context.getAttributeCombo(),
-					context.getCogDimensionConstraints(), context.getCoDimensionConstraints(), lastUpdatedMap );
-		}
-
-		// See if there are any data elements we need to get recursively:
-		Set<DataElement> recursiveDataElementsNeeded = new HashSet<>( recursiveDataElements );
-		recursiveDataElementsNeeded.removeAll( dataElementsToGet );
-
-		if ( !recursiveDataElementsNeeded.isEmpty() )
-		{
-			int childCount = 0;
-			MapMap<Integer, DataElementOperand, Integer> childValueCounts = new MapMap<>();
-
-			for ( OrganisationUnit child : source.getChildren() )
-			{
-				Collection<DataElement> childDataElements = periodTypeX.getSourceDataElements().get( child );
-				MapMap<Integer, DataElementOperand, Double> childMap = getValueMap( periodTypeX,
-						recursiveDataElementsNeeded, childDataElements, recursiveDataElementsNeeded, allowedPeriodTypes,
-						period, child, lastUpdatedMap, incompleteValuesMap );
-
-				for ( Map.Entry<Integer, Map<DataElementOperand, Double>> entry : childMap.entrySet() )
-				{
-					int combo = entry.getKey();
-
-					for ( Map.Entry<DataElementOperand, Double> e : entry.getValue().entrySet() )
-					{
-						DataElementOperand deo = e.getKey();
-						Double childValue = e.getValue();
-
-						Double baseValue = dataValueMap.getValue( combo, deo );
-						dataValueMap.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<Integer, Map<DataElementOperand, Integer>> entry : childValueCounts.entrySet() )
-			{
-				int combo = entry.getKey();
-
-				for ( Map.Entry<DataElementOperand, Integer> e : entry.getValue().entrySet() )
-				{
-					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;
-	}
-
-	/* Generalized surveillance rules */
-
-	/**
-	 * Returns the right-side evaluated value of the validation rule.
-	 *
-	 * @param source             organisation unit being evaluated
-	 * @param periodTypeX        period type being evaluated
-	 * @param period             period being evaluated
-	 * @param rule               ValidationRule being evaluated
-	 * @param currentValueMap    current values already fetched
-	 * @param periodTypes        applicable period types
-	 * @param sourceDataElements the data elements collected by the organisation
-	 *                           unit
-	 * @return the right-side values, map by attribute category combo
-	 */
-	private ListMap<Integer, Double> getAggregateValueMap
-	(Expression expression,OrganisationUnit source,
-			Period period,int window, int n_years,
-			PeriodTypeExtended px,
-			Collection<PeriodType> periodTypes,
-			MapMap<Integer, DataElementOperand, Date> lastUpdatedMap,
-			Collection<DataElement> sourceDataElements)
-	{
-		ListMap<Integer,Double> results = new ListMap<Integer,Double>();
-		CalendarPeriodType periodType = (CalendarPeriodType) period.getPeriodType();
-		Calendar yearly = PeriodType.createCalendarInstance( period.getStartDate() );
-
-		for ( int years = 0; years <= n_years; years++ )
-		{
-			// Defensive copy because createPeriod mutates Calendar.
-			Calendar each_year = PeriodType.createCalendarInstance( yearly.getTime() );
-			// To track the period at the same time in preceding years.
-			Period base_period = periodType.createPeriod( each_year );
-
-			if (years>0) {
-				// For past years, fetch a window around the period at the
-				// same time of year as this period.
-				gatherPeriodValues(results,expression,source,
-						base_period,0,px,
-						periodTypes,lastUpdatedMap,
-						sourceDataElements);
-				if (window != 0)
-					gatherPeriodValues(results, expression, source, periodType.getNextPeriod(base_period), window-1, px,
-							periodTypes, lastUpdatedMap, sourceDataElements);
-			}
-			if (window != 0)
-				gatherPeriodValues(results, expression, source, periodType.getPreviousPeriod(base_period), 1-window, px,
-						periodTypes, lastUpdatedMap, sourceDataElements);
-
-			// Move to the previous year.
-			yearly.set( Calendar.YEAR, yearly.get( Calendar.YEAR ) - 1 );
-		}
-
-		return results;
-	}
-
-	/**
-	 * Gathers the values of an expression for a given organisation unit
-     and period, accumulating a range of values around the given period.
-	 * <p>
-	 * Note that for a surveillance-type rule, evaluating the right side
-	 * expression can result in sampling multiple periods and/or child
-	 * organisation units.
-	 *
-	 * @param results            the ListMap into which results will be stored
-	 * @param expression         the expression to be evaluated
-	 * @param source             the organisation unit
-	 * @param period             the main period for the validation rule evaluation
-	 * @param window             how many periods (before and after) to collect
-	 * @param px                 the period type extended information
-	 * @param periodTypes        the period types in which the data may exist
-	 * @param sourceElements     the data elements configured for this
-	 *                           organisation unit
-	 */
-	private void gatherPeriodValues( ListMap<Integer, Double> results,
-			Expression expression,
-			OrganisationUnit source,
-			Period period,int window,
-			PeriodTypeExtended px,
-			Collection<PeriodType> periodTypes,
-			MapMap<Integer, DataElementOperand, Date> lastUpdatedMap,
-			Collection<DataElement> sourceElements)
- {
-		CalendarPeriodType periodType = (CalendarPeriodType) period.getPeriodType();
-		Period periodInstance = context.getPeriodService().getPeriod(period.getStartDate(), period.getEndDate(),
-				periodType);
-		if (periodInstance == null)
-			return;
-		Set<DataElement> dataElements = getExpressionDataElements(expression);
-		SetMap<Integer, DataElementOperand> incompleteValuesMap = new SetMap<>();
-		MapMap<Integer, DataElementOperand, Double> dataValueMapByAttributeCombo = getValueMap(px, dataElements,
-				sourceElements, dataElements, periodTypes, periodInstance, source, lastUpdatedMap, incompleteValuesMap);
-		Map<Integer, Double> eValues = getExpressionValueMap(expression, dataValueMapByAttributeCombo,
-				incompleteValuesMap);
-		int direction = ((window < 0) ? (-1) : (window > 0) ? (1) : (0));
-		int steps = ((direction > 0) ? (window) : (direction < 0) ? (-window) : (0));
-		results.putValueMap(eValues);
-		log.debug("Gathering '" + expression.getExpression() + "' " + "at " + period + " (" + window + ") "
-				+ "from " + source.getName() + " starting with:\n\t" + eValues);
-		if (direction == 0)
-			return;
-		Period scan = new Period(periodInstance);
-		for (int count = 0; count < steps; count++) {
-			if (direction < 0)
-				scan = periodType.getPreviousPeriod(scan);
-			else
-				scan = periodType.getNextPeriod(scan);
-			gatherPeriodValues(results, expression, source, scan, 0, px, 
-					periodTypes, lastUpdatedMap, sourceElements);
-		}
-	}
-
-	/**
-	 * Returns the data elements referenced in an expression, as a set.
-	 *
-	 * This will return an empty set if e.getDataElementsInExpression returns null
-	 *
-	 * @param expression          expression to evaluate.
-	 * @return a Set of DataElement(s)
-	 */
-	private Set<DataElement> getExpressionDataElements(Expression e)
-	{
-		Set<DataElement> elts=e.getDataElementsInExpression();
-		if (elts==null)
-			return new HashSet<DataElement>();
-		else return elts;
-	}
-
-	/**
-	 * Evaluates an expression, returning a map of values by attribute option
-	 * combo.
-	 *
-	 * @param expression          expression to evaluate.
-	 * @param valueMap            Map of value maps, by attribute option combo.
-	 * @param incompleteValuesMap map of values that were incomplete.
-	 * @return map of values.
-	 */
-	private Map<Integer, Double> getRuleExpressionValueMap( Expression expression,
-			MapMap<Integer, DataElementOperand, Double> valueMap,
-			SetMap<Integer, DataElementOperand> incompleteValuesMap,
-			OrganisationUnit source,
-			Period period,int window,int n_years,
-			PeriodTypeExtended px,
-			Collection<PeriodType> periodTypes,
-			MapMap<Integer, DataElementOperand, Date> lastUpdatedMap,
-			Collection<DataElement> sourceElements)
-	{
-		Map<Integer, Double> expressionValueMap = new HashMap<>();
-		Map<Integer, ListMap<String, Double>> aggregateValuesMap = new HashMap<>();
-		Set<String> aggregates=context.getExpressionService().getAggregatesInExpression(expression.getExpression());
-		
-		if (aggregates.size()==0)
-			return getExpressionValueMap(expression,valueMap,incompleteValuesMap);
-
-		for (String subExpression: aggregates)
-		{
-			Expression subexp=new Expression(subExpression,"aggregated",
-					new HashSet<DataElement>(sourceElements));
-			ListMap<Integer,Double> aggregateValues=getAggregateValueMap
-					(subexp,source,period,window,n_years,
-							px,periodTypes,lastUpdatedMap,sourceElements);
-			for (Integer attributeOptionCombo: aggregateValues.keySet())
-			{
-				ListMap<String, Double> aggmap;
-				if (aggregateValuesMap.containsKey(attributeOptionCombo))
-					aggmap=aggregateValuesMap.get(attributeOptionCombo);
-				else {
-					aggmap=new ListMap<>();
-					aggregateValuesMap.put(attributeOptionCombo, aggmap);}
-
-				aggmap.put(subExpression, aggregateValues.get(attributeOptionCombo));
-			}
-		}
-		
-		for ( Map.Entry<Integer, Map<DataElementOperand, Double>> entry : valueMap.entrySet() )
-		{
-			Double value = context.getExpressionService().getExpressionValue( expression,
-					entry.getValue(), context.getConstantMap(), null, null,
-					incompleteValuesMap.getSet( entry.getKey() ),
-					aggregateValuesMap.get(entry.getKey()));
-
-			if ( MathUtils.isValidDouble( value ) )
-			{
-				expressionValueMap.put( entry.getKey(), value );
-			}
-		}
-		
-		return expressionValueMap;
-	}
-
-
+    private static final Log log = LogFactory.getLog( ValidatorThread.class );
+
+    private OrganisationUnitExtended sourceX;
+
+    private ValidationRunContext context;
+
+    public ValidatorThread( OrganisationUnitExtended sourceX, ValidationRunContext context )
+    {
+        this.sourceX = sourceX;
+        this.context = context;
+    }
+
+    /**
+     * Evaluates validation rules for a single organisation unit. This is the
+     * central method in validation rule evaluation.
+     */
+    @Override
+    public void run()
+    {
+        try
+        {
+            runInternal();
+        }
+        catch ( RuntimeException ex )
+        {
+            log.error( DebugUtils.getStackTrace( ex ) );
+
+            throw ex;
+        }
+    }
+
+    private void runInternal()
+    {
+        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, sourceDataElements );
+                context.getExpressionService().explodeValidationRuleExpressions( rules );
+
+                if ( !rules.isEmpty() )
+                {
+                    Set<DataElement> recursiveCurrentDataElements = getRecursiveCurrentDataElements( rules );
+
+                    for ( Period period : periodTypeX.getPeriods() )
+                    {
+                        MapMap<Integer, DataElementOperand, Date> lastUpdatedMap = new MapMap<>();
+                        SetMap<Integer, DataElementOperand> incompleteValuesMap = new SetMap<>();
+                        MapMap<Integer, DataElementOperand, Double> currentValueMap = getValueMap( periodTypeX,
+                            periodTypeX.getDataElements(), sourceDataElements, recursiveCurrentDataElements,
+                            periodTypeX.getAllowedPeriodTypes(), period, sourceX.getSource(), lastUpdatedMap,
+                            incompleteValuesMap );
+
+                        log.trace( "Source " + sourceX.getSource().getName() + " [" + period.getStartDate() + " - "
+                            + period.getEndDate() + "]" + " currentValueMap[" + currentValueMap.size() + "]" );
+
+                        for ( ValidationRule rule : rules )
+                        {
+                            if ( evaluateValidationCheck( currentValueMap, lastUpdatedMap, rule ) )
+                            {
+                                int n_years = rule.getAnnualSampleCount() == null ? 0 : rule.getAnnualSampleCount();
+                                int window = rule.getSequentialSampleCount() == null ? 0
+                                    : rule.getSequentialSampleCount();
+                                Collection<PeriodType> periodTypes = context.getRuleXMap().get( rule )
+                                    .getAllowedPastPeriodTypes();
+
+                                log.debug( "Rule " + rule.getName() + " @" + period.getDisplayShortName() + " & "
+                                    + sourceX.getSource() + " window=" + window + ", years=" + n_years );
+                                Map<Integer, Double> leftSideValues = getRuleExpressionValueMap( rule.getLeftSide(),
+                                    currentValueMap, incompleteValuesMap, sourceX.getSource(), period, window, n_years,
+                                    periodTypeX, periodTypes, lastUpdatedMap, sourceDataElements );
+
+                                if ( !leftSideValues.isEmpty()
+                                    || Operator.compulsory_pair.equals( rule.getOperator() ) )
+                                {
+                                    Map<Integer, Double> rightSideValues = getRuleExpressionValueMap(
+                                        rule.getRightSide(), currentValueMap, incompleteValuesMap, sourceX.getSource(),
+                                        period, window, n_years, periodTypeX, periodTypes, lastUpdatedMap,
+                                        sourceDataElements );
+
+                                    if ( !rightSideValues.isEmpty()
+                                        || Operator.compulsory_pair.equals( rule.getOperator() ) )
+                                    {
+                                        Set<Integer> attributeOptionCombos = leftSideValues.keySet();
+
+                                        if ( Operator.compulsory_pair.equals( rule.getOperator() ) )
+                                        {
+                                            attributeOptionCombos = new HashSet<>( attributeOptionCombos );
+                                            attributeOptionCombos.addAll( rightSideValues.keySet() );
+                                        }
+
+                                        for ( int optionCombo : attributeOptionCombos )
+                                        {
+                                            Double leftSide = leftSideValues.get( optionCombo );
+                                            Double rightSide = rightSideValues.get( optionCombo );
+                                            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( optionCombo ),
+                                                        rule, roundSignificant( zeroIfNull( leftSide ) ),
+                                                        roundSignificant( zeroIfNull( rightSide ) ) ) );
+                                            }
+
+                                            log.debug( "Evaluated " + rule.getName() + ", combo id " + optionCombo
+                                                + ": " + (violation ? "violation" : "OK") + " "
+                                                + (leftSide == null ? "(null)" : leftSide.toString()) + " "
+                                                + rule.getOperator() + " "
+                                                + (rightSide == null ? "(null)" : rightSide.toString()) + " ("
+                                                + context.getValidationResults().size() + " results)" );
+
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Gets the rules that should be evaluated for a given organisation unit and
+     * period type.
+     *
+     * @param sourceX the organisation unit extended information
+     * @param periodTypeX the period type extended information
+     * @param sourceDataElements all data elements collected for this
+     *        organisation unit
+     * @return set of rules for this org unit and period type
+     */
+    private Set<ValidationRule> getRulesBySourceAndPeriodType( OrganisationUnitExtended sourceX,
+        PeriodTypeExtended periodTypeX, Collection<DataElement> sourceDataElements )
+    {
+        Set<ValidationRule> periodTypeRules = new HashSet<>();
+
+        for ( ValidationRule rule : periodTypeX.getRules() )
+        {
+            if ( rule.getRuleType() == RuleType.VALIDATION )
+            {
+                // For validation-type rules, include only rules where the
+                // organisation collects all the data elements in the rule.
+                // But if this is some funny kind of rule with no elements
+                // (like for testing), include it also.
+                Collection<DataElement> elements = rule.getCurrentDataElements();
+
+                if ( elements == null || elements.size() == 0 || sourceDataElements.containsAll( elements ) )
+                {
+                    periodTypeRules.add( rule );
+                }
+            }
+            else
+            {
+                // For surveillance-type rules, include only rules for this
+                // organisation's unit level.
+                // The organisation may not be configured for the data elements
+                // because they could be aggregated from a lower level.
+                if ( rule.getOrganisationUnitLevel() == sourceX.getLevel() )
+                {
+                    periodTypeRules.add( rule );
+                }
+            }
+        }
+
+        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 on the left or right sides.
+     * <p>
+     * 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
+     * @return true if the rule should be evaluated with this data, false if not
+     */
+    private boolean evaluateValidationCheck( MapMap<Integer, DataElementOperand, Double> currentValueMapMap,
+        MapMap<Integer, DataElementOperand, Date> lastUpdatedMapMap, ValidationRule rule )
+    {
+        boolean evaluate = true; // Assume true for now.
+
+        if ( ValidationRunType.SCHEDULED == context.getRunType() )
+        {
+            if ( context.getLastScheduledRun() != null ) // True if no previous
+                                                         // scheduled run
+            {
+                if ( rule.getLastUpdated().before( context.getLastScheduledRun() ) )
+                {
+                    // Get the "current" DataElementOperands from this rule:
+                    // Left+Right sides for VALIDATION, Left side only for
+                    // SURVEILLANCE.
+                    Collection<DataElementOperand> deos = context.getExpressionService()
+                        .getOperandsInExpression( rule.getLeftSide().getExpression() );
+
+                    if ( rule.getRuleType() == RuleType.VALIDATION )
+                    {
+                        // Make a copy so we can add to it.
+                        deos = new HashSet<>( deos );
+                        deos.addAll( context.getExpressionService()
+                            .getOperandsInExpression( rule.getRightSide().getExpression() ) );
+                    }
+
+                    // Return true if any data is more recent than the last
+                    // scheduled run, otherwise return false.
+                    evaluate = false;
+
+                    for ( Map.Entry<Integer, Map<DataElementOperand, Date>> entry : lastUpdatedMapMap.entrySet() )
+                    {
+                        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() );
+                        }
+                    }
+                }
+            }
+        }
+        return evaluate;
+    }
+
+    /**
+     * Gets the data elements for which values should be fetched recursively if
+     * they are not collected for an organisation unit.
+     *
+     * @param rules ValidationRules to be evaluated
+     * @return the data elements to fetch recursively
+     */
+    private Set<DataElement> getRecursiveCurrentDataElements( Set<ValidationRule> rules )
+    {
+        Set<DataElement> recursiveCurrentDataElements = new HashSet<>();
+
+        for ( ValidationRule rule : rules )
+        {
+            if ( rule.getRuleType() == RuleType.SURVEILLANCE && rule.getCurrentDataElements() != null )
+            {
+                recursiveCurrentDataElements.addAll( rule.getCurrentDataElements() );
+            }
+        }
+
+        return recursiveCurrentDataElements;
+    }
+
+    /**
+     * Evaluates an expression, returning a map of values by attribute option
+     * combo.
+     *
+     * @param expression expression to evaluate.
+     * @param valueMap 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> valueMap, SetMap<Integer, DataElementOperand> incompleteValuesMap )
+    {
+        Map<Integer, Double> expressionValueMap = new HashMap<>();
+
+        for ( Map.Entry<Integer, Map<DataElementOperand, Double>> entry : valueMap.entrySet() )
+        {
+            Double value = context.getExpressionService().getExpressionValue( expression, entry.getValue(),
+                context.getConstantMap(), null, null, incompleteValuesMap.getSet( entry.getKey() ), null );
+
+            if ( MathUtils.isValidDouble( value ) )
+            {
+                expressionValueMap.put( entry.getKey(), value );
+            }
+        }
+
+        return expressionValueMap;
+    }
+
+    /**
+     * Gets data values for a given organisation unit and period, recursing if
+     * necessary to sum the values from child organisation units.
+     *
+     * @param periodTypeX period type which we are evaluating
+     * @param ruleDataElements data elements configured for the rule
+     * @param sourceDataElements data elements configured for the organisation
+     *        unit
+     * @param recursiveDataElements data elements for which we will recurse if
+     *        necessary
+     * @param allowedPeriodTypes all the periods in which we might find the data
+     *        values
+     * @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 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 MapMap<Integer, DataElementOperand, Double> getValueMap( PeriodTypeExtended periodTypeX,
+        Collection<DataElement> ruleDataElements, Collection<DataElement> sourceDataElements,
+        Set<DataElement> recursiveDataElements, Collection<PeriodType> allowedPeriodTypes, Period period,
+        OrganisationUnit source, MapMap<Integer, DataElementOperand, Date> lastUpdatedMap,
+        SetMap<Integer, DataElementOperand> incompleteValuesMap )
+    {
+        Set<DataElement> dataElementsToGet = new HashSet<>( ruleDataElements );
+        dataElementsToGet.retainAll( sourceDataElements );
+
+        log.trace( "getDataValueMapRecursive: source:" + source.getName() + " ruleDataElements["
+            + ruleDataElements.size() + "] sourceDataElements[" + sourceDataElements.size() + "] elementsToGet["
+            + dataElementsToGet.size() + "] recursiveDataElements[" + recursiveDataElements.size()
+            + "] allowedPeriodTypes[" + allowedPeriodTypes.size() + "]" );
+
+        MapMap<Integer, DataElementOperand, Double> dataValueMap = null;
+
+        if ( dataElementsToGet.isEmpty() )
+        {
+            // We still might get something recursively
+            dataValueMap = new MapMap<>();
+        }
+        else
+        {
+            dataValueMap = context.getDataValueService().getDataValueMapByAttributeCombo( dataElementsToGet,
+                period.getStartDate(), source, allowedPeriodTypes, context.getAttributeCombo(),
+                context.getCogDimensionConstraints(), context.getCoDimensionConstraints(), lastUpdatedMap );
+        }
+
+        // See if there are any data elements we need to get recursively:
+        Set<DataElement> recursiveDataElementsNeeded = new HashSet<>( recursiveDataElements );
+        recursiveDataElementsNeeded.removeAll( dataElementsToGet );
+
+        if ( !recursiveDataElementsNeeded.isEmpty() )
+        {
+            int childCount = 0;
+            MapMap<Integer, DataElementOperand, Integer> childValueCounts = new MapMap<>();
+
+            for ( OrganisationUnit child : source.getChildren() )
+            {
+                Collection<DataElement> childDataElements = periodTypeX.getSourceDataElements().get( child );
+                MapMap<Integer, DataElementOperand, Double> childMap = getValueMap( periodTypeX,
+                    recursiveDataElementsNeeded, childDataElements, recursiveDataElementsNeeded, allowedPeriodTypes,
+                    period, child, lastUpdatedMap, incompleteValuesMap );
+
+                for ( Map.Entry<Integer, Map<DataElementOperand, Double>> entry : childMap.entrySet() )
+                {
+                    int combo = entry.getKey();
+
+                    for ( Map.Entry<DataElementOperand, Double> e : entry.getValue().entrySet() )
+                    {
+                        DataElementOperand deo = e.getKey();
+                        Double childValue = e.getValue();
+
+                        Double baseValue = dataValueMap.getValue( combo, deo );
+                        dataValueMap.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<Integer, Map<DataElementOperand, Integer>> entry : childValueCounts.entrySet() )
+            {
+                int combo = entry.getKey();
+
+                for ( Map.Entry<DataElementOperand, Integer> e : entry.getValue().entrySet() )
+                {
+                    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;
+    }
+
+    /* Generalized surveillance rules */
+
+    /**
+     * Returns the right-side evaluated value of the validation rule.
+     *
+     * @param source organisation unit being evaluated
+     * @param periodTypeX period type being evaluated
+     * @param period period being evaluated
+     * @param rule ValidationRule being evaluated
+     * @param currentValueMap current values already fetched
+     * @param periodTypes applicable period types
+     * @param sourceDataElements the data elements collected by the organisation
+     *        unit
+     * @return the right-side values, map by attribute category combo
+     */
+    private ListMap<Integer, Double> getAggregateValueMap( Expression expression, OrganisationUnit source,
+        Period period, int window, int n_years, PeriodTypeExtended px, Collection<PeriodType> periodTypes,
+        MapMap<Integer, DataElementOperand, Date> lastUpdatedMap, Collection<DataElement> sourceDataElements )
+    {
+        ListMap<Integer, Double> results = new ListMap<Integer, Double>();
+        CalendarPeriodType periodType = (CalendarPeriodType) period.getPeriodType();
+        Calendar yearly = PeriodType.createCalendarInstance( period.getStartDate() );
+
+        for ( int years = 0; years <= n_years; years++ )
+        {
+            // Defensive copy because createPeriod mutates Calendar.
+            Calendar each_year = PeriodType.createCalendarInstance( yearly.getTime() );
+            // To track the period at the same time in preceding years.
+            Period base_period = periodType.createPeriod( each_year );
+
+            if ( years > 0 )
+            {
+                // For past years, fetch a window around the period at the
+                // same time of year as this period.
+                gatherPeriodValues( results, expression, source, base_period, 0, px, periodTypes, lastUpdatedMap,
+                    sourceDataElements );
+                if ( window != 0 )
+                    gatherPeriodValues( results, expression, source, periodType.getNextPeriod( base_period ),
+                        window - 1, px, periodTypes, lastUpdatedMap, sourceDataElements );
+            }
+            if ( window != 0 )
+                gatherPeriodValues( results, expression, source, periodType.getPreviousPeriod( base_period ),
+                    1 - window, px, periodTypes, lastUpdatedMap, sourceDataElements );
+
+            // Move to the previous year.
+            yearly.set( Calendar.YEAR, yearly.get( Calendar.YEAR ) - 1 );
+        }
+
+        return results;
+    }
+
+    /**
+     * Gathers the values of an expression for a given organisation unit and
+     * period, accumulating a range of values around the given period.
+     * <p>
+     * Note that for a surveillance-type rule, evaluating the right side
+     * expression can result in sampling multiple periods and/or child
+     * organisation units.
+     *
+     * @param results the ListMap into which results will be stored
+     * @param expression the expression to be evaluated
+     * @param source the organisation unit
+     * @param period the main period for the validation rule evaluation
+     * @param window how many periods (before and after) to collect
+     * @param px the period type extended information
+     * @param periodTypes the period types in which the data may exist
+     * @param sourceElements the data elements configured for this organisation
+     *        unit
+     */
+    private void gatherPeriodValues( ListMap<Integer, Double> results, Expression expression, OrganisationUnit source,
+        Period period, int window, PeriodTypeExtended px, Collection<PeriodType> periodTypes,
+        MapMap<Integer, DataElementOperand, Date> lastUpdatedMap, Collection<DataElement> sourceElements )
+    {
+        CalendarPeriodType periodType = (CalendarPeriodType) period.getPeriodType();
+        Period periodInstance = context.getPeriodService().getPeriod( period.getStartDate(), period.getEndDate(),
+            periodType );
+        if ( periodInstance == null )
+            return;
+        Set<DataElement> dataElements = getExpressionDataElements( expression );
+        SetMap<Integer, DataElementOperand> incompleteValuesMap = new SetMap<>();
+        MapMap<Integer, DataElementOperand, Double> dataValueMapByAttributeCombo = getValueMap( px, dataElements,
+            sourceElements, dataElements, periodTypes, periodInstance, source, lastUpdatedMap, incompleteValuesMap );
+        Map<Integer, Double> eValues = getExpressionValueMap( expression, dataValueMapByAttributeCombo,
+            incompleteValuesMap );
+        int direction = ((window < 0) ? (-1) : (window > 0) ? (1) : (0));
+        int steps = ((direction > 0) ? (window) : (direction < 0) ? (-window) : (0));
+        results.putValueMap( eValues );
+        log.debug( "Gathering '" + expression.getExpression() + "' " + "at " + period + " (" + window + ") " + "from "
+            + source.getName() + " starting with:\n\t" + eValues );
+        if ( direction == 0 )
+            return;
+        Period scan = new Period( periodInstance );
+        for ( int count = 0; count < steps; count++ )
+        {
+            if ( direction < 0 )
+                scan = periodType.getPreviousPeriod( scan );
+            else
+                scan = periodType.getNextPeriod( scan );
+            gatherPeriodValues( results, expression, source, scan, 0, px, periodTypes, lastUpdatedMap, sourceElements );
+        }
+    }
+
+    /**
+     * Returns the data elements referenced in an expression, as a set.
+     *
+     * This will return an empty set if e.getDataElementsInExpression returns
+     * null
+     *
+     * @param expression expression to evaluate.
+     * @return a Set of DataElement(s)
+     */
+    private Set<DataElement> getExpressionDataElements( Expression e )
+    {
+        Set<DataElement> elts = e.getDataElementsInExpression();
+        if ( elts == null )
+            return new HashSet<DataElement>();
+        else
+            return elts;
+    }
+
+    /**
+     * Evaluates an expression, returning a map of values by attribute option
+     * combo.
+     *
+     * @param expression expression to evaluate.
+     * @param valueMap Map of value maps, by attribute option combo.
+     * @param incompleteValuesMap map of values that were incomplete.
+     * @return map of values.
+     */
+    private Map<Integer, Double> getRuleExpressionValueMap( Expression expression,
+        MapMap<Integer, DataElementOperand, Double> valueMap, SetMap<Integer, DataElementOperand> incompleteValuesMap,
+        OrganisationUnit source, Period period, int window, int n_years, PeriodTypeExtended px,
+        Collection<PeriodType> periodTypes, MapMap<Integer, DataElementOperand, Date> lastUpdatedMap,
+        Collection<DataElement> sourceElements )
+    {
+        Map<Integer, Double> expressionValueMap = new HashMap<>();
+        Map<Integer, ListMap<String, Double>> aggregateValuesMap = new HashMap<>();
+        Set<String> aggregates = context.getExpressionService().getAggregatesInExpression( expression.getExpression() );
+
+        if ( aggregates.size() == 0 )
+            return getExpressionValueMap( expression, valueMap, incompleteValuesMap );
+
+        for ( String subExpression : aggregates )
+        {
+            Expression subexp = new Expression( subExpression, "aggregated",
+                new HashSet<DataElement>( sourceElements ) );
+            ListMap<Integer, Double> aggregateValues = getAggregateValueMap( subexp, source, period, window, n_years,
+                px, periodTypes, lastUpdatedMap, sourceElements );
+            for ( Integer attributeOptionCombo : aggregateValues.keySet() )
+            {
+                ListMap<String, Double> aggmap;
+                if ( aggregateValuesMap.containsKey( attributeOptionCombo ) )
+                    aggmap = aggregateValuesMap.get( attributeOptionCombo );
+                else
+                {
+                    aggmap = new ListMap<>();
+                    aggregateValuesMap.put( attributeOptionCombo, aggmap );
+                }
+
+                aggmap.put( subExpression, aggregateValues.get( attributeOptionCombo ) );
+            }
+        }
+
+        for ( Map.Entry<Integer, Map<DataElementOperand, Double>> entry : valueMap.entrySet() )
+        {
+            Double value = context.getExpressionService().getExpressionValue( expression, entry.getValue(),
+                context.getConstantMap(), null, null, incompleteValuesMap.getSet( entry.getKey() ),
+                aggregateValuesMap.get( entry.getKey() ) );
+
+            if ( MathUtils.isValidDouble( value ) )
+            {
+                expressionValueMap.put( entry.getKey(), value );
+            }
+        }
+
+        return expressionValueMap;
+    }
 
 }

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/expression/ExpressionServiceTest.java'
--- dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/expression/ExpressionServiceTest.java	2016-01-08 19:08:02 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/expression/ExpressionServiceTest.java	2016-01-13 11:52:57 +0000
@@ -149,9 +149,9 @@
     private String expressionG;
     private String expressionH;
     private String expressionI;
+    private String expressionK;
     private String expressionJ;
-    private String expressionJ0;
-    private String expressionJ1;
+    private String expressionL;
 
     private String descriptionA;
     private String descriptionB;
@@ -264,9 +264,9 @@
         expressionH = "#{" + deA.getUid() + SEPARATOR + coc.getUid() + "}*OUG{" + groupA.getUid() + "}";        
         expressionI = "#{" + opA.getDimensionItem() + "}*" + "#{" + deB.getDimensionItem() + "}+" + "C{" + constantA.getUid() + "}+5-" +
             "D{" + pdeA.getDimensionItem() + "}+" + "A{" + pteaA.getDimensionItem() + "}-10+" + "I{" + piA.getDimensionItem() + "}";
-        expressionJ0 = "#{" + opA.getDimensionItem() + "}+#{" + opB.getDimensionItem() + "}";
-        expressionJ = "1.5*AVG("+expressionJ0+")";
-        expressionJ1 = "AVG("+expressionJ0+")+1.5*STDDEV("+expressionJ0+")";
+        expressionJ = "#{" + opA.getDimensionItem() + "}+#{" + opB.getDimensionItem() + "}";
+        expressionK = "1.5*AVG("+expressionJ+")";
+        expressionL = "AVG("+expressionJ+")+1.5*STDDEV("+expressionJ+")";
 
         descriptionA = "Expression A";
         descriptionB = "Expression B";
@@ -358,27 +358,27 @@
     @Test
     public void testGetAggregatesInExpression()
     {
-        Set<DataElement> dataElements = expressionService.getDataElementsInExpression( expressionJ );
-        Set<String> aggregates=expressionService.getAggregatesInExpression(expressionJ.toString());
+        Set<DataElement> dataElements = expressionService.getDataElementsInExpression( expressionK );
+        Set<String> aggregates=expressionService.getAggregatesInExpression(expressionK.toString());
 
         assertTrue( dataElements.size() == 2 );
         assertTrue( dataElements.contains( deA ) );
         assertTrue( dataElements.contains( deB ) );
   
         assertEquals( 1, aggregates.size() );
-        for (String subexp: aggregates) assertEquals(expressionJ0,subexp);
-        assertTrue( aggregates.contains( expressionJ0 ) );
+        for (String subexp: aggregates) assertEquals(expressionJ,subexp);
+        assertTrue( aggregates.contains( expressionJ ) );
         
-        dataElements=expressionService.getDataElementsInExpression( expressionJ );
-        aggregates=expressionService.getAggregatesInExpression(expressionJ.toString());
+        dataElements=expressionService.getDataElementsInExpression( expressionK );
+        aggregates=expressionService.getAggregatesInExpression(expressionK.toString());
         
         assertTrue( dataElements.size() == 2 );
         assertTrue( dataElements.contains( deA ) );
         assertTrue( dataElements.contains( deB ) );
   
         assertEquals( 1, aggregates.size() );
-        for (String subexp: aggregates) assertEquals(expressionJ0,subexp);
-        assertTrue( aggregates.contains( expressionJ0 ) );
+        for (String subexp: aggregates) assertEquals(expressionJ,subexp);
+        assertTrue( aggregates.contains( expressionJ ) );
 
         
     }
@@ -496,8 +496,8 @@
         assertTrue( expressionService.expressionIsValid( expressionD ).isValid() );
         assertTrue( expressionService.expressionIsValid( expressionE ).isValid() );
         assertTrue( expressionService.expressionIsValid( expressionH ).isValid() );
-        assertTrue( expressionService.expressionIsValid( expressionJ ).isValid() );
-        assertTrue( expressionService.expressionIsValid( expressionJ1 ).isValid() );
+        assertTrue( expressionService.expressionIsValid( expressionK ).isValid() );
+        assertTrue( expressionService.expressionIsValid( expressionL ).isValid() );
 
         expressionA = "#{nonExisting" + SEPARATOR + coc.getUid() + "} + 12";
 
@@ -516,7 +516,7 @@
 
         assertEquals( ExpressionValidationOutcome.EXPRESSION_IS_NOT_WELL_FORMED, expressionService.expressionIsValid( expressionA ) );
         
-        expressionA=expressionJ.replace(")", "");
+        expressionA=expressionK.replace(")", "");
 
         assertEquals( ExpressionValidationOutcome.EXPRESSION_IS_NOT_WELL_FORMED, expressionService.expressionIsValid( expressionA ) );
         

=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/ArithmeticMean.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/ArithmeticMean.java	2016-01-06 17:14:10 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/ArithmeticMean.java	2016-01-13 11:52:57 +0000
@@ -8,34 +8,45 @@
 import org.nfunk.jep.function.PostfixMathCommand;
 import org.nfunk.jep.function.PostfixMathCommandI;
 
-public class ArithmeticMean extends PostfixMathCommand 
-implements PostfixMathCommandI 
+public class ArithmeticMean
+    extends PostfixMathCommand
+    implements PostfixMathCommandI
 {
-	public ArithmeticMean() {
-		numberOfParameters = 1;
-	}
-
-	@Override
-	@SuppressWarnings({ "rawtypes", "unchecked" })
-	public void run(Stack inStack) throws ParseException {
-		
-		// check the stack
-		checkStack(inStack);
-		
-		Object param= inStack.pop();
-		if (param instanceof List) {
-			List<Double> vals=CustomFunctions.checkVector(param);
-			int n=vals.size();
-
-			if (n==0) {
-				inStack.push(new Double(0));
-			} else {
-				double sum=0; for (Double v: vals) {
-					sum=sum+v;}
-				inStack.push(new Double(sum/n));
-			}
-		}
-		else throw new ParseException("Invalid aggregate value in expression");
-	}
+    public ArithmeticMean()
+    {
+        numberOfParameters = 1;
+    }
+
+    @Override
+    @SuppressWarnings( { "rawtypes", "unchecked" } )
+    public void run( Stack inStack )
+        throws ParseException
+    {
+
+        // check the stack
+        checkStack( inStack );
+
+        Object param = inStack.pop();
+        if ( param instanceof List )
+        {
+            List<Double> vals = CustomFunctions.checkVector( param );
+            int n = vals.size();
+
+            if ( n == 0 )
+            {
+                inStack.push( new Double( 0 ) );
+            }
+            else
+            {
+                double sum = 0;
+                for ( Double v : vals )
+                {
+                    sum = sum + v;
+                }
+                inStack.push( new Double( sum / n ) );
+            }
+        }
+        else
+            throw new ParseException( "Invalid aggregate value in expression" );
+    }
 }
-

=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/Count.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/Count.java	2015-12-22 20:42:54 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/Count.java	2016-01-13 11:52:57 +0000
@@ -8,22 +8,25 @@
 import org.nfunk.jep.function.PostfixMathCommand;
 import org.nfunk.jep.function.PostfixMathCommandI;
 
-public class Count extends PostfixMathCommand 
-    implements PostfixMathCommandI 
+public class Count
+    extends PostfixMathCommand
+    implements PostfixMathCommandI
 {
-    public Count() {
-	numberOfParameters = 1;
+    public Count()
+    {
+        numberOfParameters = 1;
     }
 
-	// nFunk's JEP run() method uses the raw Stack type
-	@SuppressWarnings({ "rawtypes", "unchecked" })
-    public void run(Stack inStack) throws ParseException {
-	// check the stack
-	checkStack(inStack);
+    // nFunk's JEP run() method uses the raw Stack type
+    @SuppressWarnings( { "rawtypes", "unchecked" } )
+    public void run( Stack inStack )
+        throws ParseException
+    {
+        // check the stack
+        checkStack( inStack );
 
-	Object param= inStack.pop();
-	List<Double> vals=CustomFunctions.checkVector(param);
-	inStack.push(vals.size());
+        Object param = inStack.pop();
+        List<Double> vals = CustomFunctions.checkVector( param );
+        inStack.push( vals.size() );
     }
 }
-

=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/CustomFunctions.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/CustomFunctions.java	2016-01-08 16:30:51 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/CustomFunctions.java	2016-01-13 11:52:57 +0000
@@ -11,66 +11,90 @@
 import org.nfunk.jep.ParseException;
 import org.nfunk.jep.function.PostfixMathCommandI;
 
-public class CustomFunctions {
-	
-	private static Boolean init_done=false;
-	
-	public static Map<String,PostfixMathCommandI> aggregate_functions=
-			new HashMap<String,PostfixMathCommandI>();
-
-	public static void addFunctions(JEP parser)
-	{
-		if (!(init_done)) initCustomFunctions();
-		for (Entry<String,PostfixMathCommandI> e: 
-			aggregate_functions.entrySet()) {
-			String fname=e.getKey();
-			PostfixMathCommandI cmd=e.getValue();
-			parser.addFunction(fname,cmd);
-			}
-	}
-
-	private static Pattern aggregate_prefix=Pattern.compile("");
-	private static int n_aggregates=0;
-	public static Pattern getAggregatePrefixPattern(){
-		if (!(init_done)) initCustomFunctions();
-		if (n_aggregates==aggregate_functions.size()) 
-			return aggregate_prefix;
-		else {
-		StringBuffer s=new StringBuffer(); int i=0; s.append("(");
-		for (String key: aggregate_functions.keySet()) {
-			if (i>0) s.append('|'); else i++; 
-			s.append(key);}
-		s.append(")\\s*\\(");
-		aggregate_prefix=Pattern.compile(s.toString());
-		n_aggregates=aggregate_functions.size();
-		return aggregate_prefix;}
-	}
-
-	public static void addAggregateFunction(String name,PostfixMathCommandI fn){
-		aggregate_functions.put(name,fn);
-	}
-
-	@SuppressWarnings("unchecked") 
-	public static List<Double> checkVector(Object param) throws ParseException
-	{
-		if (param instanceof List) {
-			List<?> vals=(List<?>) param;
-			for (Object val: vals) {
-				if (!(val instanceof Double))
-					throw new ParseException("Non numeric vector");
-			}
-			return (List<Double>) param;
-		}
-		else throw new ParseException("Invalid vector argument");
-	}
-
-	private synchronized static void initCustomFunctions() {
-		if (init_done) return; else init_done=true;
-		CustomFunctions.addAggregateFunction("AVG",new ArithmeticMean());
-		CustomFunctions.addAggregateFunction("STDDEV",new StandardDeviation());
-		CustomFunctions.addAggregateFunction("MEDIAN",new MedianValue());
-		CustomFunctions.addAggregateFunction("MAX",new MaxValue());
-		CustomFunctions.addAggregateFunction("MIN",new MinValue());
-		CustomFunctions.addAggregateFunction("COUNT",new Count());
-		CustomFunctions.addAggregateFunction("VSUM",new VectorSum());}
+public class CustomFunctions
+{
+
+    private static Boolean init_done = false;
+
+    public static Map<String, PostfixMathCommandI> aggregate_functions = new HashMap<String, PostfixMathCommandI>();
+
+    public static void addFunctions( JEP parser )
+    {
+        if ( !(init_done) )
+            initCustomFunctions();
+        for ( Entry<String, PostfixMathCommandI> e : aggregate_functions.entrySet() )
+        {
+            String fname = e.getKey();
+            PostfixMathCommandI cmd = e.getValue();
+            parser.addFunction( fname, cmd );
+        }
+    }
+
+    private static Pattern aggregate_prefix = Pattern.compile( "" );
+
+    private static int n_aggregates = 0;
+
+    public static Pattern getAggregatePrefixPattern()
+    {
+        if ( !(init_done) )
+            initCustomFunctions();
+        if ( n_aggregates == aggregate_functions.size() )
+            return aggregate_prefix;
+        else
+        {
+            StringBuffer s = new StringBuffer();
+            int i = 0;
+            s.append( "(" );
+            for ( String key : aggregate_functions.keySet() )
+            {
+                if ( i > 0 )
+                    s.append( '|' );
+                else
+                    i++;
+                s.append( key );
+            }
+            s.append( ")\\s*\\(" );
+            aggregate_prefix = Pattern.compile( s.toString() );
+            n_aggregates = aggregate_functions.size();
+            return aggregate_prefix;
+        }
+    }
+
+    public static void addAggregateFunction( String name, PostfixMathCommandI fn )
+    {
+        aggregate_functions.put( name, fn );
+    }
+
+    @SuppressWarnings( "unchecked" )
+    public static List<Double> checkVector( Object param )
+        throws ParseException
+    {
+        if ( param instanceof List )
+        {
+            List<?> vals = (List<?>) param;
+            for ( Object val : vals )
+            {
+                if ( !(val instanceof Double) )
+                    throw new ParseException( "Non numeric vector" );
+            }
+            return (List<Double>) param;
+        }
+        else
+            throw new ParseException( "Invalid vector argument" );
+    }
+
+    private synchronized static void initCustomFunctions()
+    {
+        if ( init_done )
+            return;
+        else
+            init_done = true;
+        CustomFunctions.addAggregateFunction( "AVG", new ArithmeticMean() );
+        CustomFunctions.addAggregateFunction( "STDDEV", new StandardDeviation() );
+        CustomFunctions.addAggregateFunction( "MEDIAN", new MedianValue() );
+        CustomFunctions.addAggregateFunction( "MAX", new MaxValue() );
+        CustomFunctions.addAggregateFunction( "MIN", new MinValue() );
+        CustomFunctions.addAggregateFunction( "COUNT", new Count() );
+        CustomFunctions.addAggregateFunction( "VSUM", new VectorSum() );
+    }
 }

=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MaxValue.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MaxValue.java	2015-12-22 20:42:54 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MaxValue.java	2016-01-13 11:52:57 +0000
@@ -8,26 +8,33 @@
 import org.nfunk.jep.function.PostfixMathCommand;
 import org.nfunk.jep.function.PostfixMathCommandI;
 
-public class MaxValue extends PostfixMathCommand implements PostfixMathCommandI {
-	public MaxValue() {
-		numberOfParameters = 1;
-	}
-
-	// nFunk's JEP run() method uses the raw Stack type
-	@SuppressWarnings({ "rawtypes", "unchecked" })
-	public void run(Stack inStack) throws ParseException {
-		// check the stack
-		checkStack(inStack);
-
-		Object param = inStack.pop();
-		List<Double> vals = CustomFunctions.checkVector(param);
-		Double max = null;
-		for (Double v : vals) {
-			if (max == null)
-				max = v;
-			else if (v > max)
-				max = v;
-		}
-		inStack.push(new Double(max));
-	}
+public class MaxValue
+    extends PostfixMathCommand
+    implements PostfixMathCommandI
+{
+    public MaxValue()
+    {
+        numberOfParameters = 1;
+    }
+
+    // nFunk's JEP run() method uses the raw Stack type
+    @SuppressWarnings( { "rawtypes", "unchecked" } )
+    public void run( Stack inStack )
+        throws ParseException
+    {
+        // check the stack
+        checkStack( inStack );
+
+        Object param = inStack.pop();
+        List<Double> vals = CustomFunctions.checkVector( param );
+        Double max = null;
+        for ( Double v : vals )
+        {
+            if ( max == null )
+                max = v;
+            else if ( v > max )
+                max = v;
+        }
+        inStack.push( new Double( max ) );
+    }
 }

=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MedianValue.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MedianValue.java	2015-12-22 20:42:54 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MedianValue.java	2016-01-13 11:52:57 +0000
@@ -8,24 +8,32 @@
 import org.nfunk.jep.function.PostfixMathCommand;
 import org.nfunk.jep.function.PostfixMathCommandI;
 
-public class MedianValue extends PostfixMathCommand implements PostfixMathCommandI {
-	public MedianValue() {
-		numberOfParameters = 1;
-	}
-
-	// nFunk's JEP run() method uses the raw Stack type
-	@SuppressWarnings({ "rawtypes", "unchecked" })
-	public void run(Stack inStack) throws ParseException {
-		// check the stack
-		checkStack(inStack);
-
-		Object param = inStack.pop();
-		List<Double> vals = CustomFunctions.checkVector(param);
-		int n = vals.size();
-		// Sort it here
-		if (n % 2 == 0) {
-			inStack.push(new Double((vals.get(n / 2) + vals.get(n / 2 + 1)) / 2));
-		} else
-			inStack.push(new Double(vals.get((n + 1) / 2)));
-	}
+public class MedianValue
+    extends PostfixMathCommand
+    implements PostfixMathCommandI
+{
+    public MedianValue()
+    {
+        numberOfParameters = 1;
+    }
+
+    // nFunk's JEP run() method uses the raw Stack type
+    @SuppressWarnings( { "rawtypes", "unchecked" } )
+    public void run( Stack inStack )
+        throws ParseException
+    {
+        // check the stack
+        checkStack( inStack );
+
+        Object param = inStack.pop();
+        List<Double> vals = CustomFunctions.checkVector( param );
+        int n = vals.size();
+        // Sort it here
+        if ( n % 2 == 0 )
+        {
+            inStack.push( new Double( (vals.get( n / 2 ) + vals.get( n / 2 + 1 )) / 2 ) );
+        }
+        else
+            inStack.push( new Double( vals.get( (n + 1) / 2 ) ) );
+    }
 }

=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MinValue.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MinValue.java	2015-12-22 20:42:54 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MinValue.java	2016-01-13 11:52:57 +0000
@@ -8,26 +8,33 @@
 import org.nfunk.jep.function.PostfixMathCommand;
 import org.nfunk.jep.function.PostfixMathCommandI;
 
-public class MinValue extends PostfixMathCommand implements PostfixMathCommandI {
-	public MinValue() {
-		numberOfParameters = 1;
-	}
-
-	// nFunk's JEP run() method uses the raw Stack type
-	@SuppressWarnings({ "rawtypes", "unchecked" })
-	public void run(Stack inStack) throws ParseException {
-		// check the stack
-		checkStack(inStack);
-
-		Object param = inStack.pop();
-		List<Double> vals = CustomFunctions.checkVector(param);
-		Double min = null;
-		for (Double v : vals) {
-			if (min == null)
-				min = v;
-			else if (v < min)
-				min = v;
-		}
-		inStack.push(new Double(min));
-	}
+public class MinValue
+    extends PostfixMathCommand
+    implements PostfixMathCommandI
+{
+    public MinValue()
+    {
+        numberOfParameters = 1;
+    }
+
+    // nFunk's JEP run() method uses the raw Stack type
+    @SuppressWarnings( { "rawtypes", "unchecked" } )
+    public void run( Stack inStack )
+        throws ParseException
+    {
+        // check the stack
+        checkStack( inStack );
+
+        Object param = inStack.pop();
+        List<Double> vals = CustomFunctions.checkVector( param );
+        Double min = null;
+        for ( Double v : vals )
+        {
+            if ( min == null )
+                min = v;
+            else if ( v < min )
+                min = v;
+        }
+        inStack.push( new Double( min ) );
+    }
 }

=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/StandardDeviation.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/StandardDeviation.java	2015-12-22 20:42:54 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/StandardDeviation.java	2016-01-13 11:52:57 +0000
@@ -8,34 +8,45 @@
 import org.nfunk.jep.function.PostfixMathCommand;
 import org.nfunk.jep.function.PostfixMathCommandI;
 
-public class StandardDeviation extends PostfixMathCommand implements PostfixMathCommandI {
-	public StandardDeviation() {
-		numberOfParameters = 1;
-	}
-
-	// nFunk's JEP run() method uses the raw Stack type
-	@SuppressWarnings({ "rawtypes", "unchecked" })
-	public void run(Stack inStack) throws ParseException {
-		// check the stack
-		checkStack(inStack);
-
-		Object param = inStack.pop();
-		List<Double> vals = CustomFunctions.checkVector(param);
-		int n = vals.size();
-		if (n == 0) {
-			inStack.push(new Double(0));
-		} else {
-			double sum = 0, sum2 = 0, mean, variance;
-			for (Double v : vals) {
-				sum = sum + v;
-			}
-			;
-			mean = sum / n;
-			for (Double v : vals) {
-				sum2 = sum2 + ((v - mean) * (v - mean));
-			}
-			variance = sum2 / n;
-			inStack.push(new Double(Math.sqrt(variance)));
-		}
-	}
+public class StandardDeviation
+    extends PostfixMathCommand
+    implements PostfixMathCommandI
+{
+    public StandardDeviation()
+    {
+        numberOfParameters = 1;
+    }
+
+    // nFunk's JEP run() method uses the raw Stack type
+    @SuppressWarnings( { "rawtypes", "unchecked" } )
+    public void run( Stack inStack )
+        throws ParseException
+    {
+        // check the stack
+        checkStack( inStack );
+
+        Object param = inStack.pop();
+        List<Double> vals = CustomFunctions.checkVector( param );
+        int n = vals.size();
+        if ( n == 0 )
+        {
+            inStack.push( new Double( 0 ) );
+        }
+        else
+        {
+            double sum = 0, sum2 = 0, mean, variance;
+            for ( Double v : vals )
+            {
+                sum = sum + v;
+            }
+
+            mean = sum / n;
+            for ( Double v : vals )
+            {
+                sum2 = sum2 + ((v - mean) * (v - mean));
+            }
+            variance = sum2 / n;
+            inStack.push( new Double( Math.sqrt( variance ) ) );
+        }
+    }
 }

=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/VectorSum.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/VectorSum.java	2015-12-22 20:42:54 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/VectorSum.java	2016-01-13 11:52:57 +0000
@@ -8,32 +8,43 @@
 import org.nfunk.jep.function.PostfixMathCommand;
 import org.nfunk.jep.function.PostfixMathCommandI;
 
-public class VectorSum extends PostfixMathCommand 
-implements PostfixMathCommandI 
+public class VectorSum
+    extends PostfixMathCommand
+    implements PostfixMathCommandI
 {
-	public VectorSum() {
-		numberOfParameters = 1;
-	}
-
-	// nFunk's JEP run() method uses the raw Stack type
-	@SuppressWarnings({ "rawtypes", "unchecked" })
-	public void run(Stack inStack) throws ParseException {
-		// check the stack
-		checkStack(inStack);
-
-		Object param= inStack.pop();
-		if (param instanceof List) {
-			List<Double> vals=CustomFunctions.checkVector(param);
-			int n=vals.size();
-			if (n==0) {
-				inStack.push(new Double(0));
-			} else {
-				double sum=0; for (Double v: vals) {
-					sum=sum+v;}
-				inStack.push(new Double(sum));
-			}
-		}
-		else throw new ParseException("Invalid aggregate value in expression");
-	}
+    public VectorSum()
+    {
+        numberOfParameters = 1;
+    }
+
+    // nFunk's JEP run() method uses the raw Stack type
+    @SuppressWarnings( { "rawtypes", "unchecked" } )
+    public void run( Stack inStack )
+        throws ParseException
+    {
+        // check the stack
+        checkStack( inStack );
+
+        Object param = inStack.pop();
+        if ( param instanceof List )
+        {
+            List<Double> vals = CustomFunctions.checkVector( param );
+            int n = vals.size();
+            if ( n == 0 )
+            {
+                inStack.push( new Double( 0 ) );
+            }
+            else
+            {
+                double sum = 0;
+                for ( Double v : vals )
+                {
+                    sum = sum + v;
+                }
+                inStack.push( new Double( sum ) );
+            }
+        }
+        else
+            throw new ParseException( "Invalid aggregate value in expression" );
+    }
 }
-