← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 12807: Validation/monitoring. Moved methods inMonitoringTask inside ValidationRuleService to make the fu...

 

------------------------------------------------------------
revno: 12807
committer: Lars Helge Øverland <larshelge@xxxxxxxxx>
branch nick: dhis2
timestamp: Thu 2013-10-24 00:03:17 +0200
message:
  Validation/monitoring. Moved methods inMonitoringTask inside ValidationRuleService to make the function run inside a transaction, avoiding hibernate session problems.
modified:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/scheduling/TaskCategory.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleService.java
  dhis-2/dhis-services/dhis-service-core/pom.xml
  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/Validator.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-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/ResourceTableController.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/scheduling/TaskCategory.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/scheduling/TaskCategory.java	2013-09-04 11:58:22 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/scheduling/TaskCategory.java	2013-10-23 22:03:17 +0000
@@ -35,6 +35,8 @@
 {
     DATAMART,
     RESOURCETABLE_UPDATE,
+    ANALYTICSTABLE_UPDATE,
+    MONITORING,
     DATAVALUE_IMPORT,
     EVENT_IMPORT,
     METADATA_IMPORT,

=== 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-16 13:41:01 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleService.java	2013-10-23 22:03:17 +0000
@@ -92,6 +92,12 @@
      */
     Collection<ValidationResult> validate( Date startDate, Date endDate, OrganisationUnit source );
 
+    /**
+     * Evaluates all the validation rules that could generate alerts,
+     * and sends results (if any) to users who should be notified.
+     */
+    void scheduledRun();
+    
     // -------------------------------------------------------------------------
     // ValidationRule
     // -------------------------------------------------------------------------

=== modified file 'dhis-2/dhis-services/dhis-service-core/pom.xml'
--- dhis-2/dhis-services/dhis-service-core/pom.xml	2013-10-17 06:57:37 +0000
+++ dhis-2/dhis-services/dhis-service-core/pom.xml	2013-10-23 22:03:17 +0000
@@ -92,7 +92,6 @@
       <groupId>org.smslib</groupId>
       <artifactId>smslib</artifactId>
     </dependency>
-
     <dependency>
       <groupId>com.googlecode.jsmpp</groupId>
       <artifactId>jsmpp</artifactId>

=== 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-21 14:19:10 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java	2013-10-23 22:03:17 +0000
@@ -34,11 +34,17 @@
 import static org.hisp.dhis.i18n.I18nUtils.getObjectsByName;
 import static org.hisp.dhis.i18n.I18nUtils.i18n;
 
+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;
@@ -51,11 +57,19 @@
 import org.hisp.dhis.datavalue.DataValueService;
 import org.hisp.dhis.expression.ExpressionService;
 import org.hisp.dhis.i18n.I18nService;
+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.setting.SystemSettingManager;
 import org.hisp.dhis.system.util.Filter;
 import org.hisp.dhis.system.util.FilterUtils;
+import org.hisp.dhis.user.User;
+import org.hisp.dhis.user.UserAuthorityGroup;
+import org.hisp.dhis.user.UserCredentials;
 import org.springframework.transaction.annotation.Transactional;
 
 /**
@@ -128,11 +142,33 @@
     {
         i18nService = service;
     }
+    
+    private MessageService messageService;
+
+    public void setMessageService( MessageService messageService )
+    {
+        this.messageService = messageService;
+    }
+    
+    private OrganisationUnitService organisationUnitService;
+
+    public void setOrganisationUnitService( OrganisationUnitService organisationUnitService )
+    {
+        this.organisationUnitService = organisationUnitService;
+    }
+    
+    private SystemSettingManager systemSettingManager;
+
+    public void setSystemSettingManager( SystemSettingManager systemSettingManager )
+    {
+        this.systemSettingManager = systemSettingManager;
+    }
 
     // -------------------------------------------------------------------------
     // ValidationRule business logic
     // -------------------------------------------------------------------------
 
+    @Override
     public Collection<ValidationResult> validate( Date startDate, Date endDate, Collection<OrganisationUnit> sources )
     {
         log.info( "Validate startDate=" + startDate + " endDate=" + endDate + " sources[" + sources.size() + "]" );
@@ -143,6 +179,7 @@
             constantService, expressionService, periodService, dataValueService );
     }
 
+    @Override
     public Collection<ValidationResult> validate( Date startDate, Date endDate, Collection<OrganisationUnit> sources,
         ValidationRuleGroup group )
     {
@@ -154,6 +191,7 @@
             constantService, expressionService, periodService, dataValueService );
     }
 
+    @Override
     public Collection<ValidationResult> validate( Date startDate, Date endDate, OrganisationUnit source )
     {
     	log.info( "Validate startDate=" + startDate + " endDate=" + endDate + " source=" + source.getName() );
@@ -166,6 +204,7 @@
             constantService, expressionService, periodService, dataValueService );
     }
 
+    @Override
     public Collection<ValidationResult> validate( DataSet dataSet, Period period, OrganisationUnit source )
     {
     	log.info( "Validate dataSet=" + dataSet.getName() + " period=[" + period.getPeriodType().getName() + " "
@@ -191,10 +230,322 @@
             constantService, expressionService, periodService, dataValueService );
     }
 
-    // -------------------------------------------------------------------------
-    // Support methods
-    // -------------------------------------------------------------------------
-
+    @Override
+    public void scheduledRun()
+    {
+        // 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 lastScheduledRun = (Date) systemSettingManager.getSystemSetting( SystemSettingManager.KEY_LAST_MONITORING_RUN );
+        
+        // Any database changes after this moment will contribute to the next run.
+        
+        Date thisRun = new Date();
+        
+        log.info( "Scheduled monitoring run sources[" + sources.size() + "] periods[" + periods.size() + "] rules[" + rules.size()
+            + "] last run " + lastScheduledRun == null ? "(none)" : lastScheduledRun );
+        
+        Collection<ValidationResult> results = Validator.validate( sources, periods, rules, ValidationRunType.SCHEDULED,
+            lastScheduledRun, constantService, expressionService, periodService, dataValueService );
+        
+        log.trace( "scheduledRun() results[" + results.size() + "]" );
+        
+        if ( !results.isEmpty() )
+        {
+            postAlerts( results, thisRun );
+        }
+        
+        systemSettingManager.saveSystemSetting( SystemSettingManager.KEY_LAST_MONITORING_RUN, thisRun );
+    }
+
+    // -------------------------------------------------------------------------
+    // Supportive methods - scheduled run
+    // -------------------------------------------------------------------------
+
+    /**
+     * 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 : 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.
+     * 
+     * TODO If the last successful daily run was more than one day ago, we might 
+     * add some additional periods of type DailyPeriodType not to miss any
+     * alerts.
+     *
+     * @param rules the ValidationRules to be evaluated on this 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() ) );            
+        }
+
+        return periods;
+    }
+
+    /**
+     * At the end of a scheduled monitoring 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 scheduledRunStart the date/time when this scheduled run started
+     */
+    private void postAlerts( Collection<ValidationResult> validationResults, Date scheduledRunStart )
+    {
+        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(), scheduledRunStart );
+        }
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * 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 users receiving alerts, find the subset of results from 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 other users having the same result subset.
+
+            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 : 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 scheduledRunStart date/time when the scheduled run started
+     */
+    private void sendAlertmessage( List<ValidationResult> results, Set<User> users, Date scheduledRunStart )
+    {
+        StringBuilder messageBuilder = new StringBuilder();
+
+        SimpleDateFormat dateTimeFormatter = new SimpleDateFormat( "yyyy-MM-dd HH:mm" );
+
+        Map<String, Integer> importanceCountMap = countResultsByImportanceType( results );
+
+        String subject = "DHIS alerts as of " + dateTimeFormatter.format( scheduledRunStart ) + " - 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 );
+    }
+
+    // -------------------------------------------------------------------------
+    // Supportive methods - monitoring
+    // -------------------------------------------------------------------------
+
+    /**
+     * Counts the results of each importance type, for all the importance
+     * types that are found within the results.
+     * 
+     * @param results results to analyze
+     * @return Mapping between importance type and result counts.
+     */
+    private Map<String, Integer> countResultsByImportanceType ( 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;
+    }
+    
     /**
      * Returns all validation-type rules which have specified data elements
      * assigned to them.

=== 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	2013-10-16 16:00:05 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/Validator.java	2013-10-23 22:03:17 +0000
@@ -69,7 +69,7 @@
      */
     public static Collection<ValidationResult> validate( Collection<OrganisationUnit> sources,
         Collection<Period> periods, Collection<ValidationRule> rules, ValidationRunType runType, Date lastScheduledRun,
-        ConstantService constantService, ExpressionService expressionService, PeriodService periodService, DataValueService dataValueService)
+        ConstantService constantService, ExpressionService expressionService, PeriodService periodService, DataValueService dataValueService )
     {
         ValidationRunContext context = ValidationRunContext.getNewValidationRunContext( sources, periods, rules,
             constantService.getConstantMap(), ValidationRunType.SCHEDULED, lastScheduledRun,

=== 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-16 16:00:05 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/scheduling/MonitoringTask.java	2013-10-23 22:03:17 +0000
@@ -31,42 +31,16 @@
 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;
 
 /**
@@ -76,8 +50,6 @@
 public class MonitoringTask
     implements Runnable
 {
-    private static final Log log = LogFactory.getLog( MonitoringTask.class );
-
     @Autowired
     private ValidationRuleService validationRuleService;
 
@@ -123,7 +95,7 @@
         
         try
         {
-            scheduledRun();
+            validationRuleService.scheduledRun();
             
             notifier.notify( taskId, INFO, "Monitoring process done", true );
         }
@@ -136,319 +108,4 @@
             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 scheduledRun()
-    {
-        // 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 lastScheduledRun = (Date) systemSettingManager.getSystemSetting( SystemSettingManager.KEY_LAST_MONITORING_RUN );
-        
-        // Any database changes after this moment will contribute to the next run.
-        
-        Date thisScheduledRun = new Date();
-        
-        log.info( "Scheduled monitoring run sources[" + sources.size() + "] periods[" + periods.size() + "] rules[" + rules.size()
-            + "] last run " + lastScheduledRun == null ? "(none)" : lastScheduledRun );
-        
-        Collection<ValidationResult> results = Validator.validate( sources, periods, rules, ValidationRunType.SCHEDULED,
-            lastScheduledRun, constantService, expressionService, periodService, dataValueService );
-        
-        log.trace( "scheduledRun() results[" + results.size() + "]" );
-        
-        if ( !results.isEmpty() )
-        {
-            postAlerts( results, thisScheduledRun );
-        }
-        
-        systemSettingManager.saveSystemSetting( SystemSettingManager.KEY_LAST_MONITORING_RUN, thisScheduledRun );
-    }
-
-    /**
-     * 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.
-     * 
-     * TODO If the last successful daily run was more than one day ago, we might 
-     * add some additional periods of type DailyPeriodType not to miss any
-     * alerts.
-     *
-     * @param rules the ValidationRules to be evaluated on this 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() ) );            
-        }
-
-        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 a scheduled monitoring 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 scheduledRunStart the date/time when this scheduled run started
-     */
-    private void postAlerts( Collection<ValidationResult> validationResults, Date scheduledRunStart )
-    {
-        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(), scheduledRunStart );
-        }
-    }
-
-    /**
-     * 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 users receiving alerts, find the subset of results from 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 other users having the same result subset.
-
-            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 scheduledRunStart date/time when the scheduled run started
-     */
-    private void sendAlertmessage( List<ValidationResult> results, Set<User> users, Date scheduledRunStart )
-    {
-        StringBuilder messageBuilder = new StringBuilder();
-
-        SimpleDateFormat dateTimeFormatter = new SimpleDateFormat( "yyyy-MM-dd HH:mm" );
-
-        Map<String, Integer> importanceCountMap = countResultsByImportanceType( results );
-
-        String subject = "DHIS alerts as of " + dateTimeFormatter.format( scheduledRunStart ) + " - 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 Mapping between importance type and result counts.
-     */
-    private Map<String, Integer> countResultsByImportanceType ( 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-16 11:58:17 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml	2013-10-23 22:03:17 +0000
@@ -436,6 +436,9 @@
     <property name="constantService" ref="org.hisp.dhis.constant.ConstantService" />
     <property name="dataValueService" ref="org.hisp.dhis.datavalue.DataValueService" />
     <property name="i18nService" ref="org.hisp.dhis.i18n.I18nService" />
+    <property name="messageService" ref="org.hisp.dhis.message.MessageService" />
+    <property name="organisationUnitService" ref="org.hisp.dhis.organisationunit.OrganisationUnitService" />
+    <property name="systemSettingManager" ref="org.hisp.dhis.setting.SystemSettingManager" />
   </bean>
   
   <!-- Scheduled task for data monitoring -->

=== modified file 'dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/ResourceTableController.java'
--- dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/ResourceTableController.java	2013-09-30 19:54:38 +0000
+++ dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/ResourceTableController.java	2013-10-23 22:03:17 +0000
@@ -39,6 +39,7 @@
 import org.hisp.dhis.scheduling.TaskId;
 import org.hisp.dhis.system.scheduling.Scheduler;
 import org.hisp.dhis.user.CurrentUserService;
+import org.hisp.dhis.validation.scheduling.MonitoringTask;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Controller;
@@ -64,6 +65,9 @@
     private ResourceTableTask resourceTableTask;
     
     @Autowired
+    private MonitoringTask monitoringTask;
+    
+    @Autowired
     private Scheduler scheduler;
     
     @Autowired
@@ -103,4 +107,15 @@
         
         ContextUtils.okResponse( response, "Initiated resource table update" );
     }    
+
+    @RequestMapping( value = "/monitoring", method = { RequestMethod.PUT, RequestMethod.POST } )
+    @PreAuthorize( "hasRole('ALL') or hasRole('F_PERFORM_MAINTENANCE')" )
+    public void monitoring( HttpServletResponse response )
+    {
+        monitoringTask.setTaskId( new TaskId( TaskCategory.MONITORING, currentUserService.getCurrentUser() ) );
+        
+        scheduler.executeTask( monitoringTask );
+        
+        ContextUtils.okResponse( response, "Initiated data monitoring" );
+    }   
 }