← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 12651: Refactor validationRule logic for scheduled alert runs.

 

------------------------------------------------------------
revno: 12651
committer: dhis2-c <dhis2@xxxxxxxxxxxxxx>
branch nick: trunk
timestamp: Mon 2013-10-14 16:00:00 -0400
message:
  Refactor validationRule logic for scheduled alert runs.
added:
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/Validator.java
renamed:
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationWorkerThread.java => dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidatorThread.java
modified:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/OrganisationUnitExtended.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/PeriodTypeExtended.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRunContext.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/scheduling/MonitoringTask.java
  dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidatorThread.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/validation/ValidationRuleService.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleService.java	2013-10-08 17:20:57 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleService.java	2013-10-14 20:00:00 +0000
@@ -92,11 +92,6 @@
      */
     Collection<ValidationResult> validate( Date startDate, Date endDate, OrganisationUnit source );
 
-    /**
-     * For a nightly alert run, run validation tests and notify users as requested of any validations.
-     */
-    void alertRun();
-    
     // -------------------------------------------------------------------------
     // ValidationRule
     // -------------------------------------------------------------------------

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java	2013-10-13 16:15:28 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java	2013-10-14 20:00:00 +0000
@@ -127,13 +127,6 @@
         this.periodService = periodService;
     }
 
-    private OrganisationUnitService organisationUnitService;
-
-    public void setOrganisationUnitService( OrganisationUnitService organisationUnitService )
-    {
-        this.organisationUnitService = organisationUnitService;
-    }
-
     private DataValueService dataValueService;
 
     public void setDataValueService( DataValueService dataValueService )
@@ -148,20 +141,6 @@
         this.constantService = constantService;
     }
 
-    private SystemSettingManager systemSettingManager;
-
-    public void setSystemSettingManager( SystemSettingManager systemSettingManager )
-    {
-        this.systemSettingManager = systemSettingManager;
-    }
-
-    private MessageService messageService;
-
-    public void setMessageService( MessageService messageService )
-    {
-        this.messageService = messageService;
-    }
-
     private I18nService i18nService;
 
     public void setI18nService( I18nService service )
@@ -178,7 +157,8 @@
         log.info( "Validate startDate=" + startDate + " endDate=" + endDate + " sources[" + sources.size() + "]" );
         Collection<Period> periods = periodService.getPeriodsBetweenDates( startDate, endDate );
         Collection<ValidationRule> rules = getAllValidationRules();
-        return validateInternal( sources, periods, rules, ValidationRunType.INTERACTIVE, null );
+        return Validator.validate( sources, periods, rules, ValidationRunType.INTERACTIVE, null,
+        		constantService, expressionService, periodService, dataValueService );
     }
 
     public Collection<ValidationResult> validate( Date startDate, Date endDate, Collection<OrganisationUnit> sources,
@@ -187,7 +167,8 @@
     	log.info( "Validate startDate=" + startDate + " endDate=" + endDate + " sources[" + sources.size() + "] group=" + group.getName() );
         Collection<Period> periods = periodService.getPeriodsBetweenDates( startDate, endDate );
         Collection<ValidationRule> rules = group.getMembers();
-        return validateInternal( sources, periods, rules, ValidationRunType.INTERACTIVE, null );
+        return Validator.validate( sources, periods, rules, ValidationRunType.INTERACTIVE, null,
+        		constantService, expressionService, periodService, dataValueService );
     }
 
     public Collection<ValidationResult> validate( Date startDate, Date endDate, OrganisationUnit source )
@@ -195,7 +176,10 @@
     	log.info( "Validate startDate=" + startDate + " endDate=" + endDate + " source=" + source.getName() );
         Collection<Period> periods = periodService.getPeriodsBetweenDates( startDate, endDate );
         Collection<ValidationRule> rules = getAllValidationRules();
-        return validateInternal( source, periods, rules, ValidationRunType.INTERACTIVE, null );
+        Collection<OrganisationUnit> sources = new HashSet<OrganisationUnit>();
+        sources.add( source );
+        return Validator.validate( sources, periods, rules, ValidationRunType.INTERACTIVE, null,
+        		constantService, expressionService, periodService, dataValueService );
     }
 
     public Collection<ValidationResult> validate( DataSet dataSet, Period period, OrganisationUnit source )
@@ -215,45 +199,10 @@
             rules = getValidationTypeRulesForDataElements( dataSet.getDataElements() );
         }
 
-        return validateInternal( source, periods, rules, ValidationRunType.INTERACTIVE, null );
-    }
-
-    // TODO: Schedule this to run every night.
-    public void alertRun()
-    {
-        // Find all the rules belonging to groups that will send alerts to user roles.
-        Set<ValidationRule> rules = new HashSet<ValidationRule>();
-        for ( ValidationRuleGroup validationRuleGroup : getAllValidationRuleGroups() )
-        {
-            Collection<UserAuthorityGroup> userRolesToAlert = validationRuleGroup.getUserAuthorityGroupsToAlert();
-            if ( userRolesToAlert != null && !userRolesToAlert.isEmpty() )
-            {
-                rules.addAll( validationRuleGroup.getMembers() );
-            }
-        }
-
-        Collection<OrganisationUnit> sources = organisationUnitService.getAllOrganisationUnits();
-        Collection<Period> periods = getAlertPeriodsFromRules( rules );
-        Date lastAlertRun = (Date) systemSettingManager.getSystemSetting( SystemSettingManager.KEY_LAST_ALERT_RUN );
-        
-        // Any database changes after this moment will contribute to the next run.
-        
-        Date thisAlertRun = new Date();
-        
-        log.info( "alertRun sources[" + sources.size() + "] periods[" + periods.size() + "] rules[" + rules.size()
-            + "] last run " + lastAlertRun == null ? "(none)" : lastAlertRun );
-        
-        Collection<ValidationResult> results = validateInternal( sources, periods, rules, ValidationRunType.ALERT,
-            lastAlertRun );
-        
-        log.trace( "alertRun() results[" + results.size() + "]" );
-        
-        if ( !results.isEmpty() )
-        {
-            postAlerts( results, thisAlertRun ); // Alert the users.
-        }
-        
-        systemSettingManager.saveSystemSetting( SystemSettingManager.KEY_LAST_ALERT_RUN, thisAlertRun );
+        Collection<OrganisationUnit> sources = new HashSet<OrganisationUnit>();
+        sources.add( source );
+        return Validator.validate( sources, periods, rules, ValidationRunType.INTERACTIVE, null,
+        		constantService, expressionService, periodService, dataValueService );
     }
 
     // -------------------------------------------------------------------------
