dhis2-devs team mailing list archive
-
dhis2-devs team
-
Mailing list archive
-
Message #25304
[Branch ~dhis2-devs-core/dhis2/trunk] Rev 12518: Refactor validationRule/alerts code per code review
Merge authors:
Jim Grace (dhis2-c)
------------------------------------------------------------
revno: 12518 [merge]
committer: dhis2-c <dhis2@xxxxxxxxxxxxxx>
branch nick: trunk
timestamp: Wed 2013-10-09 01:24:32 -0400
message:
Refactor validationRule/alerts code per code review
added:
dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/OrganisationUnitExtended.java
dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/PeriodTypeExtended.java
dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRuleExtended.java
dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRunContext.java
dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRunType.java
dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationWorkerThread.java
modified:
dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationResult.java
dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.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/ValidationResult.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationResult.java 2013-10-08 19:10:40 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationResult.java 2013-10-09 05:16:53 +0000
@@ -152,30 +152,36 @@
public int compareTo( ValidationResult other )
{
- int result = source.getName().compareTo( other.source.getName() );
-
- if ( result == 0 )
- {
- result = period.getStartDate().compareTo( other.period.getStartDate() );
-
- if ( result == 0 )
- {
- result = period.getEndDate().compareTo( other.period.getEndDate() );
-
- if ( result == 0 )
- {
- result = validationImportanceOrder( validationRule.getImportance() )
- - validationImportanceOrder( other.validationRule.getImportance() );
-
- if ( result == 0 )
- {
- result = validationRule.getLeftSide().getDescription()
- .compareTo( other.validationRule.getLeftSide().getDescription() );
- }
- }
- }
- }
- return result;
+ if ( source.getName().compareTo( other.source.getName() ) != 0 )
+ {
+ return source.getName().compareTo( other.source.getName() );
+ }
+ else if ( period.getStartDate().compareTo( other.period.getStartDate() ) != 0 )
+ {
+ return period.getStartDate().compareTo( other.period.getStartDate() );
+ }
+ else if ( source.getName().compareTo( other.source.getName() ) != 0 )
+ {
+ return source.getName().compareTo( other.source.getName() );
+ }
+ else if ( period.getStartDate().compareTo( other.period.getStartDate() ) != 0 )
+ {
+ return period.getStartDate().compareTo( other.period.getStartDate() );
+ }
+ else if ( period.getEndDate().compareTo( other.period.getEndDate() ) != 0 )
+ {
+ return period.getEndDate().compareTo( other.period.getEndDate() );
+ }
+ else if ( validationRule.getImportance().compareTo( other.validationRule.getImportance() ) != 0 )
+ {
+ return validationImportanceOrder( validationRule.getImportance() )
+ - validationImportanceOrder( other.validationRule.getImportance() );
+ }
+ else
+ {
+ return validationRule.getLeftSide().getDescription()
+ .compareTo( other.validationRule.getLeftSide().getDescription() );
+ }
}
private int validationImportanceOrder ( String importance )
=== 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-08 19:10:40 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java 2013-10-09 05:16:53 +0000
@@ -33,16 +33,10 @@
import static org.hisp.dhis.i18n.I18nUtils.getObjectsBetweenByName;
import static org.hisp.dhis.i18n.I18nUtils.getObjectsByName;
import static org.hisp.dhis.i18n.I18nUtils.i18n;
-import static org.hisp.dhis.system.util.MathUtils.expressionIsTrue;
-import static org.hisp.dhis.system.util.MathUtils.getRounded;
-import static org.hisp.dhis.system.util.MathUtils.zeroIfNull;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
import java.util.Collection;
-import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@@ -51,13 +45,10 @@
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
-import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
-import org.apache.commons.lang.builder.ToStringBuilder;
-import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hisp.dhis.common.GenericIdentifiableObjectStore;
@@ -68,7 +59,6 @@
import org.hisp.dhis.dataset.DataSet;
import org.hisp.dhis.datavalue.DataValueService;
import org.hisp.dhis.expression.ExpressionService;
-import org.hisp.dhis.expression.Operator;
import org.hisp.dhis.i18n.I18nService;
import org.hisp.dhis.message.MessageService;
import org.hisp.dhis.organisationunit.OrganisationUnit;
@@ -97,150 +87,6 @@
{
private static final Log log = LogFactory.getLog( DefaultValidationRuleService.class );
- /**
- * Defines how many decimal places for rounding the left and right side
- * evaluation values in the report of results.
- */
- private static final int DECIMALS = 1;
-
- /**
- * Defines the types of alert run.
- */
- private enum ValidationRunType
- {
- INTERACTIVE, ALERT
- }
-
- /**
- * This private subclass holds information for each organisation unit that
- * is needed during a validation run (either interactive or an alert run).
- *
- * It is important that they should be copied from Hibernate lazy
- * collections before the multithreaded part of the run starts, otherwise
- * the threads may not be able to access these values.
- */
- private class OrganisationUnitExtended
- {
- OrganisationUnit source;
-
- Collection<OrganisationUnit> children;
-
- int level;
-
- 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();
- }
- }
-
- /**
- * This private subclass holds information for each period type that is
- * needed during a validation run (either interactive or an alert run).
- *
- * By computing these values once at the start of a validation run, we avoid
- * the overhead of having to compute them during the processing of every
- * organisation unit. For some of these properties this is also important
- * because they should be copied from Hibernate lazy collections before the
- * multithreaded part of the run starts, otherwise the threads may not be
- * able to access these values.
- */
- private class PeriodTypeExtended
- {
- PeriodType periodType;
-
- Collection<Period> periods;
-
- Collection<ValidationRule> rules;
-
- Collection<DataElement> dataElements;
-
- Collection<PeriodType> allowedPeriodTypes;
-
- Map<OrganisationUnit, Collection<DataElement>> sourceDataElements;
-
- 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();
- }
- }
-
- /**
- * This private subclass holds common values that are used during a
- * validation run (either interactive or an alert run.) These values don't
- * change during the multi-threaded tasks (except that results entries are
- * added in a threadsafe way.)
- *
- * Some of the values are precalculated collections, to save CPU time during
- * the run. All of these values are stored in this single "context" object
- * to allow a single object reference for each of the scheduled tasks. (This
- * also reduces the amount of memory needed to queue all the multi-threaded
- * tasks.)
- *
- * For some of these properties this is also important because they should
- * be copied from Hibernate lazy collections before the multithreaded part
- * of the run starts, otherwise the threads may not be able to access these
- * values.
- */
- private class ValidationRunContext
- {
- private Map<PeriodType, PeriodTypeExtended> PeriodTypeExtendedMap;
-
- private ValidationRunType runType;
-
- private Date lastAlertRun;
-
- private Map<String, Double> constantMap;
-
- private Collection<OrganisationUnitExtended> sourceXs;
-
- private Collection<ValidationResult> validationResults;
-
- public String toString()
- {
- return new ToStringBuilder( this, ToStringStyle.SHORT_PREFIX_STYLE )
- .append( "\n PeriodTypeExtendedMap", (Arrays.toString( PeriodTypeExtendedMap.entrySet().toArray() )) )
- .append( "\n runType", runType ).append( "\n lastAlertRun", lastAlertRun )
- .append( "\n constantMap", "[" + constantMap.size() + "]" )
- .append( "\n sourceXs", Arrays.toString( sourceXs.toArray() ) )
- .append( "\n validationResults", Arrays.toString( validationResults.toArray() ) ).toString();
- }
- }
-
- /**
- * Runs a validation task on a thread within a multi-threaded validation
- * run.
- *
- * Each thread looks for validation results in a different organisation
- * unit.
- */
- private class ValidationWorkerThread
- implements Runnable
- {
- private OrganisationUnitExtended sourceX;
-
- private ValidationRunContext context;
-
- private ValidationWorkerThread( OrganisationUnitExtended sourceX, ValidationRunContext context )
- {
- this.sourceX = sourceX;
- this.context = context;
- }
-
- @Override
- public void run()
- {
- validateSource( sourceX, context );
- }
- }
-
// -------------------------------------------------------------------------
// Dependencies
// -------------------------------------------------------------------------
@@ -433,8 +279,8 @@
}
/**
- * Evaluates validation rules for a collection of organisation units. This
- * method breaks the job down by organisation unit. It assigns the
+ * Evaluates validation rules for a collection of organisation units.
+ * This method breaks the job down by organisation unit. It assigns the
* evaluation for each organisation unit to a task that can be evaluated
* independently in a multithreaded environment.
*
@@ -450,159 +296,39 @@
private Collection<ValidationResult> validateInternal( Collection<OrganisationUnit> sources,
Collection<Period> periods, Collection<ValidationRule> rules, ValidationRunType runType, Date lastAlertRun )
{
- ValidationRunContext context = buildNewContext( sources, periods, rules, ValidationRunType.ALERT, lastAlertRun );
- boolean singleThreadedOption = false;
-
- if ( singleThreadedOption )
- {
- for ( OrganisationUnitExtended sourceX : context.sourceXs )
- {
- validateSource( sourceX, context );
- }
- }
- else
- {
- int threadPoolSize = SystemUtils.getCpuCores();
- if ( threadPoolSize > 2 )
- {
- threadPoolSize--;
- }
- if ( threadPoolSize > sources.size() )
- {
- threadPoolSize = sources.size();
- }
-
- ExecutorService executor = Executors.newFixedThreadPool( threadPoolSize );
-
- for ( OrganisationUnitExtended sourceX : context.sourceXs )
- {
- Runnable worker = new ValidationWorkerThread( sourceX, context );
- executor.execute( worker );
- }
-
- executor.shutdown();
- try
- {
- executor.awaitTermination( 23, TimeUnit.HOURS );
- }
- catch ( InterruptedException e )
- {
- executor.shutdownNow();
- }
- }
- return context.validationResults;
- }
-
- /**
- * Creates and fills a new context object for a validation run.
- *
- * @param sources organisation units for validation
- * @param periods periods for validation
- * @param rules validation rules for validation
- * @param runType whether this is an INTERACTIVE or ALERT run
- * @param lastAlertRun (for ALERT runs) date of previous alert run
- * @return context object for this run
- */
- private ValidationRunContext buildNewContext( Collection<OrganisationUnit> sources, Collection<Period> periods,
- Collection<ValidationRule> rules, ValidationRunType runType, Date lastAlertRun )
- {
- ValidationRunContext context = new ValidationRunContext();
- context.runType = runType;
- context.lastAlertRun = lastAlertRun;
- context.validationResults = new ConcurrentLinkedQueue<ValidationResult>(); // thread-safe
- context.PeriodTypeExtendedMap = new HashMap<PeriodType, PeriodTypeExtended>();
- context.sourceXs = new HashSet<OrganisationUnitExtended>();
-
- context.constantMap = new HashMap<String, Double>();
- context.constantMap.putAll( constantService.getConstantMap() );
-
- // Group the periods by period type.
- for ( Period period : periods )
- {
- PeriodTypeExtended periodTypeX = getOrCreatePeriodTypeExtended( context, period.getPeriodType() );
- periodTypeX.periods.add( period );
- }
-
- for ( ValidationRule rule : rules )
- {
- // Find the period type extended for this rule
- PeriodTypeExtended periodTypeX = getOrCreatePeriodTypeExtended( context, rule.getPeriodType() );
- periodTypeX.rules.add( rule ); // Add this rule to the period type ext.
-
- if ( rule.getCurrentDataElements() != null )
- {
- // Add this rule's data elements to the period extended.
- periodTypeX.dataElements.addAll( rule.getCurrentDataElements() );
- }
- // Add the allowed period types for this rule's data elements:
- periodTypeX.allowedPeriodTypes.addAll( getAllowedPeriodTypesForDataElements( rule.getCurrentDataElements(),
- rule.getPeriodType() ) );
- }
-
- // We only need to keep period types that are selected and also used by
- // rules that are selected.
- // Start by making a defensive copy so we can delete while iterating.
- Set<PeriodTypeExtended> periodTypeXs = new HashSet<PeriodTypeExtended>( context.PeriodTypeExtendedMap.values() );
- for ( PeriodTypeExtended periodTypeX : periodTypeXs )
- {
- if ( periodTypeX.periods.isEmpty() || periodTypeX.rules.isEmpty() )
- {
- context.PeriodTypeExtendedMap.remove( periodTypeX.periodType );
- }
- }
-
- for ( OrganisationUnit source : sources )
- {
- OrganisationUnitExtended sourceX = new OrganisationUnitExtended();
- sourceX.source = source;
- sourceX.children = new HashSet<OrganisationUnit>( source.getChildren() );
- sourceX.level = source.getOrganisationUnitLevel();
- context.sourceXs.add( sourceX );
-
- Map<PeriodType, Set<DataElement>> sourceDataElementsByPeriodType = source
- .getDataElementsInDataSetsByPeriodType();
- for ( PeriodTypeExtended periodTypeX : context.PeriodTypeExtendedMap.values() )
- {
- Collection<DataElement> sourceDataElements = sourceDataElementsByPeriodType
- .get( periodTypeX.periodType );
- if ( sourceDataElements != null )
- {
- periodTypeX.sourceDataElements.put( source, sourceDataElements );
- }
- else
- {
- periodTypeX.sourceDataElements.put( source, new HashSet<DataElement>() );
- }
- }
- }
-
- return context;
- }
-
- /**
- * Gets the PeriodTypeExtended from the context object. If not found,
- * creates a new PeriodTypeExtended object, puts it into the context object,
- * and returns it.
- *
- * @param context validation run context
- * @param periodType period type to search for
- * @return period type extended from the context object
- */
- PeriodTypeExtended getOrCreatePeriodTypeExtended( ValidationRunContext context, PeriodType periodType )
- {
- PeriodTypeExtended periodTypeX = context.PeriodTypeExtendedMap.get( periodType );
- if ( periodTypeX == null )
- {
- periodTypeX = new PeriodTypeExtended();
- periodTypeX.periodType = periodType;
- periodTypeX.periods = new HashSet<Period>();
- periodTypeX.rules = new HashSet<ValidationRule>();
- periodTypeX.dataElements = new HashSet<DataElement>();
- periodTypeX.allowedPeriodTypes = new HashSet<PeriodType>();
- periodTypeX.sourceDataElements = new HashMap<OrganisationUnit, Collection<DataElement>>();
- context.PeriodTypeExtendedMap.put( periodType, periodTypeX );
- }
- return periodTypeX;
+ 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();
}
/**
@@ -630,509 +356,20 @@
// are present in the database.
for ( PeriodType periodType : rulePeriodTypes )
{
- // This is a bit awkward. The current periodType object is of type
- // periodType, but not of type CalendarPeriodType. In other words,
- // ( periodType instanceof CalendarPeriodType ) returns false!
- // In order to do periodType calendar math, we want a real
- // "CalendarPeriodType" instance.
- // TODO just cast to calendar period type
- CalendarPeriodType calendarPeriodType = getCalendarPeriodType( periodType );
- if ( calendarPeriodType != null )
- {
- 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.
- }
+ 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;
}
/**
- * Evaluates validation rules for a single organisation unit. This is the
- * central method in validation rule evaluation.
- *
- * @param sourceX extended object of the organisation unit in which to run
- * the validation rules
- * @param context the validation run context
- */
- private void validateSource( OrganisationUnitExtended sourceX, ValidationRunContext context )
- {
- if ( context.validationResults.size() < (ValidationRunType.INTERACTIVE == context.runType ? MAX_INTERACTIVE_VIOLATIONS
- : MAX_ALERT_VIOLATIONS) )
- {
- for ( PeriodTypeExtended periodTypeX : context.PeriodTypeExtendedMap.values() )
- {
- Collection<DataElement> sourceDataElements = periodTypeX.sourceDataElements.get( sourceX.source );
- Set<ValidationRule> rules = getRulesBySourceAndPeriodType( sourceX, periodTypeX, context,
- sourceDataElements );
-
- if ( !rules.isEmpty() )
- {
- Set<DataElement> recursiveCurrentDataElements = getRecursiveCurrentDataElements( rules );
- for ( Period period : periodTypeX.periods )
- {
- Map<DataElementOperand, Date> lastUpdatedMap = new HashMap<DataElementOperand, Date>();
- Set<DataElementOperand> incompleteValues = new HashSet<DataElementOperand>();
- Map<DataElementOperand, Double> currentValueMap = getDataValueMapRecursive( periodTypeX,
- periodTypeX.dataElements, sourceDataElements, recursiveCurrentDataElements,
- periodTypeX.allowedPeriodTypes, period, sourceX.source, lastUpdatedMap, incompleteValues );
- log.trace( "currentValueMap[" + currentValueMap.size() + "]" );
-
- for ( ValidationRule rule : rules )
- {
- if ( evaluateCheck( lastUpdatedMap, rule, context ) )
- {
- Double leftSide = expressionService.getExpressionValue( rule.getLeftSide(),
- currentValueMap, context.constantMap, null, incompleteValues );
-
- if ( leftSide != null || Operator.compulsory_pair.equals( rule.getOperator() ) )
- {
- Double rightSide = getRightSideValue( sourceX.source, periodTypeX, period, rule,
- currentValueMap, sourceDataElements, context );
-
- if ( rightSide != null || Operator.compulsory_pair.equals( rule.getOperator() ) )
- {
- boolean violation = false;
-
- if ( Operator.compulsory_pair.equals( rule.getOperator() ) )
- {
- violation = (leftSide != null && rightSide == null)
- || (leftSide == null && rightSide != null);
- }
- else if ( leftSide != null && rightSide != null )
- {
- violation = !expressionIsTrue( leftSide, rule.getOperator(), rightSide );
- }
-
- if ( violation )
- {
- context.validationResults.add( new ValidationResult( period,
- sourceX.source, rule, getRounded( zeroIfNull( leftSide ), DECIMALS ),
- getRounded( zeroIfNull( rightSide ), DECIMALS ) ) );
- }
-
- log.trace( "-->Evaluated " + rule.getName() + ": "
- + (violation ? "violation" : "OK") + " " + leftSide.toString() + " "
- + rule.getOperator() + " " + rightSide.toString() + " ("
- + context.validationResults.size() + " results)" );
- }
- }
- }
- }
- }
- }
- }
- }
- }
-
- /**
- * Checks to see if the evaluation should go further for this
- * evaluationRule, after the "current" data to evaluate has been fetched.
- * For INTERACTIVE runs, we always go further (always return true.) For
- * ALERT runs, we go further only if something has changed since the last
- * successful alert run -- either the rule definition or one of the
- * "current" data element / option values.
- *
- * @param lastUpdatedMap when each data value was last updated
- * @param rule the rule that may be evaluated
- * @param context the evaluation run context
- * @return true if the rule should be evaluated with this data, false if not
- */
- private boolean evaluateCheck( Map<DataElementOperand, Date> lastUpdatedMap, ValidationRule rule,
- ValidationRunContext context )
- {
- boolean evaluate = true; // Assume true for now.
-
- if ( ValidationRunType.ALERT == context.runType )
- {
- if ( context.lastAlertRun != null ) // True if no previous alert run
- {
- if ( rule.getLastUpdated().before( context.lastAlertRun ) )
- {
- // Get the "current" DataElementOperands from this rule:
- // Left+Right sides for VALIDATION, Left side only for
- // MONITORING
- Collection<DataElementOperand> deos = expressionService.getOperandsInExpression( rule.getLeftSide()
- .getExpression() );
- if ( ValidationRule.RULE_TYPE_VALIDATION == rule.getRuleType() )
- {
- // Make a copy so we can add to it.
- deos = new HashSet<DataElementOperand>( deos );
-
- deos.addAll( expressionService.getOperandsInExpression( rule.getRightSide().getExpression() ) );
- }
-
- // Return true if any data is more recent than the last
- // ALERT run, otherwise return false.
- evaluate = false;
- for ( DataElementOperand deo : deos )
- {
- Date lastUpdated = lastUpdatedMap.get( deo );
- if ( lastUpdated != null && lastUpdated.after( context.lastAlertRun ) )
- {
- evaluate = true; // True if new/updated data.
- break;
- }
- }
- }
- }
- }
- return evaluate;
- }
-
- /**
- * Gets the rules that should be evaluated for a given organisation unit and
- * period type.
- *
- * @param sourceX the organisation unit extended information
- * @param periodTypeX the period type extended information
- * @param context the alert run context
- * @param sourceDataElements all data elements collected for this
- * organisation unit
- * @return
- */
- private Set<ValidationRule> getRulesBySourceAndPeriodType( OrganisationUnitExtended sourceX,
- PeriodTypeExtended periodTypeX, ValidationRunContext context, Collection<DataElement> sourceDataElements )
- {
- Set<ValidationRule> periodTypeRules = new HashSet<ValidationRule>();
-
- for ( ValidationRule rule : periodTypeX.rules )
- {
- if ( (ValidationRule.RULE_TYPE_VALIDATION.equals( rule.getRuleType() )) )
- {
- // For validation-type rules, include only rules where the
- // organisation collects all the data elements in the rule.
- // But if this is some funny kind of rule with no elements (like
- // for testing), include it also.
- Collection<DataElement> elements = rule.getCurrentDataElements();
- if ( elements == null || elements.size() == 0 || sourceDataElements.containsAll( elements ) )
- {
- periodTypeRules.add( rule );
- }
- }
- else
- {
- // For monitoring-type rules, include only rules for this
- // organisation's unit level.
- // The organisation may not be configured for the data elements
- // because they could be aggregated from a lower level.
- if ( rule.getOrganisationUnitLevel() == sourceX.level )
- {
- periodTypeRules.add( rule );
- }
- }
- }
-
- return periodTypeRules;
- }
-
- /**
- * Gets the data elements for which values should be fetched recursively if
- * they are not collected for an organisation unit.
- *
- * @param rules ValidationRules to be evaluated
- * @return the data elements to fetch recursively
- */
- private Set<DataElement> getRecursiveCurrentDataElements( Set<ValidationRule> rules )
- {
- Set<DataElement> recursiveCurrentDataElements = new HashSet<DataElement>();
-
- for ( ValidationRule rule : rules )
- {
- if ( ValidationRule.RULE_TYPE_MONITORING.equals( rule.getRuleType() )
- && rule.getCurrentDataElements() != null )
- {
- recursiveCurrentDataElements.addAll( rule.getCurrentDataElements() );
- }
- }
-
- return recursiveCurrentDataElements;
- }
-
- /**
- * Returns the right-side evaluated value of the validation rule.
- *
- * @param source organisation unit being evaluated
- * @param periodTypeX period type being evaluated
- * @param period period being evaluated
- * @param rule ValidationRule being evaluated
- * @param currentValueMap current values already fetched
- * @param sourceDataElements the data elements collected by the organisation
- * unit
- * @param context the validation run context
- * @return the right-side value
- */
- private Double getRightSideValue( OrganisationUnit source, PeriodTypeExtended periodTypeX, Period period,
- ValidationRule rule, Map<DataElementOperand, Double> currentValueMap,
- Collection<DataElement> sourceDataElements, ValidationRunContext context )
- {
- Double rightSideValue = null;
-
- // If ruleType is VALIDATION, the right side is evaluated using the same
- // (current) data values.
- // If ruleType is MONITORING but there are no data elements in the right
- // side, then it doesn't matter
- // what data values we use, so just supply the current data values in
- // order to evaluate the (constant) expression.
-
- if ( ValidationRule.RULE_TYPE_VALIDATION.equals( rule.getRuleType() )
- || rule.getRightSide().getDataElementsInExpression().isEmpty() )
- {
- rightSideValue = expressionService.getExpressionValue( rule.getRightSide(), currentValueMap,
- context.constantMap, null );
- }
- else
- // ruleType equals MONITORING, and there are some data elements in the
- // right side expression
- {
- CalendarPeriodType calendarPeriodType = getCalendarPeriodType( period.getPeriodType() );
-
- if ( calendarPeriodType != null )
- {
- Collection<PeriodType> rightSidePeriodTypes = getAllowedPeriodTypesForDataElements(
- rule.getPastDataElements(), rule.getPeriodType() );
- List<Double> sampleValues = new ArrayList<Double>();
- Calendar yearlyCalendar = PeriodType.createCalendarInstance( period.getStartDate() );
- int annualSampleCount = rule.getAnnualSampleCount() == null ? 0 : rule.getAnnualSampleCount();
- int sequentialSampleCount = rule.getSequentialSampleCount() == null ? 0 : rule
- .getSequentialSampleCount();
-
- for ( int annualCount = 0; annualCount <= annualSampleCount; annualCount++ )
- {
-
- // Defensive copy because createPeriod mutates Calendar.
- Calendar calCopy = PeriodType.createCalendarInstance( yearlyCalendar.getTime() );
-
- // To track the period at the same time in preceding years.
- Period yearlyPeriod = calendarPeriodType.createPeriod( calCopy );
-
- // For past years, fetch the period at the same time of year
- // as this period,
- // and any periods after this period within the
- // sequentialPeriod limit.
- // For the year of the stating period, we will only fetch
- // previous sequential periods.
-
- if ( annualCount > 0 )
- {
- // Fetch the period at the same time of year as the
- // starting period.
- evaluateRightSidePeriod( periodTypeX, sampleValues, source, rightSidePeriodTypes, yearlyPeriod,
- rule, sourceDataElements, context );
-
- // Fetch the sequential periods after this prior-year
- // period.
- Period sequentialPeriod = new Period( yearlyPeriod );
- for ( int sequentialCount = 0; sequentialCount < sequentialSampleCount; sequentialCount++ )
- {
- sequentialPeriod = calendarPeriodType.getNextPeriod( sequentialPeriod );
- evaluateRightSidePeriod( periodTypeX, sampleValues, source, rightSidePeriodTypes,
- sequentialPeriod, rule, sourceDataElements, context );
- }
- }
-
- // Fetch the seqential periods before this period (both this
- // year and past years):
- Period sequentialPeriod = new Period( yearlyPeriod );
- for ( int sequentialCount = 0; sequentialCount < sequentialSampleCount; sequentialCount++ )
- {
- sequentialPeriod = calendarPeriodType.getPreviousPeriod( sequentialPeriod );
- evaluateRightSidePeriod( periodTypeX, sampleValues, source, rightSidePeriodTypes,
- sequentialPeriod, rule, sourceDataElements, context );
- }
-
- // Move to the previous year:
- yearlyCalendar.set( Calendar.YEAR, yearlyCalendar.get( Calendar.YEAR ) - 1 );
- }
-
- rightSideValue = rightSideAverage( rule, sampleValues, annualSampleCount, sequentialSampleCount );
- }
- }
- return rightSideValue;
- }
-
- /**
- * Evaluates the right side of a monitoring-type validation rule for a given
- * organisation unit and period, and adds the value to a list of sample
- * values.
- *
- * Note that for a monitoring-type rule, evaluating the right side
- * expression can result in sampling multiple periods and/or child
- * organisation units.
- *
- * @param periodTypeX the period type extended information
- * @param sampleValues the list of sample values to add to
- * @param source the organisation unit
- * @param allowedPeriodTypes the period types in which the data may exist
- * @param period the main period for the validation rule evaluation
- * @param rule the monitoring-type rule being evaluated
- * @param sourceDataElements the data elements configured for this
- * organisation unit
- * @param context the evaluation run context
- */
- private void evaluateRightSidePeriod( PeriodTypeExtended periodTypeX, List<Double> sampleValues,
- OrganisationUnit source, Collection<PeriodType> allowedPeriodTypes, Period period, ValidationRule rule,
- Collection<DataElement> sourceDataElements, ValidationRunContext context )
- {
- Period periodInstance = periodService.getPeriod( period.getStartDate(), period.getEndDate(),
- period.getPeriodType() );
-
- if ( periodInstance != null )
- {
- Set<DataElement> dataElements = rule.getRightSide().getDataElementsInExpression();
- Set<DataElementOperand> incompleteValues = new HashSet<DataElementOperand>();
- Map<DataElementOperand, Double> dataValueMap = getDataValueMapRecursive( periodTypeX, dataElements,
- sourceDataElements, dataElements, allowedPeriodTypes, period, source, null, incompleteValues );
- Double value = expressionService.getExpressionValue( rule.getRightSide(), dataValueMap,
- context.constantMap, null, incompleteValues );
-
- if ( value != null )
- {
- sampleValues.add( value );
- }
- }
- }
-
- /**
- * Finds the average right-side sample value. This is used as the right-side
- * expression value to evaluate a monitoring-type rule.
- *
- * @param rule monitoring-type rule being evaluated
- * @param sampleValues sample values actually collected
- * @param annualSampleCount number of annual samples tried for
- * @param sequentialSampleCount number of sequential samples tried for
- * @return
- */
- Double rightSideAverage( ValidationRule rule, List<Double> sampleValues, int annualSampleCount,
- int sequentialSampleCount )
- {
- // Find the expected sample count for the last period of its type in the
- // database: sequentialSampleCount for the immediately preceding periods
- // in this year and for every past year: one sample for the same period
- // in that year, plus sequentialSampleCounts before and after.
- Double average = null;
- if ( !sampleValues.isEmpty() )
- {
- int expectedSampleCount = sequentialSampleCount + annualSampleCount * (1 + 2 * sequentialSampleCount);
- int highOutliers = rule.getHighOutliers() == null ? 0 : rule.getHighOutliers();
- int lowOutliers = rule.getLowOutliers() == null ? 0 : rule.getLowOutliers();
-
- // If we had fewer than the expected number of samples, then scale
- // back
- if ( highOutliers + lowOutliers > sampleValues.size() )
- {
- highOutliers = (highOutliers * sampleValues.size()) / expectedSampleCount;
- lowOutliers = (lowOutliers * sampleValues.size()) / expectedSampleCount;
- }
-
- // If we (still) have any high and/or low outliers to remove, then
- // sort the sample values and remove the high and/or low outliers.
- if ( highOutliers + lowOutliers > 0 )
- {
- Collections.sort( sampleValues );
- log.trace( "Removing " + highOutliers + " high and " + lowOutliers + " low outliers from "
- + Arrays.toString( sampleValues.toArray() ) );
- sampleValues = sampleValues.subList( lowOutliers, sampleValues.size() - highOutliers );
- log.trace( "Result: " + Arrays.toString( sampleValues.toArray() ) );
- }
- Double sum = 0.0;
- for ( Double sample : sampleValues )
- {
- sum += sample;
- }
- average = sum / sampleValues.size();
- }
- return average;
- }
-
- /**
- * Gets data values for a given organisation unit and period, recursing if
- * necessary to sum the values from child organisation units.
- *
- * @param periodTypeX period type which we are evaluating
- * @param ruleDataElements data elements configured for the rule
- * @param sourceDataElements data elements configured for the organisation
- * unit
- * @param recursiveDataElements data elements for which we will recurse if
- * necessary
- * @param allowedPeriodTypes all the periods in which we might find the data
- * values
- * @param period period in which we are looking for values
- * @param source organisation unit for which we are looking for values
- * @param lastUpdatedMap map showing when each data values was last updated
- * @param incompleteValues ongoing list showing which values were found but
- * not from all children
- * @return the map of values found
- */
- private Map<DataElementOperand, Double> getDataValueMapRecursive( PeriodTypeExtended periodTypeX,
- Collection<DataElement> ruleDataElements, Collection<DataElement> sourceDataElements,
- Set<DataElement> recursiveDataElements, Collection<PeriodType> allowedPeriodTypes, Period period,
- OrganisationUnit source, Map<DataElementOperand, Date> lastUpdatedMap, Set<DataElementOperand> incompleteValues )
- {
- Set<DataElement> dataElementsToGet = new HashSet<DataElement>( ruleDataElements );
- dataElementsToGet.retainAll( sourceDataElements );
- log.trace( "getDataValueMapRecursive: source:" + source.getName() + " elementsToGet["
- + dataElementsToGet.size() + "] allowedPeriodTypes[" + allowedPeriodTypes.size() + "]" );
-
- Map<DataElementOperand, Double> dataValueMap;
-
- if ( dataElementsToGet.isEmpty() )
- {
- // We still might get something recursively
- dataValueMap = new HashMap<DataElementOperand, Double>();
- }
- else
- {
- dataValueMap = dataValueService.getDataValueMap( dataElementsToGet, period.getStartDate(), source,
- allowedPeriodTypes, lastUpdatedMap );
- }
-
- // See if there are any data elements we need to get recursively:
- Set<DataElement> recursiveDataElementsNeeded = new HashSet<DataElement>( recursiveDataElements );
- recursiveDataElementsNeeded.removeAll( dataElementsToGet );
- if ( !recursiveDataElementsNeeded.isEmpty() )
- {
- int childCount = 0;
- Map<DataElementOperand, Integer> childValueCounts = new HashMap<DataElementOperand, Integer>();
-
- for ( OrganisationUnit child : source.getChildren() )
- {
- Collection<DataElement> childDataElements = periodTypeX.sourceDataElements.get( child );
- Map<DataElementOperand, Double> childMap = getDataValueMapRecursive( periodTypeX,
- recursiveDataElementsNeeded, childDataElements, recursiveDataElementsNeeded, allowedPeriodTypes,
- period, child, lastUpdatedMap, incompleteValues );
-
- for ( DataElementOperand deo : childMap.keySet() )
- {
- Double baseValue = dataValueMap.get( deo );
- dataValueMap.put( deo, baseValue == null ? childMap.get( deo ) : baseValue + childMap.get( deo ) );
-
- Integer childValueCount = childValueCounts.get( deo );
- childValueCounts.put( deo, childValueCount == null ? 1 : childValueCount + 1 );
- }
-
- childCount++;
- }
-
- for ( Map.Entry<DataElementOperand, Integer> entry : childValueCounts.entrySet() )
- {
- if ( childCount != entry.getValue() )
- {
- // Found this DataElementOperand value in some but not all children
- incompleteValues.add( entry.getKey() );
- }
- }
- }
-
- return dataValueMap;
- }
-
- /**
* Returns all validation-type rules which have specified data elements
* assigned to them.
*
@@ -1185,10 +422,10 @@
if ( rule.getRuleType().equals( ValidationRule.RULE_TYPE_VALIDATION ) )
{
validationRuleOperands.clear();
- validationRuleOperands.addAll( expressionService.getOperandsInExpression( rule.getLeftSide()
- .getExpression() ) );
- validationRuleOperands.addAll( expressionService.getOperandsInExpression( rule.getRightSide()
- .getExpression() ) );
+ validationRuleOperands.addAll( expressionService.getOperandsInExpression(
+ rule.getLeftSide().getExpression() ) );
+ validationRuleOperands.addAll( expressionService.getOperandsInExpression(
+ rule.getRightSide().getExpression() ) );
if ( operands.containsAll( validationRuleOperands ) )
{
@@ -1201,64 +438,6 @@
}
/**
- * Finds all period types that may contain given data elements, whose period
- * type interval is at least as long as the given period type.
- *
- * @param dataElements data elements to look for
- * @param periodType the minimum-length period type
- * @return all period types that are allowed for these data elements
- */
- private Collection<PeriodType> getAllowedPeriodTypesForDataElements( Collection<DataElement> dataElements,
- PeriodType periodType )
- {
- Collection<PeriodType> allowedPeriodTypes = new HashSet<PeriodType>();
- for ( DataElement dataElement : dataElements )
- {
- for ( DataSet dataSet : dataElement.getDataSets() )
- {
- if ( dataSet.getPeriodType().getFrequencyOrder() >= periodType.getFrequencyOrder() )
- {
- allowedPeriodTypes.add( dataSet.getPeriodType() );
- }
- }
- }
- return allowedPeriodTypes;
- }
-
- /**
- * Returns an instance of type CalendarPeriodType that matches the specified
- * periodType. This can be needed in order to access the calendar-computing
- * methods that are available in a CalendarPeriodType but not an ordinary
- * PeriodType.
- *
- * Note: Perhaps this should be moved to PeriodService. Or perhaps some
- * refactoring can be done in the relationship between PeriodType and
- * CalendarPeriodType.
- *
- * @param periodType the period type of interest
- * @return the corresponding CalendarPeriodType
- */
- private CalendarPeriodType getCalendarPeriodType( PeriodType periodType )
- {
- for ( PeriodType p : PeriodType.PERIOD_TYPES )
- {
- if ( periodType.getName().equals( p.getName() ) )
- {
- if ( p instanceof CalendarPeriodType )
- {
- return (CalendarPeriodType) p;
- }
- else
- {
- log.error( "DefaultValidationRuleService.getCalendarPeriodType() - PeriodType.PERIOD_TYPES ["
- + p.getName() + "] is not a CalendarPeriodType!" );
- }
- }
- }
- return null;
- }
-
- /**
* At the end of an ALERT run, post messages to the users who want to see
* the results.
*
@@ -1295,17 +474,14 @@
}
// 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.
+ // 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 )
+ // 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() )
@@ -1385,33 +561,47 @@
+ (importanceCountMap.get( "low" ) == null ? 0 : importanceCountMap.get( "low" ));
// Construct the text of the message.
- messageBuilder.append( "<html>\n" ).append( "<head>\n" )
+ messageBuilder
+ .append( "<html>\n" )
+ .append( "<head>\n" )
.append( "</head>\n" )
.append( "<body>\n" )
.append( subject )
.append( "\n" )
- // Repeat the subject line at the start of the message.
- .append( "<br />\n" ).append( "<table>\n" ).append( " <tr>\n" ).append( " <th>Organisation Unit</th>\n" )
- .append( " <th>Period</th>\n" ).append( " <th>Importance</th>\n" )
- .append( " <th>Left side description</th>\n" ).append( " <th>Value</th>\n" )
- .append( " <th>Operator</th>\n" ).append( " <th>Value</th>\n" )
- .append( " <th>Right side description</th>\n" ).append( " </tr>\n" );
+ .append( "<br />\n" )
+ .append( "<table>\n" )
+ .append( " <tr>\n" )
+ .append( " <th>Organisation Unit</th>\n" )
+ .append( " <th>Period</th>\n" )
+ .append( " <th>Importance</th>\n" )
+ .append( " <th>Left side description</th>\n" )
+ .append( " <th>Value</th>\n" )
+ .append( " <th>Operator</th>\n" )
+ .append( " <th>Value</th>\n" )
+ .append( " <th>Right side description</th>\n" )
+ .append( " </tr>\n" );
for ( ValidationResult result : results )
{
ValidationRule rule = result.getValidationRule();
- messageBuilder.append( " <tr>\n" ).append( " <td>" ).append( result.getSource().getName() )
- .append( "<\td>\n" ).append( " <td>" ).append( result.getPeriod().getName() ).append( "<\td>\n" )
- .append( " <td>" ).append( rule.getImportance() ).append( "<\td>\n" ).append( " <td>" )
- .append( rule.getLeftSide().getDescription() ).append( "<\td>\n" ).append( " <td>" )
- .append( result.getLeftsideValue() ).append( "<\td>\n" ).append( " <td>" )
- .append( rule.getOperator().toString() ).append( "<\td>\n" ).append( " <td>" )
- .append( result.getRightsideValue() ).append( "<\td>\n" ).append( " <td>" )
- .append( rule.getRightSide().getDescription() ).append( "<\td>\n" ).append( " </tr>\n" );
+ messageBuilder
+ .append( " <tr>\n" )
+ .append( " <td>" ).append( result.getSource().getName() ).append( "<\td>\n" )
+ .append( " <td>" ).append( result.getPeriod().getName() ).append( "<\td>\n" )
+ .append( " <td>" ).append( rule.getImportance() ).append( "<\td>\n" )
+ .append( " <td>" ).append( rule.getLeftSide().getDescription() ).append( "<\td>\n" )
+ .append( " <td>" ).append( result.getLeftsideValue() ).append( "<\td>\n" )
+ .append( " <td>" ).append( rule.getOperator().toString() ).append( "<\td>\n" )
+ .append( " <td>" ).append( result.getRightsideValue() ).append( "<\td>\n" )
+ .append( " <td>" ).append( rule.getRightSide().getDescription() ).append( "<\td>\n" )
+ .append( " </tr>\n" );
}
- messageBuilder.append( "</table>\n" ).append( "</body>\n" ).append( "</html>\n" );
+ messageBuilder
+ .append( "</table>\n" )
+ .append( "</body>\n" )
+ .append( "</html>\n" );
String messageText = messageBuilder.toString();
=== added 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 1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/OrganisationUnitExtended.java 2013-10-09 05:16:53 +0000
@@ -0,0 +1,86 @@
+package org.hisp.dhis.validation;
+
+/*
+ * 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.
+ */
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.hisp.dhis.organisationunit.OrganisationUnit;
+
+/**
+ * Holds information for each organisation unit that is needed during
+ * a validation run (either interactive or an alert run).
+ *
+ * It is important that they should be copied from Hibernate lazy
+ * collections before the multithreaded part of the run starts, otherwise
+ * the threads may not be able to access these values.
+ *
+ * @author Jim Grace
+ */
+public class OrganisationUnitExtended
+{
+ private OrganisationUnit source;
+
+ private Collection<OrganisationUnit> children;
+
+ private int level;
+
+ public OrganisationUnitExtended( OrganisationUnit source )
+ {
+ this.source = source;
+ children = new HashSet<OrganisationUnit>( source.getChildren() );
+ level = source.getOrganisationUnitLevel();
+ }
+
+ 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();
+ }
+
+ // -------------------------------------------------------------------------
+ // Set and get methods
+ // -------------------------------------------------------------------------
+
+ public OrganisationUnit getSource() {
+ return source;
+ }
+
+ public Collection<OrganisationUnit> getChildren() {
+ return children;
+ }
+
+ public int getLevel() {
+ return level;
+ }
+}
+
=== added 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 1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/PeriodTypeExtended.java 2013-10-09 05:16:53 +0000
@@ -0,0 +1,140 @@
+package org.hisp.dhis.validation;
+
+/*
+ * 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.
+ */
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.hisp.dhis.dataelement.DataElement;
+import org.hisp.dhis.organisationunit.OrganisationUnit;
+import org.hisp.dhis.period.Period;
+import org.hisp.dhis.period.PeriodType;
+
+/**
+ * Holds information for each period type that is needed during
+ * a validation run (either interactive or an alert run).
+ *
+ * By computing these values once at the start of a validation run, we avoid
+ * the overhead of having to compute them during the processing of every
+ * organisation unit. For some of these properties this is also important
+ * because they should be copied from Hibernate lazy collections before the
+ * multithreaded part of the run starts, otherwise the threads may not be
+ * able to access these values.
+ *
+ * @author Jim Grace
+ */
+public class PeriodTypeExtended {
+
+ private PeriodType periodType;
+
+ private Collection<Period> periods;
+
+ private Collection<ValidationRule> rules;
+
+ private Collection<DataElement> dataElements;
+
+ private Collection<PeriodType> allowedPeriodTypes;
+
+ private Map<OrganisationUnit, Collection<DataElement>> sourceDataElements;
+
+ public PeriodTypeExtended( PeriodType periodType )
+ {
+ this.periodType = periodType;
+ periods = new HashSet<Period>();
+ rules = new HashSet<ValidationRule>();
+ dataElements = new HashSet<DataElement>();
+ allowedPeriodTypes = new HashSet<PeriodType>();
+ sourceDataElements = new HashMap<OrganisationUnit, Collection<DataElement>>();
+ }
+
+ 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();
+ }
+
+ // -------------------------------------------------------------------------
+ // Set and get methods
+ // -------------------------------------------------------------------------
+
+ public PeriodType getPeriodType() {
+ return periodType;
+ }
+
+ public Collection<Period> getPeriods() {
+ return periods;
+ }
+
+ public void setPeriods(Collection<Period> periods) {
+ this.periods = periods;
+ }
+
+ public Collection<ValidationRule> getRules() {
+ return rules;
+ }
+
+ public void setRules(Collection<ValidationRule> rules) {
+ this.rules = rules;
+ }
+
+ public Collection<DataElement> getDataElements() {
+ return dataElements;
+ }
+
+ public void setDataElements(Collection<DataElement> dataElements) {
+ this.dataElements = dataElements;
+ }
+
+ public Collection<PeriodType> getAllowedPeriodTypes() {
+ return allowedPeriodTypes;
+ }
+
+ public void setAllowedPeriodTypes(Collection<PeriodType> allowedPeriodTypes) {
+ this.allowedPeriodTypes = allowedPeriodTypes;
+ }
+
+ public Map<OrganisationUnit, Collection<DataElement>> getSourceDataElements() {
+ return sourceDataElements;
+ }
+
+ public void setSourceDataElements(
+ Map<OrganisationUnit, Collection<DataElement>> sourceDataElements) {
+ this.sourceDataElements = sourceDataElements;
+ }
+}
=== added file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRuleExtended.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRuleExtended.java 1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRuleExtended.java 2013-10-09 05:16:53 +0000
@@ -0,0 +1,71 @@
+package org.hisp.dhis.validation;
+
+import java.util.Collection;
+
+import org.hisp.dhis.period.PeriodType;
+
+/*
+ * 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.
+ */
+
+/**
+ * Holds information for each validation rule that is needed during
+ * a validation run (either interactive or an alert run).
+ *
+ * By computing these values once at the start of a validation run, we avoid
+ * the overhead of having to compute them during the processing of every
+ * organisation unit. For some of these properties this is also important
+ * because they should be copied from Hibernate lazy collections before the
+ * multithreaded part of the run starts, otherwise the threads may not be
+ * able to access these values.
+ *
+ * @author Jim Grace
+ */
+public class ValidationRuleExtended {
+
+ private ValidationRule rule;
+
+ private Collection<PeriodType> allowedPastPeriodTypes;
+
+ public ValidationRuleExtended( ValidationRule rule, Collection<PeriodType> allowedPastPeriodTypes )
+ {
+ this.rule = rule;
+ this.allowedPastPeriodTypes = allowedPastPeriodTypes;
+ }
+
+ // -------------------------------------------------------------------------
+ // Set and get methods
+ // -------------------------------------------------------------------------
+
+ public ValidationRule getRule() {
+ return rule;
+ }
+
+ public Collection<PeriodType> getAllowedPastPeriodTypes() {
+ return allowedPastPeriodTypes;
+ }
+}
=== added 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 1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRunContext.java 2013-10-09 05:16:53 +0000
@@ -0,0 +1,300 @@
+package org.hisp.dhis.validation;
+
+/*
+ * 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.
+ */
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.hisp.dhis.dataelement.DataElement;
+import org.hisp.dhis.dataset.DataSet;
+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.period.PeriodType;
+
+/**
+ * Holds common values that are used during a validation run (either interactive
+ * or an alert run.) These values don't change during the multi-threaded tasks
+ * (except that results entries are added in a threadsafe way.)
+ *
+ * Some of the values are precalculated collections, to save CPU time during
+ * the run. All of these values are stored in this single "context" object
+ * to allow a single object reference for each of the scheduled tasks. (This
+ * also reduces the amount of memory needed to queue all the multi-threaded
+ * tasks.)
+ *
+ * For some of these properties this is also important because they should
+ * be copied from Hibernate lazy collections before the multithreaded part
+ * of the run starts, otherwise the threads may not be able to access these
+ * values.
+ *
+ * @author Jim Grace
+ */
+public class ValidationRunContext
+{
+ private Map<PeriodType, PeriodTypeExtended> periodTypeExtendedMap;
+
+ private ValidationRunType runType;
+
+ private Date lastAlertRun;
+
+ private Map<String, Double> constantMap;
+
+ private Map<ValidationRule, ValidationRuleExtended> ruleXMap;
+
+ private Collection<OrganisationUnitExtended> sourceXs;
+
+ private Collection<ValidationResult> validationResults;
+
+ private ExpressionService expressionService;
+
+ private PeriodService periodService;
+
+ private DataValueService dataValueService;
+
+ private ValidationRunContext()
+ {
+ }
+
+ public String toString()
+ {
+ return new ToStringBuilder( this, ToStringStyle.SHORT_PREFIX_STYLE )
+ .append( "\n PeriodTypeExtendedMap", (Arrays.toString( periodTypeExtendedMap.entrySet().toArray() )) )
+ .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();
+ }
+
+ /**
+ * Creates and fills a new context object for a validation run.
+ *
+ * @param sources organisation units for validation
+ * @param periods periods for validation
+ * @param rules validation rules for validation
+ * @param runType whether this is an INTERACTIVE or ALERT run
+ * @param lastAlertRun (for ALERT runs) date of previous alert run
+ * @return context object for this run
+ */
+ public static ValidationRunContext getNewValidationRunContext( Collection<OrganisationUnit> sources, Collection<Period> periods,
+ Collection<ValidationRule> rules, Map<String, Double> constantMap, ValidationRunType runType, Date lastAlertRun,
+ ExpressionService expressionService, PeriodService periodService, DataValueService dataValueService )
+ {
+ ValidationRunContext context = new ValidationRunContext();
+ context.runType = runType;
+ context.lastAlertRun = lastAlertRun;
+ context.validationResults = new ConcurrentLinkedQueue<ValidationResult>(); // thread-safe
+ context.periodTypeExtendedMap = new HashMap<PeriodType, PeriodTypeExtended>();
+ context.ruleXMap = new HashMap<ValidationRule, ValidationRuleExtended>();
+ context.sourceXs = new HashSet<OrganisationUnitExtended>();
+ context.constantMap = constantMap;
+ context.expressionService = expressionService;
+ context.periodService = periodService;
+ context.dataValueService = dataValueService;
+ context.initialize( sources, periods, rules );
+ return context;
+ }
+
+ /**
+ * Initializes context values based on sources, periods and rules
+ *
+ * @param sources
+ * @param periods
+ * @param rules
+ */
+ private void initialize( Collection<OrganisationUnit> sources, Collection<Period> periods, Collection<ValidationRule> rules )
+ {
+ // Group the periods by period type.
+ for ( Period period : periods )
+ {
+ PeriodTypeExtended periodTypeX = getOrCreatePeriodTypeExtended( period.getPeriodType() );
+ periodTypeX.getPeriods().add( period );
+ }
+
+ for ( ValidationRule rule : rules )
+ {
+ // Find the period type extended for this rule
+ PeriodTypeExtended periodTypeX = getOrCreatePeriodTypeExtended( rule.getPeriodType() );
+ periodTypeX.getRules().add( rule ); // Add this rule to the period type ext.
+
+ if ( rule.getCurrentDataElements() != null )
+ {
+ // Add this rule's data elements to the period extended.
+ periodTypeX.getDataElements().addAll( rule.getCurrentDataElements() );
+ }
+ // Add the allowed period types for rule's current data elements:
+ periodTypeX.getAllowedPeriodTypes().addAll(
+ getAllowedPeriodTypesForDataElements( rule.getCurrentDataElements(), rule.getPeriodType() ) );
+
+ // Add the ValidationRuleExtended
+ Collection<PeriodType> allowedPastPeriodTypes =
+ getAllowedPeriodTypesForDataElements( rule.getPastDataElements(), rule.getPeriodType() );
+ ValidationRuleExtended ruleX = new ValidationRuleExtended( rule, allowedPastPeriodTypes );
+ ruleXMap.put( rule, ruleX );
+ }
+
+ // We only need to keep period types that are selected and also used by
+ // rules that are selected.
+ // 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() )
+ {
+ periodTypeExtendedMap.remove( periodTypeX.getPeriodType() );
+ }
+ }
+
+ for ( OrganisationUnit source : sources )
+ {
+ OrganisationUnitExtended sourceX = new OrganisationUnitExtended( source );
+ sourceXs.add( sourceX );
+
+ Map<PeriodType, Set<DataElement>> sourceDataElementsByPeriodType = source
+ .getDataElementsInDataSetsByPeriodType();
+ for ( PeriodTypeExtended periodTypeX : periodTypeExtendedMap.values() )
+ {
+ Collection<DataElement> sourceDataElements = sourceDataElementsByPeriodType
+ .get( periodTypeX.getPeriodType() );
+ if ( sourceDataElements != null )
+ {
+ periodTypeX.getSourceDataElements().put( source, sourceDataElements );
+ }
+ else
+ {
+ periodTypeX.getSourceDataElements().put( source, new HashSet<DataElement>() );
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the PeriodTypeExtended from the context object. If not found,
+ * creates a new PeriodTypeExtended object, puts it into the context object,
+ * and returns it.
+ *
+ * @param context validation run context
+ * @param periodType period type to search for
+ * @return period type extended from the context object
+ */
+ private PeriodTypeExtended getOrCreatePeriodTypeExtended( PeriodType periodType )
+ {
+ PeriodTypeExtended periodTypeX = periodTypeExtendedMap.get( periodType );
+ if ( periodTypeX == null )
+ {
+ periodTypeX = new PeriodTypeExtended( periodType );
+ periodTypeExtendedMap.put( periodType, periodTypeX );
+ }
+ return periodTypeX;
+ }
+
+ /**
+ * Finds all period types that may contain given data elements, whose period
+ * type interval is at least as long as the given period type.
+ *
+ * @param dataElements data elements to look for
+ * @param periodType the minimum-length period type
+ * @return all period types that are allowed for these data elements
+ */
+ private static Collection<PeriodType> getAllowedPeriodTypesForDataElements( Collection<DataElement> dataElements,
+ PeriodType periodType )
+ {
+ Collection<PeriodType> allowedPeriodTypes = new HashSet<PeriodType>();
+ if ( dataElements != null )
+ {
+ for ( DataElement dataElement : dataElements )
+ {
+ for ( DataSet dataSet : dataElement.getDataSets() )
+ {
+ if ( dataSet.getPeriodType().getFrequencyOrder() >= periodType.getFrequencyOrder() )
+ {
+ allowedPeriodTypes.add( dataSet.getPeriodType() );
+ }
+ }
+ }
+ }
+ return allowedPeriodTypes;
+ }
+
+ // -------------------------------------------------------------------------
+ // Set and get methods
+ // -------------------------------------------------------------------------
+
+ public Map<PeriodType, PeriodTypeExtended> getPeriodTypeExtendedMap() {
+ return periodTypeExtendedMap;
+ }
+
+ public ValidationRunType getRunType() {
+ return runType;
+ }
+
+ public Date getLastAlertRun() {
+ return lastAlertRun;
+ }
+
+ public Map<String, Double> getConstantMap() {
+ return constantMap;
+ }
+
+ public Map<ValidationRule, ValidationRuleExtended> getRuleXMap() {
+ return ruleXMap;
+ }
+
+ public Collection<OrganisationUnitExtended> getSourceXs() {
+ return sourceXs;
+ }
+
+ public Collection<ValidationResult> getValidationResults() {
+ return validationResults;
+ }
+
+ public ExpressionService getExpressionService() {
+ return expressionService;
+ }
+
+ public PeriodService getPeriodService() {
+ return periodService;
+ }
+
+ public DataValueService getDataValueService() {
+ return dataValueService;
+ }
+}
+
=== added file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRunType.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRunType.java 1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRunType.java 2013-10-09 05:16:53 +0000
@@ -0,0 +1,37 @@
+package org.hisp.dhis.validation;
+
+/*
+ * 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.
+ */
+
+/**
+ * Defines the types of alert run.
+ */
+public enum ValidationRunType
+{
+ INTERACTIVE, ALERT
+}
=== added 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/ValidationWorkerThread.java 1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationWorkerThread.java 2013-10-09 05:16:53 +0000
@@ -0,0 +1,567 @@
+package org.hisp.dhis.validation;
+
+/*
+ * 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.
+ */
+
+import static org.hisp.dhis.system.util.MathUtils.expressionIsTrue;
+import static org.hisp.dhis.system.util.MathUtils.getRounded;
+import static org.hisp.dhis.system.util.MathUtils.zeroIfNull;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+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 org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hisp.dhis.dataelement.DataElement;
+import org.hisp.dhis.dataelement.DataElementOperand;
+import org.hisp.dhis.expression.Operator;
+import org.hisp.dhis.organisationunit.OrganisationUnit;
+import org.hisp.dhis.period.CalendarPeriodType;
+import org.hisp.dhis.period.Period;
+import org.hisp.dhis.period.PeriodType;
+
+/**
+ * Runs a validation task on a thread within a multi-threaded validation run.
+ *
+ * Each thread looks for validation results in a different organisation unit.
+ *
+ * @author Jim Grace
+ */
+public class ValidationWorkerThread
+ implements Runnable
+{
+ private static final Log log = LogFactory.getLog( ValidationWorkerThread.class );
+
+ /**
+ * Defines how many decimal places for rounding the left and right side
+ * evaluation values in the report of results.
+ */
+ private static final int DECIMALS = 1;
+
+ private OrganisationUnitExtended sourceX;
+
+ private ValidationRunContext context;
+
+ public ValidationWorkerThread( OrganisationUnitExtended sourceX, ValidationRunContext context )
+ {
+ this.sourceX = sourceX;
+ this.context = context;
+ }
+
+ @Override
+ public void run()
+ {
+ validateSource( sourceX, context );
+ }
+
+ /**
+ * Evaluates validation rules for a single organisation unit. This is the
+ * central method in validation rule evaluation.
+ *
+ * @param sourceX extended object of the organisation unit in which to run
+ * the validation rules
+ * @param context the validation run context
+ */
+ private void validateSource( OrganisationUnitExtended sourceX, ValidationRunContext context )
+ {
+ if ( context.getValidationResults().size() < ( ValidationRunType.INTERACTIVE == context.getRunType() ?
+ ValidationRuleService.MAX_INTERACTIVE_VIOLATIONS : ValidationRuleService.MAX_ALERT_VIOLATIONS) )
+ {
+ for ( PeriodTypeExtended periodTypeX : context.getPeriodTypeExtendedMap().values() )
+ {
+ Collection<DataElement> sourceDataElements = periodTypeX.getSourceDataElements().get( sourceX.getSource() );
+ Set<ValidationRule> rules = getRulesBySourceAndPeriodType( sourceX, periodTypeX, context,
+ sourceDataElements );
+
+ if ( !rules.isEmpty() )
+ {
+ Set<DataElement> recursiveCurrentDataElements = getRecursiveCurrentDataElements( rules );
+ for ( Period period : periodTypeX.getPeriods() )
+ {
+ Map<DataElementOperand, Date> lastUpdatedMap = new HashMap<DataElementOperand, Date>();
+ Set<DataElementOperand> incompleteValues = new HashSet<DataElementOperand>();
+ Map<DataElementOperand, Double> currentValueMap = getDataValueMapRecursive( periodTypeX,
+ periodTypeX.getDataElements(), sourceDataElements, recursiveCurrentDataElements,
+ periodTypeX.getAllowedPeriodTypes(), period, sourceX.getSource(), lastUpdatedMap, incompleteValues );
+ log.trace( "currentValueMap[" + currentValueMap.size() + "]" );
+
+ for ( ValidationRule rule : rules )
+ {
+ CalendarPeriodType calendarPeriodType = ( CalendarPeriodType ) rule.getPeriodType(); //TODO: remove
+
+ if ( evaluateCheck( lastUpdatedMap, rule, context ) )
+ {
+ Double leftSide = context.getExpressionService().getExpressionValue( rule.getLeftSide(),
+ currentValueMap, context.getConstantMap(), null, incompleteValues );
+
+ if ( leftSide != null || Operator.compulsory_pair.equals( rule.getOperator() ) )
+ {
+ Double rightSide = getRightSideValue( sourceX.getSource(), periodTypeX, period, rule,
+ currentValueMap, sourceDataElements, context );
+
+ if ( rightSide != null || Operator.compulsory_pair.equals( rule.getOperator() ) )
+ {
+ boolean violation = false;
+
+ if ( Operator.compulsory_pair.equals( rule.getOperator() ) )
+ {
+ violation = (leftSide != null && rightSide == null)
+ || (leftSide == null && rightSide != null);
+ }
+ else if ( leftSide != null && rightSide != null )
+ {
+ violation = !expressionIsTrue( leftSide, rule.getOperator(), rightSide );
+ }
+
+ if ( violation )
+ {
+ context.getValidationResults().add( new ValidationResult( period,
+ sourceX.getSource(), rule, getRounded( zeroIfNull( leftSide ), DECIMALS ),
+ getRounded( zeroIfNull( rightSide ), DECIMALS ) ) );
+ }
+
+ log.trace( "-->Evaluated " + rule.getName() + ": "
+ + (violation ? "violation" : "OK") + " " + leftSide.toString() + " "
+ + rule.getOperator() + " " + rightSide.toString() + " ("
+ + context.getValidationResults().size() + " results)" );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the rules that should be evaluated for a given organisation unit and
+ * period type.
+ *
+ * @param sourceX the organisation unit extended information
+ * @param periodTypeX the period type extended information
+ * @param context the alert run context
+ * @param sourceDataElements all data elements collected for this
+ * organisation unit
+ * @return
+ */
+ private Set<ValidationRule> getRulesBySourceAndPeriodType( OrganisationUnitExtended sourceX,
+ PeriodTypeExtended periodTypeX, ValidationRunContext context, Collection<DataElement> sourceDataElements )
+ {
+ Set<ValidationRule> periodTypeRules = new HashSet<ValidationRule>();
+
+ for ( ValidationRule rule : periodTypeX.getRules() )
+ {
+ if ( (ValidationRule.RULE_TYPE_VALIDATION.equals( rule.getRuleType() )) )
+ {
+ // For validation-type rules, include only rules where the
+ // organisation collects all the data elements in the rule.
+ // But if this is some funny kind of rule with no elements
+ // (like for testing), include it also.
+ Collection<DataElement> elements = rule.getCurrentDataElements();
+ if ( elements == null || elements.size() == 0 || sourceDataElements.containsAll( elements ) )
+ {
+ periodTypeRules.add( rule );
+ }
+ }
+ else
+ {
+ // For monitoring-type rules, include only rules for this
+ // organisation's unit level.
+ // The organisation may not be configured for the data elements
+ // because they could be aggregated from a lower level.
+ if ( rule.getOrganisationUnitLevel() == sourceX.getLevel() )
+ {
+ periodTypeRules.add( rule );
+ }
+ }
+ }
+
+ return periodTypeRules;
+ }
+
+
+ /**
+ * Checks to see if the evaluation should go further for this
+ * evaluationRule, after the "current" data to evaluate has been fetched.
+ * For INTERACTIVE runs, we always go further (always return true.) For
+ * ALERT runs, we go further only if something has changed since the last
+ * successful alert run -- either the rule definition or one of the
+ * "current" data element / option values.
+ *
+ * @param lastUpdatedMap when each data value was last updated
+ * @param rule the rule that may be evaluated
+ * @param context the evaluation run context
+ * @return true if the rule should be evaluated with this data, false if not
+ */
+ private boolean evaluateCheck( Map<DataElementOperand, Date> lastUpdatedMap, ValidationRule rule,
+ ValidationRunContext context )
+ {
+ boolean evaluate = true; // Assume true for now.
+
+ if ( ValidationRunType.ALERT == context.getRunType() )
+ {
+ if ( context.getLastAlertRun() != null ) // True if no previous alert run
+ {
+ if ( rule.getLastUpdated().before( context.getLastAlertRun() ) )
+ {
+ // Get the "current" DataElementOperands from this rule:
+ // Left+Right sides for VALIDATION, Left side only for
+ // MONITORING
+ Collection<DataElementOperand> deos = context.getExpressionService().getOperandsInExpression(
+ rule.getLeftSide().getExpression() );
+ if ( ValidationRule.RULE_TYPE_VALIDATION == rule.getRuleType() )
+ {
+ // Make a copy so we can add to it.
+ deos = new HashSet<DataElementOperand>( deos );
+
+ deos.addAll( context.getExpressionService().getOperandsInExpression( rule.getRightSide().getExpression() ) );
+ }
+
+ // Return true if any data is more recent than the last
+ // ALERT run, otherwise return false.
+ evaluate = false;
+ for ( DataElementOperand deo : deos )
+ {
+ Date lastUpdated = lastUpdatedMap.get( deo );
+ if ( lastUpdated != null && lastUpdated.after( context.getLastAlertRun() ) )
+ {
+ evaluate = true; // True if new/updated data.
+ break;
+ }
+ }
+ }
+ }
+ }
+ return evaluate;
+ }
+
+
+ /**
+ * Gets the data elements for which values should be fetched recursively if
+ * they are not collected for an organisation unit.
+ *
+ * @param rules ValidationRules to be evaluated
+ * @return the data elements to fetch recursively
+ */
+ private Set<DataElement> getRecursiveCurrentDataElements( Set<ValidationRule> rules )
+ {
+ Set<DataElement> recursiveCurrentDataElements = new HashSet<DataElement>();
+
+ for ( ValidationRule rule : rules )
+ {
+ if ( ValidationRule.RULE_TYPE_MONITORING.equals( rule.getRuleType() )
+ && rule.getCurrentDataElements() != null )
+ {
+ recursiveCurrentDataElements.addAll( rule.getCurrentDataElements() );
+ }
+ }
+
+ return recursiveCurrentDataElements;
+ }
+
+ /**
+ * Returns the right-side evaluated value of the validation rule.
+ *
+ * @param source organisation unit being evaluated
+ * @param periodTypeX period type being evaluated
+ * @param period period being evaluated
+ * @param rule ValidationRule being evaluated
+ * @param currentValueMap current values already fetched
+ * @param sourceDataElements the data elements collected by the organisation
+ * unit
+ * @param context the validation run context
+ * @return the right-side value
+ */
+ private Double getRightSideValue( OrganisationUnit source, PeriodTypeExtended periodTypeX, Period period,
+ ValidationRule rule, Map<DataElementOperand, Double> currentValueMap,
+ Collection<DataElement> sourceDataElements, ValidationRunContext context )
+ {
+ Double rightSideValue = null;
+
+ // If ruleType is VALIDATION, the right side is evaluated using the same
+ // (current) data values. If ruleType is MONITORING but there are no
+ // data elements in the right side, then it doesn't matter what data
+ // values we use, so just supply the current data values in order to
+ // evaluate the (constant) expression.
+
+ if ( ValidationRule.RULE_TYPE_VALIDATION.equals( rule.getRuleType() )
+ || rule.getRightSide().getDataElementsInExpression().isEmpty() )
+ {
+ rightSideValue = context.getExpressionService().getExpressionValue( rule.getRightSide(),
+ currentValueMap, context.getConstantMap(), null );
+ }
+ else
+ // ruleType equals MONITORING, and there are some data elements in the
+ // right side expression
+ {
+ CalendarPeriodType calendarPeriodType = ( CalendarPeriodType ) period.getPeriodType();
+ Collection<PeriodType> rightSidePeriodTypes = context.getRuleXMap().get( rule ).getAllowedPastPeriodTypes();
+ List<Double> sampleValues = new ArrayList<Double>();
+ Calendar yearlyCalendar = PeriodType.createCalendarInstance( period.getStartDate() );
+ int annualSampleCount = rule.getAnnualSampleCount() == null ? 0 : rule.getAnnualSampleCount();
+ int sequentialSampleCount = rule.getSequentialSampleCount() == null ? 0 : rule
+ .getSequentialSampleCount();
+
+ for ( int annualCount = 0; annualCount <= annualSampleCount; annualCount++ )
+ {
+ // Defensive copy because createPeriod mutates Calendar.
+ Calendar calCopy = PeriodType.createCalendarInstance( yearlyCalendar.getTime() );
+
+ // To track the period at the same time in preceding years.
+ Period yearlyPeriod = calendarPeriodType.createPeriod( calCopy );
+
+ // For past years, fetch the period at the same time of year
+ // as this period, and any periods after this period within the
+ // sequentialPeriod limit. For the year of the stating period,
+ // we will only fetch previous sequential periods.
+
+ if ( annualCount > 0 )
+ {
+ // Fetch the period at the same time of year as the
+ // starting period.
+ evaluateRightSidePeriod( periodTypeX, sampleValues, source, rightSidePeriodTypes, yearlyPeriod,
+ rule, sourceDataElements, context );
+
+ // Fetch the sequential periods after this prior-year
+ // period.
+ Period sequentialPeriod = new Period( yearlyPeriod );
+ for ( int sequentialCount = 0; sequentialCount < sequentialSampleCount; sequentialCount++ )
+ {
+ sequentialPeriod = calendarPeriodType.getNextPeriod( sequentialPeriod );
+ evaluateRightSidePeriod( periodTypeX, sampleValues, source, rightSidePeriodTypes,
+ sequentialPeriod, rule, sourceDataElements, context );
+ }
+ }
+
+ // Fetch the seqential periods before this period (both this
+ // year and past years):
+ Period sequentialPeriod = new Period( yearlyPeriod );
+ for ( int sequentialCount = 0; sequentialCount < sequentialSampleCount; sequentialCount++ )
+ {
+ sequentialPeriod = calendarPeriodType.getPreviousPeriod( sequentialPeriod );
+ evaluateRightSidePeriod( periodTypeX, sampleValues, source, rightSidePeriodTypes,
+ sequentialPeriod, rule, sourceDataElements, context );
+ }
+
+ // Move to the previous year:
+ yearlyCalendar.set( Calendar.YEAR, yearlyCalendar.get( Calendar.YEAR ) - 1 );
+ }
+
+ rightSideValue = rightSideAverage( rule, sampleValues, annualSampleCount, sequentialSampleCount );
+ }
+ return rightSideValue;
+ }
+
+ /**
+ * Evaluates the right side of a monitoring-type validation rule for a given
+ * organisation unit and period, and adds the value to a list of sample
+ * values.
+ *
+ * Note that for a monitoring-type rule, evaluating the right side
+ * expression can result in sampling multiple periods and/or child
+ * organisation units.
+ *
+ * @param periodTypeX the period type extended information
+ * @param sampleValues the list of sample values to add to
+ * @param source the organisation unit
+ * @param allowedPeriodTypes the period types in which the data may exist
+ * @param period the main period for the validation rule evaluation
+ * @param rule the monitoring-type rule being evaluated
+ * @param sourceDataElements the data elements configured for this
+ * organisation unit
+ * @param context the evaluation run context
+ */
+ private void evaluateRightSidePeriod( PeriodTypeExtended periodTypeX, List<Double> sampleValues,
+ OrganisationUnit source, Collection<PeriodType> allowedPeriodTypes, Period period, ValidationRule rule,
+ Collection<DataElement> sourceDataElements, ValidationRunContext context )
+ {
+ Period periodInstance = context.getPeriodService().getPeriod( period.getStartDate(), period.getEndDate(),
+ period.getPeriodType() );
+
+ if ( periodInstance != null )
+ {
+ Set<DataElement> dataElements = rule.getRightSide().getDataElementsInExpression();
+ Set<DataElementOperand> incompleteValues = new HashSet<DataElementOperand>();
+ Map<DataElementOperand, Double> dataValueMap = getDataValueMapRecursive( periodTypeX, dataElements,
+ sourceDataElements, dataElements, allowedPeriodTypes, period, source, null, incompleteValues );
+ Double value = context.getExpressionService().getExpressionValue( rule.getRightSide(), dataValueMap,
+ context.getConstantMap(), null, incompleteValues );
+
+ if ( value != null )
+ {
+ sampleValues.add( value );
+ }
+ }
+ }
+
+ /**
+ * Finds the average right-side sample value. This is used as the right-side
+ * expression value to evaluate a monitoring-type rule.
+ *
+ * @param rule monitoring-type rule being evaluated
+ * @param sampleValues sample values actually collected
+ * @param annualSampleCount number of annual samples tried for
+ * @param sequentialSampleCount number of sequential samples tried for
+ * @return
+ */
+ Double rightSideAverage( ValidationRule rule, List<Double> sampleValues, int annualSampleCount,
+ int sequentialSampleCount )
+ {
+ // Find the expected sample count for the last period of its type in the
+ // database: sequentialSampleCount for the immediately preceding periods
+ // in this year and for every past year: one sample for the same period
+ // in that year, plus sequentialSampleCounts before and after.
+ Double average = null;
+ if ( !sampleValues.isEmpty() )
+ {
+ int expectedSampleCount = sequentialSampleCount + annualSampleCount * (1 + 2 * sequentialSampleCount);
+ int highOutliers = rule.getHighOutliers() == null ? 0 : rule.getHighOutliers();
+ int lowOutliers = rule.getLowOutliers() == null ? 0 : rule.getLowOutliers();
+
+ // If we had fewer than the expected number of samples, then scale
+ // back
+ if ( highOutliers + lowOutliers > sampleValues.size() )
+ {
+ highOutliers = (highOutliers * sampleValues.size()) / expectedSampleCount;
+ lowOutliers = (lowOutliers * sampleValues.size()) / expectedSampleCount;
+ }
+
+ // If we (still) have any high and/or low outliers to remove, then
+ // sort the sample values and remove the high and/or low outliers.
+ if ( highOutliers + lowOutliers > 0 )
+ {
+ Collections.sort( sampleValues );
+ log.trace( "Removing " + highOutliers + " high and " + lowOutliers + " low outliers from "
+ + Arrays.toString( sampleValues.toArray() ) );
+ sampleValues = sampleValues.subList( lowOutliers, sampleValues.size() - highOutliers );
+ log.trace( "Result: " + Arrays.toString( sampleValues.toArray() ) );
+ }
+ Double sum = 0.0;
+ for ( Double sample : sampleValues )
+ {
+ sum += sample;
+ }
+ average = sum / sampleValues.size();
+ }
+ return average;
+ }
+
+ /**
+ * Gets data values for a given organisation unit and period, recursing if
+ * necessary to sum the values from child organisation units.
+ *
+ * @param periodTypeX period type which we are evaluating
+ * @param ruleDataElements data elements configured for the rule
+ * @param sourceDataElements data elements configured for the organisation
+ * unit
+ * @param recursiveDataElements data elements for which we will recurse if
+ * necessary
+ * @param allowedPeriodTypes all the periods in which we might find the data
+ * values
+ * @param period period in which we are looking for values
+ * @param source organisation unit for which we are looking for values
+ * @param lastUpdatedMap map showing when each data values was last updated
+ * @param incompleteValues ongoing list showing which values were found but
+ * not from all children
+ * @return the map of values found
+ */
+ private Map<DataElementOperand, Double> getDataValueMapRecursive( PeriodTypeExtended periodTypeX,
+ Collection<DataElement> ruleDataElements, Collection<DataElement> sourceDataElements,
+ Set<DataElement> recursiveDataElements, Collection<PeriodType> allowedPeriodTypes, Period period,
+ OrganisationUnit source, Map<DataElementOperand, Date> lastUpdatedMap, Set<DataElementOperand> incompleteValues )
+ {
+ Set<DataElement> dataElementsToGet = new HashSet<DataElement>( ruleDataElements );
+ dataElementsToGet.retainAll( sourceDataElements );
+ log.trace( "getDataValueMapRecursive: source:" + source.getName() + " elementsToGet["
+ + dataElementsToGet.size() + "] allowedPeriodTypes[" + allowedPeriodTypes.size() + "]" );
+
+ Map<DataElementOperand, Double> dataValueMap;
+
+ if ( dataElementsToGet.isEmpty() )
+ {
+ // We still might get something recursively
+ dataValueMap = new HashMap<DataElementOperand, Double>();
+ }
+ else
+ {
+ dataValueMap = context.getDataValueService().getDataValueMap( dataElementsToGet, period.getStartDate(), source,
+ allowedPeriodTypes, lastUpdatedMap );
+ }
+
+ // See if there are any data elements we need to get recursively:
+ Set<DataElement> recursiveDataElementsNeeded = new HashSet<DataElement>( recursiveDataElements );
+ recursiveDataElementsNeeded.removeAll( dataElementsToGet );
+ if ( !recursiveDataElementsNeeded.isEmpty() )
+ {
+ int childCount = 0;
+ Map<DataElementOperand, Integer> childValueCounts = new HashMap<DataElementOperand, Integer>();
+
+ for ( OrganisationUnit child : source.getChildren() )
+ {
+ Collection<DataElement> childDataElements = periodTypeX.getSourceDataElements().get( child );
+ Map<DataElementOperand, Double> childMap = getDataValueMapRecursive( periodTypeX,
+ recursiveDataElementsNeeded, childDataElements, recursiveDataElementsNeeded, allowedPeriodTypes,
+ period, child, lastUpdatedMap, incompleteValues );
+
+ for ( DataElementOperand deo : childMap.keySet() )
+ {
+ Double baseValue = dataValueMap.get( deo );
+ dataValueMap.put( deo, baseValue == null ? childMap.get( deo ) : baseValue + childMap.get( deo ) );
+
+ Integer childValueCount = childValueCounts.get( deo );
+ childValueCounts.put( deo, childValueCount == null ? 1 : childValueCount + 1 );
+ }
+
+ childCount++;
+ }
+
+ for ( Map.Entry<DataElementOperand, Integer> entry : childValueCounts.entrySet() )
+ {
+ if ( childCount != entry.getValue() )
+ {
+ // Remember that we found this DataElementOperand value
+ // in some but not all children
+ incompleteValues.add( entry.getKey() );
+ }
+ }
+ }
+
+ return dataValueMap;
+ }
+
+}