@@ -261,117 +210,6 @@
     // -------------------------------------------------------------------------
 
     /**
-     * Evaluates validation rules for a single organisation unit.
-     * 
-     * @param source the organisation unit in which to run the validation rules
-     * @param periods the periods of data to check
-     * @param rules the ValidationRules to evaluate
-     * @param runType whether this is an interactive or alert run
-     * @param lastAlertRun date/time of the most recent successful alerts run
-     *        (needed only for alert run)
-     * @return a collection of any validations that were found
-     */
-    private Collection<ValidationResult> validateInternal( OrganisationUnit source, Collection<Period> periods,
-        Collection<ValidationRule> rules, ValidationRunType runType, Date lastAlertRun )
-    {
-        Collection<OrganisationUnit> sources = new HashSet<OrganisationUnit>();
-        sources.add( source );
-        return validateInternal( sources, periods, rules, ValidationRunType.INTERACTIVE, null );
-    }
-
-    /**
-     * 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 multithreaded environment.
-     * 
-     * @param sources the organisation units in which to run the validation
-     *        rules
-     * @param periods the periods of data to check
-     * @param rules the ValidationRules to evaluate
-     * @param runType whether this is an INTERACTIVE or ALERT run
-     * @param lastAlertRun date/time of the most recent successful alerts run
-     *        (needed only for alert run)
-     * @return a collection of any validations that were found
-     */
-    private Collection<ValidationResult> validateInternal( Collection<OrganisationUnit> sources,
-        Collection<Period> periods, Collection<ValidationRule> rules, ValidationRunType runType, Date lastAlertRun )
-    {
-        ValidationRunContext context = ValidationRunContext.getNewValidationRunContext( sources, periods, rules,
-        		constantService.getConstantMap(), ValidationRunType.ALERT, lastAlertRun,
-        		expressionService, periodService, dataValueService);
-
-        int threadPoolSize = SystemUtils.getCpuCores();
-        
-        if ( threadPoolSize > 2 )
-        {
-            threadPoolSize--;
-        }
-        if ( threadPoolSize > sources.size() )
-        {
-            threadPoolSize = sources.size();
-        }
-
-        ExecutorService executor = Executors.newFixedThreadPool( threadPoolSize );
-
-        for ( OrganisationUnitExtended sourceX : context.getSourceXs() )
-        {
-            Runnable worker = new ValidationWorkerThread( sourceX, context );
-            executor.execute( worker );
-        }
-
-        executor.shutdown();
-        
-        try
-        {
-            executor.awaitTermination( 23, TimeUnit.HOURS );
-        }
-        catch ( InterruptedException e )
-        {
-            executor.shutdownNow();
-        }
-        return context.getValidationResults();
-    }
-
-    /**
-     * For an alert run, gets the current and most periods to search, based on
-     * the period types from the rules to run.
-     * 
-     * @param rules the ValidationRules to be evaluated on this alert run
-     * @return periods to search for new alerts
-     */
-    private Collection<Period> getAlertPeriodsFromRules( Collection<ValidationRule> rules )
-    {
-        Set<Period> periods = new HashSet<Period>();
-
-        // Construct a set of all period types found in validation rules.
-        Set<PeriodType> rulePeriodTypes = new HashSet<PeriodType>();
-        for ( ValidationRule rule : rules )
-        {
-            // (Note that we have to get periodType from periodService,
-            // otherwise the ID will not be present.)
-            rulePeriodTypes.add( periodService.getPeriodTypeByName( rule.getPeriodType().getName() ) );
-        }
-
-        // For each period type, find the period containing the current date (if
-        // any), and the most recent previous period. Add whichever one(s) of these 
-        // are present in the database.
-        for ( PeriodType periodType : rulePeriodTypes )
-        {
-            CalendarPeriodType calendarPeriodType = ( CalendarPeriodType ) periodType;
-            Period currentPeriod = calendarPeriodType.createPeriod();
-            Period previousPeriod = calendarPeriodType.getPreviousPeriod( currentPeriod );
-            periods.addAll( periodService.getIntersectingPeriodsByPeriodType( periodType,
-                previousPeriod.getStartDate(), currentPeriod.getEndDate() ) );
-            // Note: If the last successful daily run was more than one day
-            // ago, we might consider adding some additional periods of type 
-            // DailyPeriodType so we don't miss any alerts.
-        }
-
-        return periods;
-    }
-
-    /**
      * Returns all validation-type rules which have specified data elements
      * assigned to them.
      * 
@@ -437,176 +275,6 @@
 
         return rulesForDataSet;
     }
-
-    /**
-     * At the end of an ALERT run, post messages to the users who want to see
-     * the results.
-     * 
-     * @param validationResults the set of validation error results
-     * @param alertRunStart the date/time when the alert run started
-     */
-    private void postAlerts( Collection<ValidationResult> validationResults, Date alertRunStart )
-    {
-        SortedSet<ValidationResult> results = new TreeSet<ValidationResult>( validationResults );
-
-        // Find out which users receive alerts, and which ValidationRules they
-        // receive alerts for.
-        Map<User, Collection<ValidationRule>> userRulesMap = new HashMap<User, Collection<ValidationRule>>();
-        
-        for ( ValidationRuleGroup validationRuleGroup : getAllValidationRuleGroups() )
-        {
-            Collection<UserAuthorityGroup> userRolesToAlert = validationRuleGroup.getUserAuthorityGroupsToAlert();
-            if ( userRolesToAlert != null && !userRolesToAlert.isEmpty() )
-            {
-                for ( UserAuthorityGroup role : userRolesToAlert )
-                {
-                    for ( UserCredentials userCredentials : role.getMembers() )
-                    {
-                        User user = userCredentials.getUser();
-                        Collection<ValidationRule> userRules = userRulesMap.get( user );
-                        if ( userRules == null )
-                        {
-                            userRules = new ArrayList<ValidationRule>();
-                            userRulesMap.put( user, userRules );
-                        }
-                        userRules.addAll( validationRuleGroup.getMembers() );
-                    }
-                }
-            }
-        }
-
-        // We will create one message for each set of users who receive the same
-        // subset of results. (Not necessarily the same as the set of users who
-        // receive alerts from the same subset of validation rules -- because
-        // some of these rules may return no results.) This saves on message
-        // storage space.
-
-        // TODO: Encapsulate this in another level of Map by the user's
-        // language, and generate a message for each combination of ( target
-        // language, set of results )
-        Map<List<ValidationResult>, Set<User>> messageMap = new HashMap<List<ValidationResult>, Set<User>>();
-
-        for ( User user : userRulesMap.keySet() )
-        {
-            // For each user who receives alerts, find the subset of results
-            // from this run.
-            Collection<ValidationRule> userRules = userRulesMap.get( user );
-            List<ValidationResult> userResults = new ArrayList<ValidationResult>();
-            Map<String, Integer> importanceSummary = new HashMap<String, Integer>();
-
-            for ( ValidationResult result : results )
-            {
-                if ( userRules.contains( result.getValidationRule() ) )
-                {
-                    userResults.add( result );
-                    String importance = result.getValidationRule().getImportance();
-                    Integer importanceCount = importanceSummary.get( importance );
-                    if ( importanceCount == null )
-                    {
-                        importanceSummary.put( importance, 1 );
-                    }
-                    else
-                    {
-                        importanceSummary.put( importance, importanceCount + 1 );
-                    }
-                }
-            }
-
-            // Group this user with other users who have the same subset of
-            // results.
-            if ( !userResults.isEmpty() )
-            {
-                Set<User> messageReceivers = messageMap.get( userResults );
-                if ( messageReceivers == null )
-                {
-                    messageReceivers = new HashSet<User>();
-                    messageMap.put( userResults, messageReceivers );
-                }
-                messageReceivers.add( user );
-            }
-        }
-
-        // For each unique subset of results, send a message to all users
-        // receiving that subset of results.
-        for ( Map.Entry<List<ValidationResult>, Set<User>> entry : messageMap.entrySet() )
-        {
-            sendAlertmessage( entry.getKey(), entry.getValue(), alertRunStart );
-        }
-    }
-
-    /**
-     * Generate and send an alert message containing alert results to a set of
-     * users.
-     * 
-     * @param results results to put in this message
-     * @param users users to receive these results
-     * @param alertRunStart date/time when the alert run started
-     */
-    private void sendAlertmessage( List<ValidationResult> results, Set<User> users, Date alertRunStart )
-    {
-        StringBuilder messageBuilder = new StringBuilder();
-        SimpleDateFormat dateTimeFormatter = new SimpleDateFormat( "yyyy-MM-dd HH:mm" );
-
-        // Count the number of messages of each importance type.
-        Map<String, Integer> importanceCountMap = new HashMap<String, Integer>();
-        for ( ValidationResult result : results )
-        {
-            Integer importanceCount = importanceCountMap.get( result.getValidationRule().getImportance() );
-            importanceCountMap.put( result.getValidationRule().getImportance(), importanceCount == null ? 1
-                : importanceCount + 1 );
-        }
-
-        // Construct the subject line.
-        String subject = "DHIS alerts as of " + dateTimeFormatter.format( alertRunStart ) + " - priority High "
-            + (importanceCountMap.get( "high" ) == null ? 0 : importanceCountMap.get( "high" )) + ", Medium "
-            + (importanceCountMap.get( "medium" ) == null ? 0 : importanceCountMap.get( "medium" )) + ", Low "
-            + (importanceCountMap.get( "low" ) == null ? 0 : importanceCountMap.get( "low" ));
-
-        // Construct the text of the message.
-        messageBuilder
-            .append( "<html>" )
-            .append( "<head>" ).append( "</head>" )
-            .append( "<body>" ).append( subject ).append( "<br />" )
-            .append( "<table>" )
-            .append( "<tr>" )
-            .append( "<th>Organisation Unit</th>" )
-            .append( "<th>Period</th>" )
-            .append( "<th>Importance</th>" )
-            .append( "<th>Left side description</th>" )
-            .append( "<th>Value</th>" )
-            .append( "<th>Operator</th>" )
-            .append( "<th>Value</th>" )
-            .append( "<th>Right side description</th>" )
-            .append( "</tr>" );
-
-        for ( ValidationResult result : results )
-        {
-            ValidationRule rule = result.getValidationRule();
-
-            messageBuilder
-            	.append( "<tr>" )
-            	.append( "<td>" ).append( result.getSource().getName() ).append( "<\td>" )
-                .append( "<td>" ).append( result.getPeriod().getName() ).append( "<\td>" )
-                .append( "<td>" ).append( rule.getImportance() ).append( "<\td>" )
-                .append( "<td>" ).append( rule.getLeftSide().getDescription() ).append( "<\td>" )
-                .append( "<td>" ).append( result.getLeftsideValue() ).append( "<\td>" )
-                .append( "<td>" ).append( rule.getOperator().toString() ).append( "<\td>" )
-                .append( "<td>" ).append( result.getRightsideValue() ).append( "<\td>" )
-                .append( "<td>" ).append( rule.getRightSide().getDescription() ).append( "<\td>" )
-                .append( "</tr>" );
-        }
-
-        messageBuilder
-        	.append( "</table>" )
-        	.append( "</body>" )
-        	.append( "</html>" );
-
-        String messageText = messageBuilder.toString();
-
-        log.info( "postUserResults() users[" + users.size() + "] subject " + subject );
-        messageService.sendMessage( subject, messageText, null, users );
-    }
-
     // -------------------------------------------------------------------------
     // ValidationRule CRUD operations
     // -------------------------------------------------------------------------

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/OrganisationUnitExtended.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/OrganisationUnitExtended.java	2013-10-09 05:16:53 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/OrganisationUnitExtended.java	2013-10-14 20:00:00 +0000
@@ -48,14 +48,17 @@
 public class OrganisationUnitExtended
 {
     private OrganisationUnit source;
+    
+    private boolean toBeValidated;
 
     private Collection<OrganisationUnit> children;
 
     private int level;
 
-    public OrganisationUnitExtended( OrganisationUnit source )
+    public OrganisationUnitExtended( OrganisationUnit source, boolean toBeValidated )
     {
     	this.source = source;
+    	this.toBeValidated = toBeValidated;
     	children = new HashSet<OrganisationUnit>( source.getChildren() );
     	level = source.getOrganisationUnitLevel();
     }
@@ -63,8 +66,9 @@
     public String toString()
     {
         return new ToStringBuilder( this, ToStringStyle.SHORT_PREFIX_STYLE )
-            .append( "\n     name", source.getName() ).append( "\n     children[", children.size() + "]" )
-            .append( "\n     level", level ).toString();
+            .append( "\n  name", source.getName() )
+            .append( "\n  children[", children.size() + "]" )
+            .append( "\n  level", level ).toString();
     }
 
     // -------------------------------------------------------------------------
@@ -75,6 +79,10 @@
 		return source;
 	}
 
+    public boolean getToBeValidated() {
+		return toBeValidated;
+	}
+
 	public Collection<OrganisationUnit> getChildren() {
 		return children;
 	}

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/PeriodTypeExtended.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/PeriodTypeExtended.java	2013-10-09 05:16:53 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/PeriodTypeExtended.java	2013-10-14 20:00:00 +0000
@@ -81,12 +81,12 @@
     public String toString()
     {
         return new ToStringBuilder( this, ToStringStyle.SHORT_PREFIX_STYLE )
-            .append( "\n periodType", periodType )
-            .append( "\n periods", (Arrays.toString( periods.toArray() )) )
-            .append( "\n rules", (Arrays.toString( rules.toArray() )) )
-            .append( "\n dataElements", (Arrays.toString( dataElements.toArray() )) )
-            .append( "\n allowedPeriodTypes", (Arrays.toString( allowedPeriodTypes.toArray() )) )
-            .append( "\n sourceDataElements", "[" + sourceDataElements.size() + "]" ).toString();
+            .append( "\n  periodType", periodType )
+            .append( "\n  periods", (Arrays.toString( periods.toArray() )) )
+            .append( "\n  rules", (Arrays.toString( rules.toArray() )) )
+            .append( "\n  dataElements", (Arrays.toString( dataElements.toArray() )) )
+            .append( "\n  allowedPeriodTypes", (Arrays.toString( allowedPeriodTypes.toArray() )) )
+            .append( "\n  sourceDataElements", "[" + sourceDataElements.size() + "]" ).toString();
     }
 
     // -------------------------------------------------------------------------

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRunContext.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRunContext.java	2013-10-13 09:59:35 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRunContext.java	2013-10-14 20:00:00 +0000
@@ -81,6 +81,8 @@
     private Map<ValidationRule, ValidationRuleExtended> ruleXMap;
 
     private Collection<OrganisationUnitExtended> sourceXs;
+    
+    private int countOfSourcesToValidate;
 
     private Collection<ValidationResult> validationResults;
 
@@ -101,8 +103,8 @@
             .append( "\n runType", runType ).append( "\n  lastAlertRun", lastAlertRun )
             .append( "\n constantMap", "[" + constantMap.size() + "]" )
             .append( "\n ruleXMap", "[" + ruleXMap.size() + "]" )
-            .append( "\n  sourceXs", Arrays.toString( sourceXs.toArray() ) )
-            .append( "\n  validationResults", Arrays.toString( validationResults.toArray() ) ).toString();
+            .append( "\n sourceXs", Arrays.toString( sourceXs.toArray() ) )
+            .append( "\n validationResults", Arrays.toString( validationResults.toArray() ) ).toString();
     }
 
     /**
@@ -138,22 +140,54 @@
     /**
      * Initializes context values based on sources, periods and rules
      * 
-     * @param sources
-     * @param periods
-     * @param rules
+     * @param sources organisation units to evaluate for rules
+     * @param periods periods for validation
+     * @param rules validation rules for validation
      */
     private void initialize( Collection<OrganisationUnit> sources, Collection<Period> periods,
         Collection<ValidationRule> rules )
     {
-        boolean monitoringRulesPresent = false;
-
-        // Group the periods by period type.
-        for ( Period period : periods )
+        addPeriodsToContext( periods );
+        
+        boolean monitoringRulesPresent = addRulesToContext ( rules );
+        
+        removeAnyUnneededPeriodTypes();
+        
+        addSourcesToContext( sources, true );
+        
+        countOfSourcesToValidate = sources.size();
+        
+        if ( monitoringRulesPresent )
         {
-            PeriodTypeExtended periodTypeX = getOrCreatePeriodTypeExtended( period.getPeriodType() );
-            periodTypeX.getPeriods().add( period );
+        	Set<OrganisationUnit> otherDescendants = getAllOtherDescendants( sources );
+        	addSourcesToContext( otherDescendants, false );
         }
-
+    }
+
+    /**
+     * Adds Periods to the context, grouped by period type.
+     * 
+     * @param periods Periods to group and add
+     */
+    private void addPeriodsToContext ( Collection<Period> periods )
+    {
+	    for ( Period period : periods )
+	    {
+	        PeriodTypeExtended periodTypeX = getOrCreatePeriodTypeExtended( period.getPeriodType() );
+	        periodTypeX.getPeriods().add( period );
+	    }
+    }
+
+    /**
+     * Adds validation rules to the context.
+     * 
+     * @param rules validation rules to add
+     * @return true if there were some monitoring-type rules, false otherwise.
+     */
+    private boolean addRulesToContext ( Collection<ValidationRule> rules )
+    {
+    	boolean monitoringRulesPresent = false;
+    	
         for ( ValidationRule rule : rules )
         {
             if ( ValidationRule.RULE_TYPE_MONITORING.equals( rule.getRuleType() ) )
@@ -187,34 +221,91 @@
             ValidationRuleExtended ruleX = new ValidationRuleExtended( rule, allowedPastPeriodTypes );
             ruleXMap.put( rule, ruleX );
         }
+        return monitoringRulesPresent;
+    }
 
-        // We only need to keep period types that are selected and also used by
-        // rules that are selected.
+    /**
+     * Removes any period types that don't have rules assigned to them.
+     */
+    private void removeAnyUnneededPeriodTypes()
+    {
         // Start by making a defensive copy so we can delete while iterating.
         Set<PeriodTypeExtended> periodTypeXs = new HashSet<PeriodTypeExtended>( periodTypeExtendedMap.values() );
         for ( PeriodTypeExtended periodTypeX : periodTypeXs )
         {
-            if ( periodTypeX.getPeriods().isEmpty() || periodTypeX.getRules().isEmpty() )
+            if ( periodTypeX.getRules().isEmpty() )
             {
                 periodTypeExtendedMap.remove( periodTypeX.getPeriodType() );
             }
         }
-
-        // If we have some monitoring rules, then make sure that we add all
-        // the descendants of each source to our collection of sources.
-        // This is so we can recurse if needed to find monitoring rule values.
-        if ( monitoringRulesPresent )
-        {
-            for ( OrganisationUnit source : new HashSet<OrganisationUnit>( sources ) )
+    }
+
+    /**
+     * Finds all organisation unit descendants that are not in a given
+     * collection of organisation units. This is needed for monitoring-type
+     * rules, because the data values for the rules may need to be aggregated
+     * from the organisation unit's descendants.
+     * 
+     * The descendants will likely be there anyway for a run including
+     * monitoring-type rules, because an interactive run containing
+     * monitoring-type rules should select an entire subtree, and a
+     * scheduled alert run will contain all organisation units. But check
+     * just to be sure, and find any that may be missing. This makes sure
+     * that some of the tests will work, and may be required for some
+     * future features to work.
+     * 
+     * @param sources organisation units whose descendants to check
+     * @return all other descendants who need to be added who were not
+     * in the original list
+     */
+    private Set<OrganisationUnit> getAllOtherDescendants( Collection<OrganisationUnit> sources )
+    {
+    	Set<OrganisationUnit> allOtherDescendants = new HashSet<OrganisationUnit>();
+        for ( OrganisationUnit source : sources )
+        {
+            getOtherDescendantsRecursive( source, sources, allOtherDescendants );
+        }
+        return allOtherDescendants;
+    }
+
+    /**
+     * If the children of this organisation unit are not in the collection, then
+     * add them and all their descendants if needed.
+     * 
+     * @param source organisation unit whose children to check
+     * @param sources organisation units in the initial list
+     * @param allOtherDescendants list of organisation unit descendants we
+     * need to add
+     */
+    private void getOtherDescendantsRecursive( OrganisationUnit source, Collection<OrganisationUnit> sources,
+    		Set<OrganisationUnit> allOtherDescendants )
+    {
+        for ( OrganisationUnit child : source.getChildren() )
+        {
+            if ( !sources.contains( child ) && !allOtherDescendants.contains( child ) )
             {
-                addSourceRecursive( sources, source );
+            	allOtherDescendants.add( child );
+                getOtherDescendantsRecursive( child, sources, allOtherDescendants );
             }
         }
+    }
 
+    /**
+     * Adds a collection of organisation units to the validation run context.
+     * 
+     * @param sources organisation units to add
+     * @param ruleCheckThisSource true if these organisation units should be
+     * evaluated with validation rules, false if not. (This is false when
+     * adding descendants of organisation units for the purpose of getting
+     * aggregated expression values from descendants, but these organisation
+     * units are not in the main list to be evaluated.)
+     */
+    private void addSourcesToContext ( Collection<OrganisationUnit> sources, boolean ruleCheckThisSource )
+    {
         // Get the information we need for each source.
         for ( OrganisationUnit source : sources )
         {
-            OrganisationUnitExtended sourceX = new OrganisationUnitExtended( source );
+            OrganisationUnitExtended sourceX = new OrganisationUnitExtended( source, ruleCheckThisSource );
             sourceXs.add( sourceX );
 
             Map<PeriodType, Set<DataElement>> sourceElementsMap = source.getDataElementsInDataSetsByPeriodType();
@@ -232,26 +323,7 @@
             }
         }
     }
-
-    /**
-     * If the children of this organisation unit are not in the collection, then
-     * add them and all their descendants if needed.
-     * 
-     * @param sources organisation units to test and add to
-     * @param source organisation unit whose children to check
-     */
-    private void addSourceRecursive( Collection<OrganisationUnit> sources, OrganisationUnit source )
-    {
-        for ( OrganisationUnit child : source.getChildren() )
-        {
-            if ( !sources.contains( child ) )
-            {
-                sources.add( child );
-                addSourceRecursive( sources, child );
-            }
-        }
-    }
-
+    
     /**
      * Gets the PeriodTypeExtended from the context object. If not found,
      * creates a new PeriodTypeExtended object, puts it into the context object,
@@ -334,6 +406,11 @@
         return sourceXs;
     }
 
+    public int getCountOfSourcesToValidate()
+    {
+        return countOfSourcesToValidate;
+    }
+
     public Collection<ValidationResult> getValidationResults()
     {
         return validationResults;

=== added 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	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/Validator.java	2013-10-14 20:00:00 +0000
@@ -0,0 +1,127 @@
+package org.hisp.dhis.validation;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.hisp.dhis.constant.ConstantService;
+import org.hisp.dhis.datavalue.DataValueService;
+import org.hisp.dhis.expression.ExpressionService;
+import org.hisp.dhis.organisationunit.OrganisationUnit;
+import org.hisp.dhis.period.Period;
+import org.hisp.dhis.period.PeriodService;
+import org.hisp.dhis.system.util.SystemUtils;
+
+/*
+ * Copyright (c) 2004-2013, University of Oslo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * Neither the name of the HISP project nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * Evaluates validation rules.
+ * 
+ * @author Jim Grace
+ */
+public class Validator
+{
+
+    /**
+     * 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 multithreaded environment.
+     * 
+     * @param sources the organisation units in which to run the validation
+     *        rules
+     * @param periods the periods of data to check
+     * @param rules the ValidationRules to evaluate
+     * @param runType whether this is an INTERACTIVE or ALERT run
+     * @param lastAlertRun date/time of the most recent successful alerts run
+     *        (needed only for alert run)
+	 * @param constantService Constant Service reference
+	 * @param expressionService Expression Service reference
+	 * @param periodService Period Service reference
+	 * @param dataValueService Data Value Service reference
+     * @return a collection of any validations that were found
+     */
+    public static Collection<ValidationResult> validate( Collection<OrganisationUnit> sources,
+        Collection<Period> periods, Collection<ValidationRule> rules, ValidationRunType runType, Date lastAlertRun,
+        ConstantService constantService, ExpressionService expressionService, PeriodService periodService, DataValueService dataValueService)
+    {
+        ValidationRunContext context = ValidationRunContext.getNewValidationRunContext( sources, periods, rules,
+        		constantService.getConstantMap(), ValidationRunType.ALERT, lastAlertRun,
+        		expressionService, periodService, dataValueService);
+
+        int threadPoolSize = getThreadPoolSize( context );
+        ExecutorService executor = Executors.newFixedThreadPool( threadPoolSize );
+
+        for ( OrganisationUnitExtended sourceX : context.getSourceXs() )
+        {
+        	if ( sourceX.getToBeValidated() )
+        	{
+	            Runnable worker = new ValidatorThread( sourceX, context );
+	            executor.execute( worker );
+        	}
+        }
+
+        executor.shutdown();
+        
+        try
+        {
+            executor.awaitTermination( 23, TimeUnit.HOURS );
+        }
+        catch ( InterruptedException e )
+        {
+            executor.shutdownNow();
+        }
+        return context.getValidationResults();
+    }
+    
+    /**
+     * Determines how many threads we should use for testing validation rules.
+     * 
+     * @param context validation run context
+     * @return number of threads we should use for testing validation rules
+     */
+    private static int getThreadPoolSize( ValidationRunContext context )
+    {
+        int threadPoolSize = SystemUtils.getCpuCores();
+        
+        if ( threadPoolSize > 2 )
+        {
+            threadPoolSize--;
+        }
+        if ( threadPoolSize > context.getCountOfSourcesToValidate() )
+        {
+            threadPoolSize = context.getCountOfSourcesToValidate();
+        }
+
+    	return threadPoolSize;
+    }
+
+}

=== renamed file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationWorkerThread.java' => 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidatorThread.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationWorkerThread.java	2013-10-11 12:58:30 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidatorThread.java	2013-10-14 20:00:00 +0000
@@ -57,20 +57,20 @@
 /**
  * Runs a validation task on a thread within a multi-threaded validation run.
  * 
- * Each thread looks for validation results in a different organisation unit.
+ * Each task looks for validation results in a different organisation unit.
  *
  * @author Jim Grace
  */
-public class ValidationWorkerThread
+public class ValidatorThread
     implements Runnable
 {
-    private static final Log log = LogFactory.getLog( ValidationWorkerThread.class );
+    private static final Log log = LogFactory.getLog( ValidatorThread.class );
 
     private OrganisationUnitExtended sourceX;
 
     private ValidationRunContext context;
 
-    public ValidationWorkerThread( OrganisationUnitExtended sourceX, ValidationRunContext context )
+    public ValidatorThread( OrganisationUnitExtended sourceX, ValidationRunContext context )
     {
         this.sourceX = sourceX;
         this.context = context;

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/scheduling/MonitoringTask.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/scheduling/MonitoringTask.java	2013-10-13 20:16:32 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/scheduling/MonitoringTask.java	2013-10-14 20:00:00 +0000
@@ -31,18 +31,53 @@
 import static org.hisp.dhis.system.notification.NotificationLevel.ERROR;
 import static org.hisp.dhis.system.notification.NotificationLevel.INFO;
 
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hisp.dhis.constant.ConstantService;
+import org.hisp.dhis.datavalue.DataValueService;
+import org.hisp.dhis.expression.ExpressionService;
 import org.hisp.dhis.message.MessageService;
+import org.hisp.dhis.organisationunit.OrganisationUnit;
+import org.hisp.dhis.organisationunit.OrganisationUnitService;
+import org.hisp.dhis.period.CalendarPeriodType;
+import org.hisp.dhis.period.Period;
+import org.hisp.dhis.period.PeriodService;
+import org.hisp.dhis.period.PeriodType;
 import org.hisp.dhis.scheduling.TaskId;
+import org.hisp.dhis.setting.SystemSettingManager;
 import org.hisp.dhis.system.notification.Notifier;
+import org.hisp.dhis.user.User;
+import org.hisp.dhis.user.UserAuthorityGroup;
+import org.hisp.dhis.user.UserCredentials;
+import org.hisp.dhis.validation.ValidationResult;
+import org.hisp.dhis.validation.ValidationRule;
+import org.hisp.dhis.validation.ValidationRuleGroup;
 import org.hisp.dhis.validation.ValidationRuleService;
+import org.hisp.dhis.validation.ValidationRunType;
+import org.hisp.dhis.validation.Validator;
 import org.springframework.beans.factory.annotation.Autowired;
 
 /**
  * @author Lars Helge Overland
+ * @author Jim Grace
  */
 public class MonitoringTask
     implements Runnable
 {
+    private static final Log log = LogFactory.getLog( MonitoringTask.class );
+
     @Autowired
     private ValidationRuleService validationRuleService;
 
@@ -50,6 +85,24 @@
     private Notifier notifier;
 
     @Autowired
+    private ExpressionService expressionService;
+
+    @Autowired
+    private PeriodService periodService;
+
+    @Autowired
+    private OrganisationUnitService organisationUnitService;
+
+    @Autowired
+    private DataValueService dataValueService;
+
+    @Autowired
+    private ConstantService constantService;
+
+    @Autowired
+    private SystemSettingManager systemSettingManager;
+
+    @Autowired
     private MessageService messageService;
 
     private TaskId taskId;
@@ -70,7 +123,7 @@
         
         try
         {
-            validationRuleService.alertRun();
+            alertRun();
             
             notifier.notify( taskId, INFO, "Monitoring process done", true );
         }
@@ -83,4 +136,312 @@
             throw ex;
         }
     }
+
+    // -------------------------------------------------------------------------
+    // Support methods
+    // -------------------------------------------------------------------------
+
+    /**
+     * Evaluates all the validation rules that could generate alerts,
+     * and sends results (if any) to users who should be notified.
+     */
+    private void alertRun()
+    {
+        // Find all the rules belonging to groups that will send alerts to user roles.
+        Set<ValidationRule> rules = getAlertRules();
+
+        Collection<OrganisationUnit> sources = organisationUnitService.getAllOrganisationUnits();
+        Set<Period> periods = getAlertPeriodsFromRules( rules );
+        Date lastAlertRun = (Date) systemSettingManager.getSystemSetting( SystemSettingManager.KEY_LAST_ALERT_RUN );
+        
+        // Any database changes after this moment will contribute to the next run.
+        
+        Date thisAlertRun = new Date();
+        
+        log.info( "alertRun sources[" + sources.size() + "] periods[" + periods.size() + "] rules[" + rules.size()
+            + "] last run " + lastAlertRun == null ? "(none)" : lastAlertRun );
+        
+        Collection<ValidationResult> results = Validator.validate( sources, periods, rules, ValidationRunType.ALERT,
+            lastAlertRun, constantService, expressionService, periodService, dataValueService );
+        
+        log.trace( "alertRun() results[" + results.size() + "]" );
+        
+        if ( !results.isEmpty() )
+        {
+            postAlerts( results, thisAlertRun ); // Alert the users.
+        }
+        
+        systemSettingManager.saveSystemSetting( SystemSettingManager.KEY_LAST_ALERT_RUN, thisAlertRun );
+    }
+
+    /**
+     * Gets all the validation rules that could generate alerts.
+     * 
+     * @return rules that will generate alerts
+     */
+    private Set<ValidationRule> getAlertRules()
+    {
+        Set<ValidationRule> rules = new HashSet<ValidationRule>();
+        for ( ValidationRuleGroup validationRuleGroup : validationRuleService.getAllValidationRuleGroups() )
+        {
+        	Set<UserAuthorityGroup> userRolesToAlert = validationRuleGroup.getUserAuthorityGroupsToAlert();
+            if ( userRolesToAlert != null && !userRolesToAlert.isEmpty() )
+            {
+                rules.addAll( validationRuleGroup.getMembers() );
+            }
+        }
+        
+        return rules;
+    }
+
+    /**
+     * Gets the current and most recent periods to search, based on
+     * the period types from the rules to run.
+     * 
+     * For each period type, return the period containing the current date
+     * (if any), and the most recent previous period. Add whichever of
+     * these periods actually exist in the database.
+     * 
+     * @param rules the ValidationRules to be evaluated on this alert run
+     * @return periods to search for new alerts
+     */
+    private Set<Period> getAlertPeriodsFromRules( Set<ValidationRule> rules )
+    {
+        Set<Period> periods = new HashSet<Period>();
+
+        Set<PeriodType> rulePeriodTypes = getPeriodTypesFromRules( rules );
+
+        for ( PeriodType periodType : rulePeriodTypes )
+        {
+            CalendarPeriodType calendarPeriodType = ( CalendarPeriodType ) periodType;
+            Period currentPeriod = calendarPeriodType.createPeriod();
+            Period previousPeriod = calendarPeriodType.getPreviousPeriod( currentPeriod );
+            periods.addAll( periodService.getIntersectingPeriodsByPeriodType( periodType,
+                previousPeriod.getStartDate(), currentPeriod.getEndDate() ) );
+            // Note: If the last successful daily run was more than one day
+            // ago, we might consider adding some additional periods of type 
+            // DailyPeriodType so we don't miss any alerts.
+        }
+
+        return periods;
+    }
+
+    /**
+     * Gets the Set of period types found in a set of rules.
+     * 
+     * Note that that we have to get periodType from periodService,
+     * otherwise the ID will not be present.)
+     * 
+     * @param rules validation rules of interest
+     * @return period types contained in those rules
+     */
+    private Set<PeriodType> getPeriodTypesFromRules ( Collection<ValidationRule> rules )
+    {
+        Set<PeriodType> rulePeriodTypes = new HashSet<PeriodType>();
+        
+        for ( ValidationRule rule : rules )
+        {
+            rulePeriodTypes.add( periodService.getPeriodTypeByName( rule.getPeriodType().getName() ) );
+        }
+        
+        return rulePeriodTypes;
+    }
+
+    /**
+     * At the end of an ALERT run, post messages to the users who want to see
+     * the results.
+     * 
+     * Create one message for each set of users who receive the same
+     * subset of results. (Not necessarily the same as the set of users who
+     * receive alerts from the same subset of validation rules -- because
+     * some of these rules may return no results.) This saves on message
+     * storage space.
+     * 
+     * The message results are sorted into their natural order.
+     * 
+     * TODO: Internationalize the messages according to the user's
+     * preferred language, and generate a message for each combination of
+     * ( target language, set of results ).
+     * 
+     * @param validationResults the set of validation error results
+     * @param alertRunStart the date/time when the alert run started
+     */
+    private void postAlerts( Collection<ValidationResult> validationResults, Date alertRunStart )
+    {
+        SortedSet<ValidationResult> results = new TreeSet<ValidationResult>( validationResults );
+
+        Map<List<ValidationResult>, Set<User>> messageMap = getMessageMap( results );
+        
+        for ( Map.Entry<List<ValidationResult>, Set<User>> entry : messageMap.entrySet() )
+        {
+            sendAlertmessage( entry.getKey(), entry.getValue(), alertRunStart );
+        }
+    }
+
+    /**
+     * Returns a map where the key is a (sorted) list of validation results
+     * to assemble into a message, and the value is the set of users who
+     * should receive this message.
+     * 
+     * @param results all the validation run results
+     * @return map of result sets to users
+     */
+    private Map<List<ValidationResult>, Set<User>> getMessageMap( Set<ValidationResult> results )
+    {
+        Map<User, Set<ValidationRule>> userRulesMap = getUserRulesMap();
+
+	    Map<List<ValidationResult>, Set<User>> messageMap = new HashMap<List<ValidationResult>, Set<User>>();
+	    
+	    for ( User user : userRulesMap.keySet() )
+	    {
+	        // For each user who receives alerts, find the subset of results
+	        // from this run.
+	        Collection<ValidationRule> userRules = userRulesMap.get( user );
+	        List<ValidationResult> userResults = new ArrayList<ValidationResult>();
+	
+	        for ( ValidationResult result : results )
+	        {
+	            if ( userRules.contains( result.getValidationRule() ) )
+	            {
+	                userResults.add( result );
+	            }
+	        }
+	
+	        // Group this user with any other users who have the same subset
+	        // of results.
+	        if ( !userResults.isEmpty() )
+	        {
+	            Set<User> messageReceivers = messageMap.get( userResults );
+	            if ( messageReceivers == null )
+	            {
+	                messageReceivers = new HashSet<User>();
+	                messageMap.put( userResults, messageReceivers );
+	            }
+	            messageReceivers.add( user );
+	        }
+	    }
+	    return messageMap;
+    }
+
+    /**
+     * Constructs a Map where the key is each user who is configured to
+     * receive alerts, and the value is a list of rules they should receive
+     * results for.
+     * 
+     * @return Map from users to sets of rules
+     */
+    private Map<User, Set<ValidationRule>> getUserRulesMap()
+    {
+	    Map<User, Set<ValidationRule>> userRulesMap = new HashMap<User, Set<ValidationRule>>();
+	    
+	    for ( ValidationRuleGroup validationRuleGroup : validationRuleService.getAllValidationRuleGroups() )
+	    {
+	        Collection<UserAuthorityGroup> userRolesToAlert = validationRuleGroup.getUserAuthorityGroupsToAlert();
+	        if ( userRolesToAlert != null && !userRolesToAlert.isEmpty() )
+	        {
+	            for ( UserAuthorityGroup role : userRolesToAlert )
+	            {
+	                for ( UserCredentials userCredentials : role.getMembers() )
+	                {
+	                    User user = userCredentials.getUser();
+	                    Set<ValidationRule> userRules = userRulesMap.get( user );
+	                    if ( userRules == null )
+	                    {
+	                        userRules = new HashSet<ValidationRule>();
+	                        userRulesMap.put( user, userRules );
+	                    }
+	                    userRules.addAll( validationRuleGroup.getMembers() );
+	                }
+	            }
+	        }
+	    }
+	    return userRulesMap;
+    }
+    
+    /**
+     * Generate and send an alert message containing a list of validation
+     * results to a set of users.
+     * 
+     * @param results results to put in this message
+     * @param users users to receive these results
+     * @param alertRunStart date/time when the alert run started
+     */
+    private void sendAlertmessage( List<ValidationResult> results, Set<User> users, Date alertRunStart )
+    {
+        StringBuilder messageBuilder = new StringBuilder();
+
+        SimpleDateFormat dateTimeFormatter = new SimpleDateFormat( "yyyy-MM-dd HH:mm" );
+
+        Map<String, Integer> importanceCountMap = countTheResultsByImportanceType( results );
+
+        String subject = "DHIS alerts as of " + dateTimeFormatter.format( alertRunStart ) + " - priority High "
+            + (importanceCountMap.get( "high" ) == null ? 0 : importanceCountMap.get( "high" )) + ", Medium "
+            + (importanceCountMap.get( "medium" ) == null ? 0 : importanceCountMap.get( "medium" )) + ", Low "
+            + (importanceCountMap.get( "low" ) == null ? 0 : importanceCountMap.get( "low" ));
+
+        messageBuilder
+            .append( "<html>" )
+            .append( "<head>" ).append( "</head>" )
+            .append( "<body>" ).append( subject ).append( "<br />" )
+            .append( "<table>" )
+            .append( "<tr>" )
+            .append( "<th>Organisation Unit</th>" )
+            .append( "<th>Period</th>" )
+            .append( "<th>Importance</th>" )
+            .append( "<th>Left side description</th>" )
+            .append( "<th>Value</th>" )
+            .append( "<th>Operator</th>" )
+            .append( "<th>Value</th>" )
+            .append( "<th>Right side description</th>" )
+            .append( "</tr>" );
+
+        for ( ValidationResult result : results )
+        {
+            ValidationRule rule = result.getValidationRule();
+
+            messageBuilder
+            	.append( "<tr>" )
+            	.append( "<td>" ).append( result.getSource().getName() ).append( "<\td>" )
+                .append( "<td>" ).append( result.getPeriod().getName() ).append( "<\td>" )
+                .append( "<td>" ).append( rule.getImportance() ).append( "<\td>" )
+                .append( "<td>" ).append( rule.getLeftSide().getDescription() ).append( "<\td>" )
+                .append( "<td>" ).append( result.getLeftsideValue() ).append( "<\td>" )
+                .append( "<td>" ).append( rule.getOperator().toString() ).append( "<\td>" )
+                .append( "<td>" ).append( result.getRightsideValue() ).append( "<\td>" )
+                .append( "<td>" ).append( rule.getRightSide().getDescription() ).append( "<\td>" )
+                .append( "</tr>" );
+        }
+
+        messageBuilder
+        	.append( "</table>" )
+        	.append( "</body>" )
+        	.append( "</html>" );
+
+        String messageText = messageBuilder.toString();
+
+        log.info( "Alerting users[" + users.size() + "] subject " + subject );
+        messageService.sendMessage( subject, messageText, null, users );
+    }
+    
+    /**
+     * Counts the results of each importance type, for all the importance
+     * types that are found within the results.
+     * 
+     * @param results results to analyze
+     * @return Map showing the result count for each importance type
+     */
+    private Map<String, Integer> countTheResultsByImportanceType ( List<ValidationResult> results )
+    {
+        Map<String, Integer> importanceCountMap = new HashMap<String, Integer>();
+        
+        for ( ValidationResult result : results )
+        {
+            Integer importanceCount = importanceCountMap.get( result.getValidationRule().getImportance() );
+            
+            importanceCountMap.put( result.getValidationRule().getImportance(), importanceCount == null ? 1
+                : importanceCount + 1 );
+        }
+        
+        return importanceCountMap;
+    }
 }

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml'
--- dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml	2013-10-13 20:16:32 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml	2013-10-14 20:00:00 +0000
@@ -433,11 +433,8 @@
     <property name="expressionService" ref="org.hisp.dhis.expression.ExpressionService" />
     <property name="dataEntryFormService" ref="org.hisp.dhis.dataentryform.DataEntryFormService" />
     <property name="periodService" ref="org.hisp.dhis.period.PeriodService" />
-    <property name="organisationUnitService" ref="org.hisp.dhis.organisationunit.OrganisationUnitService" />
-    <property name="systemSettingManager" ref="org.hisp.dhis.setting.SystemSettingManager" />
     <property name="constantService" ref="org.hisp.dhis.constant.ConstantService" />
     <property name="dataValueService" ref="org.hisp.dhis.datavalue.DataValueService" />
-    <property name="messageService" ref="org.hisp.dhis.message.MessageService" />
     <property name="i18nService" ref="org.hisp.dhis.i18n.I18nService" />
   </bean>