← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 8952: 1. reverted some aspects of the new data mart code, needs some more work before it can be release...

 

Merge authors:
  Lars Helge Øverland (larshelge)
------------------------------------------------------------
revno: 8952 [merge]
committer: Lars Helge Øverland <larshelge@xxxxxxxxx>
branch nick: dhis2
timestamp: Thu 2012-11-08 21:08:11 +0300
message:
  1. reverted some aspects of the new data mart code, needs some more work before it can be released. 2. added property UserCredentials.disabled, made it possible to disable users without deleting them. 3. improved comments in interpretations.
added:
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/DataElementOperandList.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/AverageIntSingleValueAggregator.java
  dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/filter/NonCriticalUserAuthorityGroupFilter.java
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/images/disable.png
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/images/disable_denied.png
  dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/DisableUserAction.java
modified:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/datamart/CrossTabDataValue.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserCredentials.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserCredentialsStore.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/DefaultUserDetailsService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/startup/TableAlteror.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/startup/TableCreator.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/user/DefaultUserService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/user/hibernate/HibernateUserCredentialsStore.java
  dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/user/hibernate/UserCredentials.hbm.xml
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/DataMartManager.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/JdbcDataMartManager.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/cache/AggregationCache.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/cache/MemoryAggregationCache.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/AverageBoolAggregator.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/AverageIntAggregator.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/DataElementAggregator.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/SumBoolAggregator.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/SumIntAggregator.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/crosstab/CrossTabService.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/crosstab/DefaultCrossTabService.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/crosstab/jdbc/CrossTabStore.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/crosstab/jdbc/JDBCCrossTabStore.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/dataelement/DataElementDataMart.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/dataelement/DefaultDataElementDataMart.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/engine/DefaultDataMartEngine.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/indicator/DefaultIndicatorDataMart.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/resources/META-INF/dhis/beans.xml
  dhis-2/dhis-services/dhis-service-datamart-default/src/test/java/org/hisp/dhis/datamart/DataMartServiceOrgUnitTest.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/test/java/org/hisp/dhis/datamart/DataMartServiceTest.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/test/java/org/hisp/dhis/datamart/crosstab/CrossTabServiceTest.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/test/java/org/hisp/dhis/datamart/crosstab/CrossTabStoreTest.java
  dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/AccountController.java
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/ajax/jsonUser.vm
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/useraccount/account.js
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/useraccount/account.vm
  dhis-2/dhis-web/dhis-web-dashboard-integration/src/main/resources/struts.xml
  dhis-2/dhis-web/dhis-web-dashboard-integration/src/main/webapp/dhis-web-dashboard-integration/interpretation.vm
  dhis-2/dhis-web/dhis-web-dashboard-integration/src/main/webapp/dhis-web-dashboard-integration/interpretationFeed.vm
  dhis-2/dhis-web/dhis-web-dashboard-integration/src/main/webapp/dhis-web-dashboard-integration/map_view.vm
  dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/java/org/hisp/dhis/settings/action/system/GetAccessSettingsAction.java
  dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/AddUserAction.java
  dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/EditUserGroupFormAction.java
  dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/GetUserListAction.java
  dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/RemoveUserAction.java
  dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/META-INF/dhis/beans.xml
  dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/org/hisp/dhis/user/i18n_module.properties
  dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/struts.xml
  dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/webapp/dhis-web-maintenance-user/allUser.vm
  dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/webapp/dhis-web-maintenance-user/javascript/user.js
  dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/webapp/dhis-web-maintenance-user/menu.vm


--
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/datamart/CrossTabDataValue.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/datamart/CrossTabDataValue.java	2012-08-25 11:47:17 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/datamart/CrossTabDataValue.java	2012-11-08 08:33:41 +0000
@@ -30,17 +30,22 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import org.hisp.dhis.dataelement.DataElementOperand;
+
 /**
  * @author Lars Helge Overland
+ * @version $Id: CrossTabDataValue.java 5514 2008-08-04 10:48:07Z larshelg $
  */
 public class CrossTabDataValue
 {
     private int periodId;
     
+    private int sourceId;
+    
     /**
-     * Contains org unit identifier mapped to data value.
+     * Contains Operand (data element id and category option combo id) and data value.
      */
-    private Map<Integer, String> valueMap = new HashMap<Integer, String>();
+    private Map<DataElementOperand, String> valueMap = new HashMap<DataElementOperand, String>();
 
     // -------------------------------------------------------------------------
     // Constructors
@@ -50,6 +55,13 @@
     {   
     }
     
+    public CrossTabDataValue( int periodId, int sourceId, Map<DataElementOperand, String> valueMap )
+    {
+        this.periodId = periodId;
+        this.sourceId = sourceId;
+        this.valueMap = valueMap;
+    }
+
     // -------------------------------------------------------------------------
     // Getters & setters
     // -------------------------------------------------------------------------
@@ -64,12 +76,22 @@
         this.periodId = periodId;
     }
 
-    public Map<Integer, String> getValueMap()
+    public int getSourceId()
+    {
+        return sourceId;
+    }
+
+    public void setSourceId( int sourceId )
+    {
+        this.sourceId = sourceId;
+    }
+
+    public Map<DataElementOperand, String> getValueMap()
     {
         return valueMap;
     }
 
-    public void setValueMap( Map<Integer, String> valueMap )
+    public void setValueMap( Map<DataElementOperand, String> valueMap )
     {
         this.valueMap = valueMap;
     }
@@ -84,8 +106,9 @@
         final int PRIME = 31;
         
         int result = 1;
-
+        
         result = PRIME * result + periodId;
+        result = PRIME * result + sourceId;
         
         return result;
     }
@@ -110,12 +133,14 @@
         
         final CrossTabDataValue other = (CrossTabDataValue) object;
         
-        return periodId == other.periodId;
+        return periodId == other.periodId && sourceId == other.sourceId;
     }
     
     @Override
     public String toString()
     {
-        return "[" + periodId + "]";
+        String toString = "[period id: " + periodId + ", source id: " + sourceId + "]";
+        
+        return toString;
     }
 }

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserCredentials.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserCredentials.java	2012-11-04 09:06:31 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserCredentials.java	2012-11-08 13:26:59 +0000
@@ -105,7 +105,28 @@
      * Indicates whether this user was originally self registered.
      */
     private boolean selfRegistered;
-
+    
+    /**
+     * Indicates whether this is user is disabled, which means the user cannot
+     * be authenticated.
+     */
+    private boolean disabled;
+    
+    /**
+     * The date this credentials was created.
+     */
+    private Date created;
+
+    // -------------------------------------------------------------------------
+    // Constructor
+    // -------------------------------------------------------------------------
+
+    public UserCredentials()
+    {
+        this.lastLogin = new Date();
+        this.created = new Date();
+    }
+    
     // -------------------------------------------------------------------------
     // Logic
     // -------------------------------------------------------------------------
@@ -208,6 +229,30 @@
 
         return !userAuthorityGroups.contains( group ) && authorities.containsAll( group.getAuthorities() );
     }
+    
+    /**
+     * Indicates whether this user credentials can modify the given user 
+     * credentials. This user credentials must have the ALL authority or possess
+     * all user authorities of the other user credentials to do so.
+     * 
+     * @param other the user credentials to modify.
+     */
+    public boolean canModify( UserCredentials other )
+    {
+        if ( other == null )
+        {
+            return false;
+        }
+        
+        final Set<String> authorities = getAllAuthorities();
+
+        if ( authorities.contains( UserAuthorityGroup.AUTHORITY_ALL ) )
+        {
+            return true;
+        }      
+        
+        return authorities.containsAll( other.getAllAuthorities() );
+    }
 
     /**
      * Indicates whether this user credentials can issue all of the user authority
@@ -426,4 +471,24 @@
     {
         this.selfRegistered = selfRegistered;
     }
+
+    public boolean isDisabled()
+    {
+        return disabled;
+    }
+
+    public void setDisabled( boolean disabled )
+    {
+        this.disabled = disabled;
+    }
+
+    public Date getCreated()
+    {
+        return created;
+    }
+
+    public void setCreated( Date created )
+    {
+        this.created = created;
+    }
 }

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserCredentialsStore.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserCredentialsStore.java	2012-06-25 18:07:55 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserCredentialsStore.java	2012-11-08 09:46:51 +0000
@@ -104,6 +104,10 @@
     Collection<UserCredentials> getUsersByOrganisationUnitBetweenByName( OrganisationUnit orgUnit, String name,
         int first, int max );
 
+    Collection<UserCredentials> getSelfRegisteredUserCredentials( int first, int max );
+    
+    int getSelfRegisteredUserCredentialsCount();
+    
     Collection<UserCredentials> getInactiveUsers( Date date );
     
     Collection<UserCredentials> getInactiveUsers( Date date, int first, int max );

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserService.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserService.java	2012-10-06 10:40:16 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/user/UserService.java	2012-11-08 09:46:51 +0000
@@ -221,9 +221,12 @@
 
     Collection<UserCredentials> getUsersByOrganisationUnitBetween( OrganisationUnit orgUnit, int first, int max );
 
-    Collection<UserCredentials> getUsersByOrganisationUnitBetweenByName( OrganisationUnit orgUnit, String name,
-                                                                         int first, int max );
+    Collection<UserCredentials> getUsersByOrganisationUnitBetweenByName( OrganisationUnit orgUnit, String name, int first, int max );
 
+    Collection<UserCredentials> getSelfRegisteredUserCredentials( int first, int max );
+    
+    int getSelfRegisteredUserCredentialsCount();
+    
     Collection<UserCredentials> getInactiveUsers( int months );
 
     Collection<UserCredentials> getInactiveUsers( int months, int first, int max );

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/DefaultUserDetailsService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/DefaultUserDetailsService.java	2012-03-12 12:33:20 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/DefaultUserDetailsService.java	2012-11-08 09:46:51 +0000
@@ -77,8 +77,10 @@
             throw new UsernameNotFoundException( "Username does not exist" );
         }
 
-        return new User( credentials.getUsername(), credentials.getPassword(), true,
-            true, true, true, getGrantedAuthorities( credentials ) );
+        User user = new User( credentials.getUsername(), credentials.getPassword(), 
+            !credentials.isDisabled(), true, true, true, getGrantedAuthorities( credentials ) );
+        
+        return user;
     }
 
     // -------------------------------------------------------------------------

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/startup/TableAlteror.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/startup/TableAlteror.java	2012-11-07 14:39:18 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/startup/TableAlteror.java	2012-11-08 08:33:41 +0000
@@ -130,7 +130,7 @@
         executeSql( "ALTER TABLE indicator DROP COLUMN alternativename" );
         executeSql( "ALTER TABLE orgunitgroup DROP COLUMN image" );
         
-        executeSql( "DROP INDEX crosstab" );
+        executeSql( "DROP INDEX datamart_crosstab" );
         
         // remove relative period type
         executeSql( "DELETE FROM period WHERE periodtypeid=(select periodtypeid from periodtype where name in ( 'Survey', 'OnChange', 'Relative' ))" );
@@ -447,6 +447,7 @@
         executeSql( "update chart set userorganisationunit = false where userorganisationunit is null" );
 
         executeSql( "update users set selfregistered = false where selfregistered is null" );
+        executeSql( "update users set disabled = false where disabled is null" );
         
         // report, reporttable, chart groups
         

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/startup/TableCreator.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/startup/TableCreator.java	2012-10-14 18:59:05 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/startup/TableCreator.java	2012-11-08 08:33:41 +0000
@@ -72,7 +72,7 @@
         createSilently( statementBuilder.getCreateDataSetCompletenessTable(), "aggregateddatasetcompleteness" );
         createSilently( statementBuilder.getCreateOrgUnitDataSetCompletenessTable(), "aggregatedorgunitdatasetcompleteness" );
         
-        createSilently( "CREATE INDEX datamart_crosstab ON datavalue (dataelementid, categoryoptioncomboid, periodid)", "datamart_crosstab" );
+        createSilently( "CREATE INDEX crosstab ON datavalue ( periodid, sourceid )", "crosstab" );
         createSilently( "CREATE INDEX messageconversation_lastmessage ON messageconversation (lastmessage)", "messageconversation_lastmessage" );
         createSilently( "CREATE INDEX interpretation_lastupdated ON interpretation (lastupdated)", "interpretation_lastupdated" );
         

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/user/DefaultUserService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/user/DefaultUserService.java	2012-10-06 10:40:16 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/user/DefaultUserService.java	2012-11-08 09:46:51 +0000
@@ -420,6 +420,16 @@
         updateUserCredentials( credentials );
     }
 
+    public Collection<UserCredentials> getSelfRegisteredUserCredentials( int first, int max )
+    {
+        return userCredentialsStore.getSelfRegisteredUserCredentials( first, max );
+    }
+
+    public int getSelfRegisteredUserCredentialsCount()
+    {
+        return userCredentialsStore.getSelfRegisteredUserCredentialsCount();
+    }
+    
     public Collection<UserCredentials> getInactiveUsers( int months )
     {
         Calendar cal = PeriodType.createCalendarInstance();

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/user/hibernate/HibernateUserCredentialsStore.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/user/hibernate/HibernateUserCredentialsStore.java	2012-06-25 18:07:55 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/user/hibernate/HibernateUserCredentialsStore.java	2012-11-08 09:46:51 +0000
@@ -229,6 +229,29 @@
     }
 
     @SuppressWarnings( "unchecked" )
+    public Collection<UserCredentials> getSelfRegisteredUserCredentials( int first, int max )
+    {
+        Criteria criteria = sessionFactory.getCurrentSession().createCriteria( UserCredentials.class );
+        criteria.add( Restrictions.eq( "selfRegistered", true ) );
+        criteria.addOrder( Order.desc( "created" ) );
+        criteria.setFirstResult( first );
+        criteria.setMaxResults( max );
+        
+        return criteria.list();
+    }
+    
+    public int getSelfRegisteredUserCredentialsCount()
+    {
+        Criteria criteria = sessionFactory.getCurrentSession().createCriteria( UserCredentials.class );
+        criteria.add( Restrictions.eq( "selfRegistered", true ) );
+        criteria.setProjection( Projections.rowCount() );
+
+        Number rs = (Number) criteria.uniqueResult();
+
+        return rs != null ? rs.intValue() : 0;
+    }
+    
+    @SuppressWarnings( "unchecked" )
     public Collection<UserCredentials> getInactiveUsers( Date date )
     {
         Criteria criteria = sessionFactory.getCurrentSession().createCriteria( UserCredentials.class );

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/user/hibernate/UserCredentials.hbm.xml'
--- dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/user/hibernate/UserCredentials.hbm.xml	2012-11-04 09:06:31 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/user/hibernate/UserCredentials.hbm.xml	2012-11-08 09:46:51 +0000
@@ -39,6 +39,10 @@
     <property name="restoreExpiry" type="timestamp" />
     
     <property name="selfRegistered" />
+    
+    <property name="disabled" />
+    
+    <property name="created" />
 
   </class>
 </hibernate-mapping>

=== added file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/DataElementOperandList.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/DataElementOperandList.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/DataElementOperandList.java	2012-11-08 08:33:41 +0000
@@ -0,0 +1,105 @@
+package org.hisp.dhis.datamart;
+
+/*
+ * Copyright (c) 2004-2012, 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.List;
+
+import org.hisp.dhis.dataelement.DataElementOperand;
+import org.hisp.dhis.organisationunit.OrganisationUnit;
+import org.hisp.dhis.organisationunit.OrganisationUnitGroup;
+import org.hisp.dhis.period.Period;
+
+/**
+ * Wrapper class for a list of DataElementOperands which encapsulates logic for
+ * initializing, adding values and checking the state of the list. 
+ * 
+ * @author Lars Helge Overland
+ */
+public class DataElementOperandList
+{
+    private List<DataElementOperand> operands;
+    
+    private Object[] valueList;
+    
+    private boolean hasValues;
+    
+    private int offset;
+    
+    public DataElementOperandList( List<DataElementOperand> operands )
+    {
+        this.operands = operands;
+    }
+    
+    public void init( Period period, OrganisationUnit unit, OrganisationUnitGroup group )
+    {
+        this.hasValues = false;
+        this.offset = group != null ? 3 : 2;
+        
+        if ( valid() )
+        {
+            this.valueList = new Object[operands.size() + offset];
+            this.valueList[0] = period.getId();
+            this.valueList[1] = unit.getId();
+            
+            if ( group != null && group.getId() != 0 )
+            {
+                this.valueList[2] = group.getId();
+            }
+        }
+    }
+    
+    public void addValue( DataElementOperand operand, Double value )
+    {
+        if ( valid() )
+        {
+            final int index = operands.indexOf( operand );
+            
+            if ( index != -1 && value != null )
+            {                
+                this.valueList[index + offset] = value;
+                this.hasValues = true;
+            }
+        }
+    }
+    
+    public List<Object> getList()
+    {
+        return valid() ? Arrays.asList( this.valueList ) : null;
+    }
+    
+    public boolean valid()
+    {
+        return operands != null && operands.size() > 0;
+    }
+    
+    public boolean hasValues()
+    {
+        return hasValues;
+    }
+}

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/DataMartManager.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/DataMartManager.java	2012-08-25 11:47:17 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/DataMartManager.java	2012-11-08 08:33:41 +0000
@@ -1,41 +1,11 @@
 package org.hisp.dhis.datamart;
 
-/*
- * Copyright (c) 2004-2012, 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.Map;
 import java.util.Set;
 
 import org.hisp.dhis.dataelement.DataElementOperand;
 
-/**
- * @author Lars Helge Overland
- */
 public interface DataMartManager
 {
     /**
@@ -47,16 +17,7 @@
      */
     Set<DataElementOperand> getOperandsWithData( Set<DataElementOperand> operands );
 
-    /**
-     * Returns a map of organisation unit identifiers and data values.
-     * 
-     * @param operand the data element operand.
-     * @param periodId the period identifier.
-     * @return a map of organisation unit identifiers and data values.
-     */
-    Map<Integer, String> getDataValueMap( DataElementOperand operand, int periodId );
-    
-    Map<DataElementOperand, String> getAggregatedValueMap( int periodId, int organisationUnitId );
+    Map<DataElementOperand, String> getDataValueMap( int periodId, int sourceId );
     
     void createDataValueIndex();
     
@@ -79,8 +40,6 @@
      * @param periodIds a collection of Period identifiers.
      */
     void deleteAggregatedIndicatorValues( Collection<Integer> periodIds );
-
-    Map<DataElementOperand, String> getAggregatedOrgUnitValueMap( int periodId, int organisationUnitId, int organisationUnitGroupId );
     
     void createOrgUnitDataValueIndex();
     

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/JdbcDataMartManager.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/JdbcDataMartManager.java	2012-10-03 18:14:28 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/JdbcDataMartManager.java	2012-11-08 08:33:41 +0000
@@ -100,26 +100,27 @@
     }
 
     @Override
-    public Map<Integer, String> getDataValueMap( DataElementOperand operand, int periodId )
+    public Map<DataElementOperand, String> getDataValueMap( int periodId, int sourceId )
     {
         final StatementHolder holder = statementManager.getHolder();
             
         try
         {
             final String sql =
-                "SELECT sourceid, value " +
+                "SELECT dataelementid, categoryoptioncomboid, value " +
                 "FROM datavalue " +
-                "WHERE dataelementid = " + operand.getDataElementId() + " " +
-                "AND categoryoptioncomboid = " + operand.getOptionComboId() + " " +
-                "AND periodid = " + periodId;
+                "WHERE periodid = " + periodId + " " +
+                "AND sourceid = " + sourceId;
             
             final ResultSet resultSet = holder.getStatement().executeQuery( sql );
             
-            final Map<Integer, String> map = new HashMap<Integer, String>();
+            final Map<DataElementOperand, String> map = new HashMap<DataElementOperand, String>();
             
             while ( resultSet.next() )
             {
-                map.put( resultSet.getInt( 1 ), resultSet.getString( 2 ) );
+                final DataElementOperand operand = new DataElementOperand( resultSet.getInt( 1 ), resultSet.getInt( 2 ) );
+                
+                map.put( operand, resultSet.getString( 3 ) );
             }
             
             return map;
@@ -139,46 +140,9 @@
     // -------------------------------------------------------------------------
 
     @Override
-    public Map<DataElementOperand, String> getAggregatedValueMap( int periodId, int organisationUnitId )
-    {
-        final StatementHolder holder = statementManager.getHolder();
-            
-        try
-        {
-            final String sql =
-                "SELECT dataelementid, categoryoptioncomboid, value " +
-                "FROM aggregateddatavalue " +
-                "WHERE periodid = " + periodId + " " +
-                "AND organisationunitid = " + organisationUnitId;
-            
-            final ResultSet resultSet = holder.getStatement().executeQuery( sql );
-            
-            final Map<DataElementOperand, String> map = new HashMap<DataElementOperand, String>();
-            
-            while ( resultSet.next() )
-            {
-                final DataElementOperand operand = new DataElementOperand( resultSet.getInt( 1 ), resultSet.getInt( 2 ) );
-                
-                map.put( operand, resultSet.getString( 3 ) );
-            }
-            
-            return map;
-        }
-        catch ( SQLException ex )
-        {
-            throw new RuntimeException( "Failed to get AggregatedDataValues", ex );
-        }
-        finally
-        {
-            holder.close();
-        }
-    }
-
-    @Override
     public void createDataValueIndex()
     {
         executeSilently( "CREATE INDEX aggregateddatavalue_index ON aggregateddatavalue (dataelementid, periodid, organisationunitid, categoryoptioncomboid, value)" );
-        executeSilently( "CREATE INDEX aggregateddatavalue_period_index ON aggregateddatavalue (periodid, organisationunitid)" );
     }
 
     @Override
@@ -191,7 +155,6 @@
     public void dropDataValueIndex()
     {
         executeSilently( "DROP INDEX aggregateddatavalue_index" );
-        executeSilently( "DROP INDEX aggregateddatavalue_period_index" );
     }
 
     @Override
@@ -225,47 +188,9 @@
     // -------------------------------------------------------------------------
 
     @Override
-    public Map<DataElementOperand, String> getAggregatedOrgUnitValueMap( int periodId, int organisationUnitId, int organisationUnitGroupId )
-    {
-        final StatementHolder holder = statementManager.getHolder();
-            
-        try
-        {
-            final String sql =
-                "SELECT dataelementid, categoryoptioncomboid, value " +
-                "FROM aggregatedorgunitdatavalue " +
-                "WHERE periodid = " + periodId + " " +
-                "AND organisationunitid = " + organisationUnitId + " " +
-                "AND organisationunitgroupid = " + organisationUnitGroupId;
-            
-            final ResultSet resultSet = holder.getStatement().executeQuery( sql );
-            
-            final Map<DataElementOperand, String> map = new HashMap<DataElementOperand, String>();
-            
-            while ( resultSet.next() )
-            {
-                final DataElementOperand operand = new DataElementOperand( resultSet.getInt( 1 ), resultSet.getInt( 2 ) );
-                
-                map.put( operand, resultSet.getString( 3 ) );
-            }
-            
-            return map;
-        }
-        catch ( SQLException ex )
-        {
-            throw new RuntimeException( "Failed to get AggregatedOrgUnitDataValues", ex );
-        }
-        finally
-        {
-            holder.close();
-        }
-    }
-    
-    @Override
     public void createOrgUnitDataValueIndex()
     {
         executeSilently( "CREATE INDEX aggregatedorgunitdatavalue_index ON aggregatedorgunitdatavalue (dataelementid, periodid, organisationunitid, organisationunitgroupid, categoryoptioncomboid, value)" );
-        executeSilently( "CREATE INDEX aggregatedorgunitdatavalue_period_index ON aggregatedorgunitdatavalue (periodid, organisationunitid, organisationunitgroupid)" );
     }
 
     @Override
@@ -278,7 +203,6 @@
     public void dropOrgUnitDataValueIndex()
     {
         executeSilently( "DROP INDEX aggregatedorgunitdatavalue_index" );
-        executeSilently( "DROP INDEX aggregatedorgunitdatavalue_period_index" );
     }
 
     @Override
@@ -330,39 +254,28 @@
     public void copyAggregatedDataValuesFromTemp()
     {
         executeSilently( "insert into aggregateddatavalue select * from aggregateddatavalue_temp" );
-        
-        executeSilently( statementBuilder.getVacuum( "aggregateddatavalue" ) );
     }
 
     public void copyAggregatedIndicatorValuesFromTemp()
     {
         executeSilently( "insert into aggregatedindicatorvalue select * from aggregatedindicatorvalue_temp" );
-        
-        executeSilently( statementBuilder.getVacuum( "aggregatedindicatorvalue" ) );
     }
 
     public void copyAggregatedOrgUnitDataValuesFromTemp()
     {
         executeSilently( "insert into aggregatedorgunitdatavalue select * from aggregatedorgunitdatavalue_temp" );
-        
-        executeSilently( statementBuilder.getVacuum( "aggregatedorgunitdatavalue" ) );
     }
 
     public void copyAggregatedOrgUnitIndicatorValuesFromTemp()
     {
         executeSilently( "insert into aggregatedorgunitindicatorvalue select * from aggregatedorgunitindicatorvalue_temp" );
-        
-        executeSilently( statementBuilder.getVacuum( "aggregatedorgunitindicatorvalue" ) );
     }
 
     private void executeSilently( String sql )
     {
         try
         {
-            if ( sql != null )
-            {
-                statementManager.getHolder().executeUpdate( sql, true );
-            }
+            statementManager.getHolder().executeUpdate( sql, true );
         }
         catch ( Exception ex )
         {

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/cache/AggregationCache.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/cache/AggregationCache.java	2012-08-29 15:13:58 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/cache/AggregationCache.java	2012-11-08 08:33:41 +0000
@@ -29,9 +29,7 @@
 
 import java.util.Collection;
 import java.util.Date;
-import java.util.Set;
 
-import org.hisp.dhis.dataelement.DataElementOperand;
 import org.hisp.dhis.period.Period;
 import org.hisp.dhis.period.PeriodType;
 
@@ -43,15 +41,13 @@
 {
     Collection<Integer> getIntersectingPeriods( Date startDate, Date endDate );
     
-    Collection<Integer> getPeriodsBetweenDatesPeriodType( final PeriodType periodType, final Date startDate, final Date endDate );
-    
     Collection<Integer> getPeriodsBetweenDates( Date startDate, Date endDate );
     
+    Collection<Integer> getPeriodsBetweenDatesPeriodType( PeriodType periodType, Date startDate, Date endDate );
+    
     Period getPeriod( int id );
     
     int getLevelOfOrganisationUnit( int id );
     
-    void filterForAggregationLevel( Set<Integer> organisationUnits, DataElementOperand operand, int unitLevel );
-    
     void clearCache();
 }

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/cache/MemoryAggregationCache.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/cache/MemoryAggregationCache.java	2012-08-29 15:13:58 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/cache/MemoryAggregationCache.java	2012-11-08 08:33:41 +0000
@@ -30,11 +30,8 @@
 import java.util.Collection;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.Map;
-import java.util.Set;
 
-import org.hisp.dhis.dataelement.DataElementOperand;
 import org.hisp.dhis.organisationunit.OrganisationUnitService;
 import org.hisp.dhis.period.Period;
 import org.hisp.dhis.period.PeriodService;
@@ -53,15 +50,15 @@
     // Cache
     // -------------------------------------------------------------------------
 
-    private final Map<String, Collection<Integer>> intersectingPeriodCache = new HashMap<String,Collection<Integer>>();
-
-    private final Map<String, Collection<Integer>> periodBetweenDatesCache = new HashMap<String,Collection<Integer>>();
-
-    private final Map<String, Collection<Integer>> periodBetweenDatesPeriodTypeCache = new HashMap<String,Collection<Integer>>();
-
-    private final Map<String, Period> periodCache = new HashMap<String,Period>();
-
-    private final Map<String, Integer> organisationUnitLevelCache = new HashMap<String, Integer>();
+    private final ThreadLocal<Map<String, Collection<Integer>>> intersectingPeriodCache = new ThreadLocal<Map<String,Collection<Integer>>>();
+
+    private final ThreadLocal<Map<String, Collection<Integer>>> periodBetweenDatesCache = new ThreadLocal<Map<String,Collection<Integer>>>();
+
+    private final ThreadLocal<Map<String, Collection<Integer>>> periodBetweenDatesPeriodTypeCache = new ThreadLocal<Map<String,Collection<Integer>>>();
+
+    private final ThreadLocal<Map<String, Period>> periodCache = new ThreadLocal<Map<String,Period>>();
+
+    private final ThreadLocal<Map<String, Integer>> organisationUnitLevelCache = new ThreadLocal<Map<String, Integer>>();
     
     // -------------------------------------------------------------------------
     // Dependencies
@@ -89,16 +86,22 @@
     {
         final String key = startDate.toString() + SEPARATOR + endDate.toString();
         
+        Map<String, Collection<Integer>> cache = intersectingPeriodCache.get();
+        
         Collection<Integer> periods = null;
         
-        if ( ( periods = intersectingPeriodCache.get( key ) ) != null )
+        if ( cache != null && ( periods = cache.get( key ) ) != null )
         {
             return periods;
         }
         
         periods = ConversionUtils.getIdentifiers( Period.class, periodService.getIntersectingPeriods( startDate, endDate ) );
         
-        intersectingPeriodCache.put( key, periods );
+        cache = ( cache == null ) ? new HashMap<String, Collection<Integer>>() : cache;
+        
+        cache.put( key, periods );
+        
+        intersectingPeriodCache.set( cache );
         
         return periods;
     }
@@ -107,34 +110,46 @@
     {
         final String key = startDate.toString() + SEPARATOR + endDate.toString();
         
+        Map<String, Collection<Integer>> cache = periodBetweenDatesCache.get();
+        
         Collection<Integer> periods = null;
         
-        if ( ( periods = periodBetweenDatesCache.get( key ) ) != null )
+        if ( cache != null && ( periods = cache.get( key ) ) != null )
         {
             return periods;
         }
         
         periods = ConversionUtils.getIdentifiers( Period.class, periodService.getPeriodsBetweenDates( startDate, endDate ) );
         
-        periodBetweenDatesCache.put( key, periods );
+        cache = ( cache == null ) ? new HashMap<String, Collection<Integer>>() : cache;
+        
+        cache.put( key, periods );
+        
+        periodBetweenDatesCache.set( cache );
         
         return periods;
     }
-    
+
     public Collection<Integer> getPeriodsBetweenDatesPeriodType( final PeriodType periodType, final Date startDate, final Date endDate )
     {
         final String key = periodType.getName() + SEPARATOR + startDate.toString() + SEPARATOR + endDate.toString();
         
+        Map<String, Collection<Integer>> cache = periodBetweenDatesPeriodTypeCache.get();
+        
         Collection<Integer> periods = null;
         
-        if ( ( periods = periodBetweenDatesPeriodTypeCache.get( key ) ) != null )
+        if ( cache != null && ( periods = cache.get( key ) ) != null )
         {
-        return periods;
+            return periods;
         }
         
         periods = ConversionUtils.getIdentifiers( Period.class, periodService.getPeriodsBetweenDates( periodType, startDate, endDate ) );
         
-        periodBetweenDatesPeriodTypeCache.put( key, periods );
+        cache = ( cache == null ) ? new HashMap<String, Collection<Integer>>() : cache;
+        
+        cache.put( key, periods );
+        
+        periodBetweenDatesPeriodTypeCache.set( cache );
         
         return periods;
     }
@@ -143,16 +158,22 @@
     {
         final String key = String.valueOf( id );
         
+        Map<String, Period> cache = periodCache.get();
+        
         Period period = null;
         
-        if ( ( period = periodCache.get( key ) ) != null )
+        if ( cache != null && ( period = cache.get( key ) ) != null )
         {
             return period;
         }
         
         period = periodService.getPeriod( id );
         
-        periodCache.put( key, period );
+        cache = ( cache == null ) ? new HashMap<String, Period>() : cache;
+        
+        cache.put( key, period );
+        
+        periodCache.set( cache );
         
         return period;
     }
@@ -161,43 +182,32 @@
     {
         final String key = String.valueOf( id );
         
+        Map<String, Integer> cache = organisationUnitLevelCache.get();
+        
         Integer level = null;
         
-        if ( ( level = organisationUnitLevelCache.get( key ) ) != null )
+        if ( cache != null && ( level = cache.get( key ) ) != null )
         {
             return level;
         }
                 
         level = organisationUnitService.getLevelOfOrganisationUnit( id );
         
-        organisationUnitLevelCache.put( key, level );
+        cache = ( cache == null ) ? new HashMap<String, Integer>() : cache;
+        
+        cache.put( key, level );
+        
+        organisationUnitLevelCache.set( cache );
         
         return level;
     }
     
-    public void filterForAggregationLevel( Set<Integer> organisationUnits, DataElementOperand operand, int unitLevel )
-    {
-        final Iterator<Integer> iter = organisationUnits.iterator();
-        
-        while ( iter.hasNext() )
-        {
-            final Integer orgUnitId = iter.next();
-            
-            final int dataValueLevel = operand.isHasAggregationLevels() ? getLevelOfOrganisationUnit( orgUnitId ) : 0;
-            
-            if ( operand.isHasAggregationLevels() && !operand.aggregationLevelIsValid( unitLevel, dataValueLevel ) )
-            {
-                iter.remove();
-            }
-        }        
-    }
-    
     public void clearCache()
     {
-        intersectingPeriodCache.clear();
-        periodBetweenDatesCache.clear();
-        periodBetweenDatesPeriodTypeCache.clear();
-        periodCache.clear();
-        organisationUnitLevelCache.clear();
+        intersectingPeriodCache.remove();
+        periodBetweenDatesCache.remove();
+        periodBetweenDatesPeriodTypeCache.remove();
+        periodCache.remove();
+        organisationUnitLevelCache.remove();
     }
 }

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/AverageBoolAggregator.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/AverageBoolAggregator.java	2012-08-29 14:44:51 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/AverageBoolAggregator.java	2012-11-08 08:33:41 +0000
@@ -31,24 +31,20 @@
 import static org.hisp.dhis.dataelement.DataElement.VALUE_TYPE_BOOL;
 import static org.hisp.dhis.system.util.DateUtils.getDaysInclusive;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Date;
-import java.util.List;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
+import java.util.Map.Entry;
 
 import org.hisp.dhis.dataelement.DataElementOperand;
-import org.hisp.dhis.datamart.OrgUnitOperand;
+import org.hisp.dhis.datamart.CrossTabDataValue;
 import org.hisp.dhis.datamart.aggregation.cache.AggregationCache;
 import org.hisp.dhis.datamart.crosstab.CrossTabService;
-import org.hisp.dhis.datamart.crosstab.jdbc.CrossTabStore;
-import org.hisp.dhis.organisationunit.OrganisationUnitGroup;
-import org.hisp.dhis.organisationunit.OrganisationUnitHierarchy;
 import org.hisp.dhis.period.Period;
-import org.hisp.dhis.period.PeriodHierarchy;
 import org.hisp.dhis.period.PeriodType;
-import org.hisp.dhis.system.util.MathUtils;
+import org.springframework.util.CollectionUtils;
 
 /**
  * @author Lars Helge Overland
@@ -78,116 +74,113 @@
     // DataElementAggregator implementation
     // -------------------------------------------------------------------------
 
-    public List<OrgUnitOperand> getAggregatedValues( DataElementOperand operand, Collection<Period> periods,
-        Collection<Integer> organisationUnits, Collection<OrganisationUnitGroup> organisationUnitGroups, 
-        PeriodHierarchy periodHierarchy, OrganisationUnitHierarchy orgUnitHierarchy, String key )
+    public Map<DataElementOperand, Double> getAggregatedValues( final Collection<DataElementOperand> operands, 
+        final Period period, int unitLevel, final Collection<Integer> organisationUnits, String key )
     {
-        final Map<String, String> crossTabValues = crossTabService.getCrossTabDataValues( operand, 
-            periodHierarchy.getIntersectingPeriods( periods ), orgUnitHierarchy.getChildren( organisationUnits ), key );
-        
-        final List<OrgUnitOperand> values = new ArrayList<OrgUnitOperand>();
-
-        if ( crossTabValues.size() == 0 )
+        if ( CollectionUtils.isEmpty( operands ) )
         {
-            return values;
+            return EMPTY_MAP;
         }
-
-        for ( Period period : periods )
+        
+        final Collection<CrossTabDataValue> crossTabValues = crossTabService.getCrossTabDataValues( operands, 
+            aggregationCache.getIntersectingPeriods( period.getStartDate(), period.getEndDate() ), organisationUnits, key );
+        
+        final Map<DataElementOperand, double[]> entries = getAggregate( crossTabValues, period.getStartDate(), 
+            period.getEndDate(), period.getStartDate(), period.getEndDate(), unitLevel ); // <Operand, [total value, total relevant days]>
+
+        final Map<DataElementOperand, Double> values = new HashMap<DataElementOperand, Double>(); // <Operand, total value>
+
+        double average = 0.0;
+        
+        for ( final Entry<DataElementOperand, double[]> entry : entries.entrySet() )
         {
-            final PeriodType periodType = period.getPeriodType();
-            
-            final Collection<Integer> intersectingPeriods = periodHierarchy.getIntersectingPeriods( period );
-            
-            for ( final Integer organisationUnit : organisationUnits )
+            if ( entry.getValue() != null && entry.getValue()[ 1 ] > 0 )
             {
-                final int unitLevel = operand.isHasAggregationLevels() ? aggregationCache.getLevelOfOrganisationUnit( organisationUnit ) : 0;
+                average = entry.getValue()[ 0 ] / entry.getValue()[ 1 ] * 100; // Percentage value
                 
-                for ( OrganisationUnitGroup group : organisationUnitGroups )
+                values.put( entry.getKey(), average );
+            }
+        }
+        
+        return values;        
+    }
+    
+    private Map<DataElementOperand, double[]> getAggregate( final Collection<CrossTabDataValue> crossTabValues, 
+        final Date startDate, final Date endDate, final Date aggregationStartDate, final Date aggregationEndDate, int unitLevel )
+    {
+        final Map<DataElementOperand, double[]> totalSums = new HashMap<DataElementOperand, double[]>(); // <Operand, [total value, total relevant days]>
+        
+        for ( final CrossTabDataValue crossTabValue : crossTabValues )
+        {
+            final Period period = aggregationCache.getPeriod( crossTabValue.getPeriodId() );
+            
+            final Date currentStartDate = period.getStartDate();
+            final Date currentEndDate = period.getEndDate();
+            
+            final double duration = getDaysInclusive( currentStartDate, currentEndDate );
+
+            final int dataValueLevel = aggregationCache.getLevelOfOrganisationUnit( crossTabValue.getSourceId() );
+            
+            if ( duration > 0 )
+            {
+                for ( final Entry<DataElementOperand, String> entry : crossTabValue.getValueMap().entrySet() ) // <Operand, value>
                 {
-                    final Set<Integer> orgUnitChildren = orgUnitHierarchy.getChildren( organisationUnit, group );
-
-                    aggregationCache.filterForAggregationLevel( orgUnitChildren, operand, unitLevel );
-
-                    double totalValue = 0d;
-                    double totalRelevantDays = 0d;
-                    
-                    for ( Integer orgUnitChild : orgUnitChildren )
-                    {
-                        for ( Integer intersectingPeriod : intersectingPeriods )
-                        {
-                            final String val = crossTabValues.get( intersectingPeriod + CrossTabStore.SEPARATOR + orgUnitChild );
-                            
-                            if ( val != null )
-                            {
-                                final double[] entry = getAggregate( orgUnitChild, period, val, period.getStartDate(), period.getEndDate(), unitLevel ); // <Operand, [total value, total relevant days]>
-
-                                totalValue += entry[0];
-                                totalRelevantDays += entry[1];
-                            }
-                        }
-                    }
-                    
-                    if ( !MathUtils.isZero( totalRelevantDays ) )
-                    {
-                        double average = totalValue / totalRelevantDays * 100; // Percentage value                        
-                        
-                        values.add( new OrgUnitOperand( period.getId(), periodType.getId(), organisationUnit, group != null ? group.getId() : 0, average ) );
+                    if ( entry.getValue() != null && entry.getKey().aggregationLevelIsValid( unitLevel, dataValueLevel ) )
+                    {
+                        double value = 0.0;                        
+                        double relevantDays = 0.0;
+                        
+                        if ( currentStartDate.compareTo( startDate ) >= 0 && currentEndDate.compareTo( endDate ) <= 0 ) // Value is within period
+                        {
+                            relevantDays = getDaysInclusive( currentStartDate, currentEndDate );
+                        }
+                        else if ( currentStartDate.compareTo( startDate ) <= 0 && currentEndDate.compareTo( endDate ) >= 0 ) // Value spans whole period
+                        {
+                            relevantDays = getDaysInclusive( startDate, endDate );
+                        }
+                        else if ( currentStartDate.compareTo( startDate ) <= 0 && currentEndDate.compareTo( startDate ) >= 0 // Value spans period start
+                            && currentEndDate.compareTo( endDate ) <= 0 )
+                        {
+                            relevantDays = getDaysInclusive( startDate, currentEndDate );
+                        }
+                        else if ( currentStartDate.compareTo( startDate ) >= 0 && currentStartDate.compareTo( endDate ) <= 0 // Value spans period end
+                            && currentEndDate.compareTo( endDate ) >= 0 )
+                        {
+                            relevantDays = getDaysInclusive( currentStartDate, endDate );
+                        }
+
+                        if ( entry.getValue().toLowerCase().equals( TRUE ) )
+                        {
+                            value = relevantDays;
+                        }
+
+                        final double[] totalSum = totalSums.get( entry.getKey() );
+                        value += totalSum != null ? totalSum[0] : 0;
+                        relevantDays += totalSum != null ? totalSum[1] : 0;
+                        
+                        final double[] values = { value, relevantDays };
+                        
+                        totalSums.put( entry.getKey(), values );
                     }
                 }
             }
         }
         
-        return values;        
-    }
-
-    public boolean isApplicable( DataElementOperand operand )
-    {
-        return operand.getValueType().equals( VALUE_TYPE_BOOL ) && operand.getAggregationOperator().equals( AGGREGATION_OPERATOR_AVERAGE );
-    }
-
-    // -------------------------------------------------------------------------
-    // Supportive methods
-    // -------------------------------------------------------------------------
-
-    private double[] getAggregate( int organisationUnit, Period period, String val, Date startDate, Date endDate, int unitLevel )
-    {
-        double value = 0.0;                        
-        double relevantDays = 0.0;
-
-        final Date currentStartDate = period.getStartDate();
-        final Date currentEndDate = period.getEndDate();
+        return totalSums;
+    }
+
+    public Collection<DataElementOperand> filterOperands( final Collection<DataElementOperand> operands, final PeriodType periodType )
+    {
+        final Collection<DataElementOperand> filteredOperands = new HashSet<DataElementOperand>();
         
-        final double duration = getDaysInclusive( currentStartDate, currentEndDate );
-
-        if ( duration > 0 )
+        for ( final DataElementOperand operand : operands )
         {
-            if ( currentStartDate.compareTo( startDate ) >= 0 && currentEndDate.compareTo( endDate ) <= 0 ) // Value is within period
-            {
-                relevantDays = getDaysInclusive( currentStartDate, currentEndDate );
-            }
-            else if ( currentStartDate.compareTo( startDate ) <= 0 && currentEndDate.compareTo( endDate ) >= 0 ) // Value spans whole period
-            {
-                relevantDays = getDaysInclusive( startDate, endDate );
-            }
-            else if ( currentStartDate.compareTo( startDate ) <= 0 && currentEndDate.compareTo( startDate ) >= 0 // Value spans period start
-                && currentEndDate.compareTo( endDate ) <= 0 )
-            {
-                relevantDays = getDaysInclusive( startDate, currentEndDate );
-            }
-            else if ( currentStartDate.compareTo( startDate ) >= 0 && currentStartDate.compareTo( endDate ) <= 0 // Value spans period end
-                && currentEndDate.compareTo( endDate ) >= 0 )
-            {
-                relevantDays = getDaysInclusive( currentStartDate, endDate );
-            }
-
-            if ( val.toLowerCase().equals( TRUE ) )
-            {
-                value = relevantDays;
+            if ( operand.getValueType().equals( VALUE_TYPE_BOOL ) && operand.getAggregationOperator().equals( AGGREGATION_OPERATOR_AVERAGE ) )
+            {
+                filteredOperands.add( operand );
             }
         }
-
-        final double[] values = { value, relevantDays };
         
-        return values;
+        return filteredOperands;
     }
 }

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/AverageIntAggregator.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/AverageIntAggregator.java	2012-08-29 14:44:51 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/AverageIntAggregator.java	2012-11-08 08:33:41 +0000
@@ -31,24 +31,22 @@
 import static org.hisp.dhis.dataelement.DataElement.VALUE_TYPE_INT;
 import static org.hisp.dhis.system.util.DateUtils.getDaysInclusive;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Date;
-import java.util.List;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
+import java.util.Map.Entry;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import org.hisp.dhis.dataelement.DataElementOperand;
-import org.hisp.dhis.datamart.OrgUnitOperand;
+import org.hisp.dhis.datamart.CrossTabDataValue;
 import org.hisp.dhis.datamart.aggregation.cache.AggregationCache;
 import org.hisp.dhis.datamart.crosstab.CrossTabService;
-import org.hisp.dhis.datamart.crosstab.jdbc.CrossTabStore;
-import org.hisp.dhis.organisationunit.OrganisationUnitGroup;
-import org.hisp.dhis.organisationunit.OrganisationUnitHierarchy;
 import org.hisp.dhis.period.Period;
-import org.hisp.dhis.period.PeriodHierarchy;
 import org.hisp.dhis.period.PeriodType;
-import org.hisp.dhis.system.util.MathUtils;
+import org.springframework.util.CollectionUtils;
 
 /**
  * @author Lars Helge Overland
@@ -56,6 +54,8 @@
 public class AverageIntAggregator
     implements DataElementAggregator
 {
+    private static final Log log = LogFactory.getLog( AverageIntAggregator.class );
+    
     // -------------------------------------------------------------------------
     // Dependencies
     // -------------------------------------------------------------------------
@@ -78,122 +78,124 @@
     // DataElementAggregator implementation
     // -------------------------------------------------------------------------
 
-    public List<OrgUnitOperand> getAggregatedValues( DataElementOperand operand, Collection<Period> periods,
-        Collection<Integer> organisationUnits, Collection<OrganisationUnitGroup> organisationUnitGroups, 
-        PeriodHierarchy periodHierarchy, OrganisationUnitHierarchy orgUnitHierarchy, String key )
+    public Map<DataElementOperand, Double> getAggregatedValues( final Collection<DataElementOperand> operands, 
+        final Period period, int unitLevel, final Collection<Integer> organisationUnits, String key )
     {
-        final Map<String, String> crossTabValues = crossTabService.getCrossTabDataValues( operand, 
-            periodHierarchy.getIntersectingPeriods( periods ), orgUnitHierarchy.getChildren( organisationUnits ), key );
-        
-        final List<OrgUnitOperand> values = new ArrayList<OrgUnitOperand>();
-
-        if ( crossTabValues.size() == 0 )
+        if ( CollectionUtils.isEmpty( operands ) )
         {
-            return values;
+            return EMPTY_MAP;
         }
-
-        for ( Period period : periods )
+        
+        final Map<DataElementOperand, Double> values = new HashMap<DataElementOperand, Double>(); // <Operand, total value>
+        
+        for ( final Integer unitId : organisationUnits )
         {
-            final PeriodType periodType = period.getPeriodType();
-            
-            final Collection<Integer> intersectingPeriods = periodHierarchy.getIntersectingPeriods( period );
-            
-            for ( final Integer organisationUnit : organisationUnits )
+            final Collection<CrossTabDataValue> crossTabValues = 
+                crossTabService.getCrossTabDataValues( operands, aggregationCache.getIntersectingPeriods( period.getStartDate(), period.getEndDate() ), unitId, key );
+            
+            final Map<DataElementOperand, double[]> entries = getAggregate( crossTabValues, period.getStartDate(), 
+                period.getEndDate(), period.getStartDate(), period.getEndDate(), unitLevel ); // <Operand, [total value, total relevant days]>
+            
+            for ( final Entry<DataElementOperand, double[]> entry : entries.entrySet() ) 
             {
-                final int unitLevel = operand.isHasAggregationLevels() ? aggregationCache.getLevelOfOrganisationUnit( organisationUnit ) : 0;
-                
-                for ( OrganisationUnitGroup group : organisationUnitGroups )
+                if ( entry.getValue() != null && entry.getValue()[ 1 ] > 0 )
                 {
-                    final Set<Integer> orgUnitChildren = orgUnitHierarchy.getChildren( organisationUnit, group );
-
-                    aggregationCache.filterForAggregationLevel( orgUnitChildren, operand, unitLevel );
-                    
-                    double value = 0d;
-                    
-                    for ( Integer orgUnitChild : orgUnitChildren )
-                    {
-                        double totalValue = 0d;
-                        double totalRelevantDays = 0d;
-                        
-                        for ( Integer intersectingPeriod : intersectingPeriods )
-                        {
-                            final String val = crossTabValues.get( intersectingPeriod + CrossTabStore.SEPARATOR + orgUnitChild );
-                            
-                            if ( val != null )
-                            {
-                                final double[] entry = getAggregate( orgUnitChild, aggregationCache.getPeriod( intersectingPeriod ), val, period.getStartDate(), period.getEndDate(), unitLevel ); // <Org unit, [total value, total relevant days]>
-                                
-                                totalValue += entry[0];
-                                totalRelevantDays += entry[1];
-                            }
-                        }
-                                                
-                        if ( !MathUtils.isZero( totalRelevantDays ) )
-                        {
-                            double average = totalValue / totalRelevantDays;
-                            
-                            value += average;
-                        }
-                    }
-                    
-                    if ( !MathUtils.isZero( value ) )
-                    {
-                        values.add( new OrgUnitOperand( period.getId(), periodType.getId(), organisationUnit, group != null ? group.getId() : 0, value ) );
-                    }
+                    double average = entry.getValue()[ 0 ] / entry.getValue()[ 1 ];
+                    
+                    average += values.containsKey( entry.getKey() ) ? values.get( entry.getKey() ) : 0;
+                    
+                    values.put( entry.getKey(), average );
                 }
             }
-        }
+        }  
         
         return values;
     }
-
-    public boolean isApplicable( DataElementOperand operand )
-    {
-        return operand.getValueType().equals( VALUE_TYPE_INT ) && operand.getAggregationOperator().equals( AGGREGATION_OPERATOR_AVERAGE );
-    }
     
-    // -------------------------------------------------------------------------
-    // Supportive methods
-    // -------------------------------------------------------------------------
-
-    private double[] getAggregate( int organisationUnit, Period period, String val, Date startDate, Date endDate, int unitLevel )
-    {
-        double value = 0.0;
-        double relevantDays = 0.0;
-        
-        final Date currentStartDate = period.getStartDate();
-        final Date currentEndDate = period.getEndDate();
-
-        final double duration = getDaysInclusive( currentStartDate, currentEndDate );
-        
-        if ( duration > 0 )
-        {
-            value = Double.parseDouble( val );
-            
-            if ( currentStartDate.compareTo( startDate ) >= 0 && currentEndDate.compareTo( endDate ) <= 0 ) // Value is within period
-            {
-                relevantDays = getDaysInclusive( currentStartDate, currentEndDate );
-            }
-            else if ( currentStartDate.compareTo( startDate ) <= 0 && currentEndDate.compareTo( endDate ) >= 0 ) // Value spans whole period
-            {
-                relevantDays = getDaysInclusive( startDate, endDate );
-            }
-            else if ( currentStartDate.compareTo( startDate ) <= 0 && currentEndDate.compareTo( startDate ) >= 0
-                && currentEndDate.compareTo( endDate ) <= 0 ) // Value spans period start
-            {
-                relevantDays = getDaysInclusive( startDate, currentEndDate );
-            }
-            else if ( currentStartDate.compareTo( startDate ) >= 0 && currentStartDate.compareTo( endDate ) <= 0
-                && currentEndDate.compareTo( endDate ) >= 0 ) // Value spans period end
-            {
-                relevantDays = getDaysInclusive( currentStartDate, endDate );
-            }
-            
-            value = value * relevantDays;
+    private Map<DataElementOperand, double[]> getAggregate( final Collection<CrossTabDataValue> crossTabValues, 
+        final Date startDate, final Date endDate, final Date aggregationStartDate, final Date aggregationEndDate, int unitLevel )
+    {
+        final Map<DataElementOperand, double[]> totalSums = new HashMap<DataElementOperand, double[]>(); // <Operand, [total value, total relevant days]>
+
+        for ( final CrossTabDataValue crossTabValue : crossTabValues )
+        {
+            final Period period = aggregationCache.getPeriod( crossTabValue.getPeriodId() );
+            
+            final Date currentStartDate = period.getStartDate();
+            final Date currentEndDate = period.getEndDate();
+
+            final int dataValueLevel = aggregationCache.getLevelOfOrganisationUnit( crossTabValue.getSourceId() );
+
+            final double duration = getDaysInclusive( currentStartDate, currentEndDate );
+            
+            if ( duration > 0 )
+            {            
+                for ( final Entry<DataElementOperand, String> entry : crossTabValue.getValueMap().entrySet() ) // <Operand, value>
+                {
+                    if ( entry.getValue() != null && entry.getKey().aggregationLevelIsValid( unitLevel, dataValueLevel )  )
+                    {
+                        double value = 0.0;
+                        double relevantDays = 0.0;               
+                        
+                        try
+                        {
+                            value = Double.parseDouble( entry.getValue() );
+                        }
+                        catch ( NumberFormatException ex )
+                        {
+                            log.warn( "Value skipped, not numeric: '" + entry.getValue() );
+                            continue;
+                        }
+                        
+                        if ( currentStartDate.compareTo( startDate ) >= 0 && currentEndDate.compareTo( endDate ) <= 0 ) // Value is within period
+                        {
+                            relevantDays = getDaysInclusive( currentStartDate, currentEndDate );
+                        }
+                        else if ( currentStartDate.compareTo( startDate ) <= 0 && currentEndDate.compareTo( endDate ) >= 0 ) // Value spans whole period
+                        {
+                            relevantDays = getDaysInclusive( startDate, endDate );
+                        }
+                        else if ( currentStartDate.compareTo( startDate ) <= 0 && currentEndDate.compareTo( startDate ) >= 0
+                            && currentEndDate.compareTo( endDate ) <= 0 ) // Value spans period start
+                        {
+                            relevantDays = getDaysInclusive( startDate, currentEndDate );
+                        }
+                        else if ( currentStartDate.compareTo( startDate ) >= 0 && currentStartDate.compareTo( endDate ) <= 0
+                            && currentEndDate.compareTo( endDate ) >= 0 ) // Value spans period end
+                        {
+                            relevantDays = getDaysInclusive( currentStartDate, endDate );
+                        }
+                        
+                        value = value * relevantDays;
+
+                        final double[] totalSum = totalSums.get( entry.getKey() );
+                        value += totalSum != null ? totalSum[0] : 0;
+                        relevantDays += totalSum != null ? totalSum[1] : 0;
+                        
+                        final double[] values = { value, relevantDays };
+                        
+                        totalSums.put( entry.getKey(), values );
+                    }
+                }
+            }
+        }                    
+        
+        return totalSums;
+    }
+
+    public Collection<DataElementOperand> filterOperands( final Collection<DataElementOperand> operands, final PeriodType periodType )
+    {
+        final Collection<DataElementOperand> filteredOperands = new HashSet<DataElementOperand>();
+        
+        for ( final DataElementOperand operand : operands )
+        {
+            if ( operand.getValueType().equals( VALUE_TYPE_INT ) && operand.getAggregationOperator().equals( AGGREGATION_OPERATOR_AVERAGE ) &&
+                operand.getFrequencyOrder() < periodType.getFrequencyOrder() )
+            {
+                filteredOperands.add( operand );
+            }
         }
         
-        final double[] values = { value, relevantDays };
-        
-        return values;
-    }
+        return filteredOperands;
+    }    
 }

=== added file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/AverageIntSingleValueAggregator.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/AverageIntSingleValueAggregator.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/AverageIntSingleValueAggregator.java	2012-11-08 08:33:41 +0000
@@ -0,0 +1,137 @@
+package org.hisp.dhis.datamart.aggregation.dataelement;
+
+/*
+ * Copyright (c) 2004-2012, 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.dataelement.DataElement.AGGREGATION_OPERATOR_AVERAGE;
+import static org.hisp.dhis.dataelement.DataElement.VALUE_TYPE_INT;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hisp.dhis.dataelement.DataElementOperand;
+import org.hisp.dhis.datamart.CrossTabDataValue;
+import org.hisp.dhis.datamart.aggregation.cache.AggregationCache;
+import org.hisp.dhis.datamart.crosstab.CrossTabService;
+import org.hisp.dhis.period.Period;
+import org.hisp.dhis.period.PeriodType;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * @author Lars Helge Overland
+ */
+public class AverageIntSingleValueAggregator
+    implements DataElementAggregator
+{
+    private static final Log log = LogFactory.getLog( AverageIntSingleValueAggregator.class );
+    
+    // -------------------------------------------------------------------------
+    // Dependencies
+    // -------------------------------------------------------------------------
+
+    private CrossTabService crossTabService;
+
+    public void setCrossTabService( CrossTabService crossTabService )
+    {
+        this.crossTabService = crossTabService;
+    }
+
+    protected AggregationCache aggregationCache;
+        
+    public void setAggregationCache( AggregationCache aggregationCache )
+    {
+        this.aggregationCache = aggregationCache;
+    }
+
+    // -------------------------------------------------------------------------
+    // DataElementAggregator implementation
+    // -------------------------------------------------------------------------
+
+    public Map<DataElementOperand, Double> getAggregatedValues( final Collection<DataElementOperand> operands, 
+        final Period period, int unitLevel, final Collection<Integer> organisationUnits, String key )
+    {
+        if ( CollectionUtils.isEmpty( operands ) )
+        {
+            return EMPTY_MAP;
+        }
+        
+        final Collection<CrossTabDataValue> crossTabValues = crossTabService.getCrossTabDataValues( operands, 
+            aggregationCache.getIntersectingPeriods( period.getStartDate(), period.getEndDate() ), organisationUnits, key );
+        
+        final Map<DataElementOperand, Double> values = new HashMap<DataElementOperand, Double>(); // <Operand, total value>
+
+        for ( final CrossTabDataValue crossTabValue : crossTabValues )
+        {
+            final int dataValueLevel = aggregationCache.getLevelOfOrganisationUnit( crossTabValue.getSourceId() );
+            
+            for ( final Entry<DataElementOperand, String> entry : crossTabValue.getValueMap().entrySet() ) // <Operand, value>
+            {
+                if ( entry.getValue() != null && entry.getKey().aggregationLevelIsValid( unitLevel, dataValueLevel )  )
+                {
+                    double value = 0.0;
+                    
+                    try
+                    {
+                        value = Double.parseDouble( entry.getValue() );
+                    }
+                    catch ( NumberFormatException ex )
+                    {
+                        log.warn( "Value skipped, not numeric: '" + entry.getValue() );
+                        continue;
+                    }
+
+                    final Double current = values.get( entry.getKey() );
+                    value += current != null ? current : 0.0;        
+                    values.put( entry.getKey(), value );
+                }
+            }
+        }                    
+        
+        return values;
+    }
+    
+    public Collection<DataElementOperand> filterOperands( final Collection<DataElementOperand> operands, final PeriodType periodType )
+    {
+        final Collection<DataElementOperand> filteredOperands = new HashSet<DataElementOperand>();
+        
+        for ( final DataElementOperand operand : operands )
+        {
+            if ( operand.getValueType().equals( VALUE_TYPE_INT ) && operand.getAggregationOperator().equals( AGGREGATION_OPERATOR_AVERAGE ) &&
+                operand.getFrequencyOrder() >= periodType.getFrequencyOrder() )
+            {
+                filteredOperands.add( operand );
+            }
+        }
+        
+        return filteredOperands;
+    }    
+}

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/DataElementAggregator.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/DataElementAggregator.java	2012-08-29 14:39:44 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/DataElementAggregator.java	2012-11-08 08:33:41 +0000
@@ -29,15 +29,11 @@
 
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 import org.hisp.dhis.dataelement.DataElementOperand;
-import org.hisp.dhis.datamart.OrgUnitOperand;
-import org.hisp.dhis.organisationunit.OrganisationUnitGroup;
-import org.hisp.dhis.organisationunit.OrganisationUnitHierarchy;
 import org.hisp.dhis.period.Period;
-import org.hisp.dhis.period.PeriodHierarchy;
+import org.hisp.dhis.period.PeriodType;
 
 /**
  * @author Lars Helge Overland
@@ -47,9 +43,8 @@
     final String TRUE = "true";
     final Map<DataElementOperand, Double> EMPTY_MAP = new HashMap<DataElementOperand, Double>();
 
-    List<OrgUnitOperand> getAggregatedValues( DataElementOperand operand, Collection<Period> periods, 
-        Collection<Integer> organisationUnits, Collection<OrganisationUnitGroup> organisationUnitGroups, 
-        PeriodHierarchy periodHierarchy, OrganisationUnitHierarchy hierarchy, String key );
+    Map<DataElementOperand, Double> getAggregatedValues( final Collection<DataElementOperand> operands, 
+        final Period period, int unitLevel, final Collection<Integer> organisationUnits, String key );
     
-    boolean isApplicable( DataElementOperand operand );
+    Collection<DataElementOperand> filterOperands( Collection<DataElementOperand> operands, PeriodType periodType );
 }

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/SumBoolAggregator.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/SumBoolAggregator.java	2012-08-29 14:44:51 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/SumBoolAggregator.java	2012-11-08 08:33:41 +0000
@@ -30,23 +30,19 @@
 import static org.hisp.dhis.dataelement.DataElement.AGGREGATION_OPERATOR_SUM;
 import static org.hisp.dhis.dataelement.DataElement.VALUE_TYPE_BOOL;
 
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
+import java.util.Map.Entry;
 
 import org.hisp.dhis.dataelement.DataElementOperand;
-import org.hisp.dhis.datamart.OrgUnitOperand;
+import org.hisp.dhis.datamart.CrossTabDataValue;
 import org.hisp.dhis.datamart.aggregation.cache.AggregationCache;
 import org.hisp.dhis.datamart.crosstab.CrossTabService;
-import org.hisp.dhis.datamart.crosstab.jdbc.CrossTabStore;
-import org.hisp.dhis.organisationunit.OrganisationUnitGroup;
-import org.hisp.dhis.organisationunit.OrganisationUnitHierarchy;
 import org.hisp.dhis.period.Period;
-import org.hisp.dhis.period.PeriodHierarchy;
 import org.hisp.dhis.period.PeriodType;
-import org.hisp.dhis.system.util.MathUtils;
+import org.springframework.util.CollectionUtils;
 
 /**
  * @author Lars Helge Overland
@@ -76,60 +72,37 @@
     // DataElementAggregator implementation
     // -------------------------------------------------------------------------
 
-    public List<OrgUnitOperand> getAggregatedValues( DataElementOperand operand, Collection<Period> periods,
-        Collection<Integer> organisationUnits, Collection<OrganisationUnitGroup> organisationUnitGroups, 
-        PeriodHierarchy periodHierarchy, OrganisationUnitHierarchy orgUnitHierarchy, String key )
-    {        
-        final Map<String, String> crossTabValues = crossTabService.getCrossTabDataValues( operand, 
-            periodHierarchy.getPeriodsBetween( periods ), orgUnitHierarchy.getChildren( organisationUnits ), key );
-
-        final List<OrgUnitOperand> values = new ArrayList<OrgUnitOperand>();
-
-        if ( crossTabValues.size() == 0 )
+    public Map<DataElementOperand, Double> getAggregatedValues( final Collection<DataElementOperand> operands, 
+        final Period period, int unitLevel, final Collection<Integer> organisationUnits, String key )
+    {
+        if ( CollectionUtils.isEmpty( operands ) )
         {
-            return values;
+            return EMPTY_MAP;
         }
-
-        for ( Period period : periods )
+        
+        final Collection<CrossTabDataValue> crossTabValues = crossTabService.getCrossTabDataValues( operands, 
+            aggregationCache.getPeriodsBetweenDates( period.getStartDate(), period.getEndDate() ), organisationUnits, key );
+
+        final Map<DataElementOperand, Double> values = new HashMap<DataElementOperand, Double>(); // <Operand, total value>
+
+        for ( final CrossTabDataValue crossTabValue : crossTabValues )
         {
-            final PeriodType periodType = period.getPeriodType();
-            
-            if ( !isApplicable( operand, periodType ) )
-            {
-                continue;
-            }
-            
-            final Collection<Integer> periodsBetween = periodHierarchy.getPeriodsBetween( period );
-            
-            for ( Integer organisationUnit : organisationUnits )
-            {
-                final int unitLevel = operand.isHasAggregationLevels() ? aggregationCache.getLevelOfOrganisationUnit( organisationUnit ) : 0;
-                
-                for ( OrganisationUnitGroup group : organisationUnitGroups )
-                {   
-                    final Set<Integer> orgUnitChildren = orgUnitHierarchy.getChildren( organisationUnit, group );
-
-                    aggregationCache.filterForAggregationLevel( orgUnitChildren, operand, unitLevel );
-                    
-                    double value = 0d;
-
-                    for ( Integer periodBetween : periodsBetween )
-                    {
-                        for ( Integer orgUnitChild : orgUnitChildren )
-                        {
-                            final String val = crossTabValues.get( periodBetween + CrossTabStore.SEPARATOR + orgUnitChild );
-                            
-                            if ( TRUE.equalsIgnoreCase( val ) )
-                            {
-                                value++;
-                            }
-                        }
-                    }
-                                        
-                    if ( !MathUtils.isZero( value ) )
-                    {
-                        values.add( new OrgUnitOperand( period.getId(), periodType.getId(), organisationUnit, group != null ? group.getId() : 0, value ) );
-                    }
+            final int dataValueLevel = aggregationCache.getLevelOfOrganisationUnit( crossTabValue.getSourceId() );
+            
+            for ( final Entry<DataElementOperand, String> entry : crossTabValue.getValueMap().entrySet() ) // <Operand, value>
+            {
+                if ( entry.getValue() != null && entry.getKey().aggregationLevelIsValid( unitLevel, dataValueLevel ) )
+                {
+                    double value = 0.0;
+
+                    if ( entry.getValue().toLowerCase().equals( TRUE ) )
+                    {
+                        value = 1;
+                    }
+
+                    final Double current = values.get( entry.getKey() );
+                    value += current != null ? current : 0.0;        
+                    values.put( entry.getKey(), value );
                 }
             }
         }
@@ -137,17 +110,19 @@
         return values;
     }
     
-    public boolean isApplicable( DataElementOperand operand )
-    {
-        return operand.getValueType().equals( VALUE_TYPE_BOOL ) && operand.getAggregationOperator().equals( AGGREGATION_OPERATOR_SUM );
-    }
-
-    // -------------------------------------------------------------------------
-    // Supportive methods
-    // -------------------------------------------------------------------------
-
-    private boolean isApplicable( DataElementOperand operand, PeriodType periodType )
-    {
-        return operand.getFrequencyOrder() <= periodType.getFrequencyOrder(); // Ignore disaggregation
+    public Collection<DataElementOperand> filterOperands( final Collection<DataElementOperand> operands, final PeriodType periodType )
+    {
+        final Collection<DataElementOperand> filteredOperands = new HashSet<DataElementOperand>();
+        
+        for ( final DataElementOperand operand : operands )
+        {
+            if ( operand.getValueType().equals( VALUE_TYPE_BOOL ) && operand.getAggregationOperator().equals( AGGREGATION_OPERATOR_SUM ) &&
+                operand.getFrequencyOrder() <= periodType.getFrequencyOrder() ) // Ignore disaggregation
+            {
+                filteredOperands.add( operand );
+            }
+        }
+        
+        return filteredOperands;
     }
 }

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/SumIntAggregator.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/SumIntAggregator.java	2012-08-29 14:44:51 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/aggregation/dataelement/SumIntAggregator.java	2012-11-08 08:33:41 +0000
@@ -30,23 +30,21 @@
 import static org.hisp.dhis.dataelement.DataElement.AGGREGATION_OPERATOR_SUM;
 import static org.hisp.dhis.dataelement.DataElement.VALUE_TYPE_INT;
 
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
+import java.util.Map.Entry;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import org.hisp.dhis.dataelement.DataElementOperand;
-import org.hisp.dhis.datamart.OrgUnitOperand;
+import org.hisp.dhis.datamart.CrossTabDataValue;
 import org.hisp.dhis.datamart.aggregation.cache.AggregationCache;
 import org.hisp.dhis.datamart.crosstab.CrossTabService;
-import org.hisp.dhis.datamart.crosstab.jdbc.CrossTabStore;
-import org.hisp.dhis.organisationunit.OrganisationUnitGroup;
-import org.hisp.dhis.organisationunit.OrganisationUnitHierarchy;
 import org.hisp.dhis.period.Period;
-import org.hisp.dhis.period.PeriodHierarchy;
 import org.hisp.dhis.period.PeriodType;
-import org.hisp.dhis.system.util.MathUtils;
+import org.springframework.util.CollectionUtils;
 
 /**
  * @author Lars Helge Overland
@@ -54,6 +52,8 @@
 public class SumIntAggregator
     implements DataElementAggregator
 {
+    private static final Log log = LogFactory.getLog( SumIntAggregator.class );
+    
     // -------------------------------------------------------------------------
     // Dependencies
     // -------------------------------------------------------------------------
@@ -76,60 +76,42 @@
     // DataElementAggregator implementation
     // -------------------------------------------------------------------------
 
-    public List<OrgUnitOperand> getAggregatedValues( DataElementOperand operand, Collection<Period> periods, 
-        Collection<Integer> organisationUnits, Collection<OrganisationUnitGroup> organisationUnitGroups, 
-        PeriodHierarchy periodHierarchy, OrganisationUnitHierarchy orgUnitHierarchy, String key )
+    public Map<DataElementOperand, Double> getAggregatedValues( final Collection<DataElementOperand> operands, 
+        final Period period, int unitLevel, final Collection<Integer> organisationUnits, String key )
     {
-        final Map<String, String> crossTabValues = crossTabService.getCrossTabDataValues( operand, 
-            periodHierarchy.getPeriodsBetween( periods ), orgUnitHierarchy.getChildren( organisationUnits ), key );
-        
-        final List<OrgUnitOperand> values = new ArrayList<OrgUnitOperand>();
-
-        if ( crossTabValues.size() == 0 )
+        if ( CollectionUtils.isEmpty( operands ) )
         {
-            return values;
+            return EMPTY_MAP;
         }
         
-        for ( Period period : periods )
+        final Collection<CrossTabDataValue> crossTabValues = crossTabService.getCrossTabDataValues( operands, 
+            aggregationCache.getPeriodsBetweenDates( period.getStartDate(), period.getEndDate() ), organisationUnits, key );
+        
+        final Map<DataElementOperand, Double> values = new HashMap<DataElementOperand, Double>(); // <Operand, total value>
+
+        for ( final CrossTabDataValue crossTabValue : crossTabValues )
         {
-            final PeriodType periodType = period.getPeriodType();
-            
-            if ( !isApplicable( operand, periodType ) )
-            {
-                continue;
-            }
-            
-            final Collection<Integer> periodsBetween = periodHierarchy.getPeriodsBetween( period );
-            
-            for ( Integer organisationUnit : organisationUnits )
-            {
-                final int unitLevel = operand.isHasAggregationLevels() ? aggregationCache.getLevelOfOrganisationUnit( organisationUnit ) : 0;
-                
-                for ( OrganisationUnitGroup group : organisationUnitGroups )
+            final int dataValueLevel = aggregationCache.getLevelOfOrganisationUnit( crossTabValue.getSourceId() );
+            
+            for ( final Entry<DataElementOperand, String> entry : crossTabValue.getValueMap().entrySet() ) // <Operand, value>
+            {
+                if ( entry.getValue() != null && entry.getKey().aggregationLevelIsValid( unitLevel, dataValueLevel ) )
                 {
-                    final Set<Integer> orgUnitChildren = orgUnitHierarchy.getChildren( organisationUnit, group );
-                    
-                    aggregationCache.filterForAggregationLevel( orgUnitChildren, operand, unitLevel );
-                    
-                    double value = 0d;
-                    
-                    for ( Integer periodBetween : periodsBetween )
-                    {
-                        for ( Integer orgUnitChild : orgUnitChildren )
-                        {
-                            final String val = crossTabValues.get( periodBetween + CrossTabStore.SEPARATOR + orgUnitChild );
-                            
-                            if ( val != null )
-                            {
-                                value += Double.parseDouble( val );
-                            }                            
-                        }
-                    }
-                    
-                    if ( !MathUtils.isZero( value ) )
-                    {
-                        values.add( new OrgUnitOperand( period.getId(), periodType.getId(), organisationUnit, group != null ? group.getId() : 0, value ) );
-                    }
+                    double value = 0.0;
+                    
+                    try
+                    {
+                        value = Double.parseDouble( entry.getValue() );
+                    }
+                    catch ( NumberFormatException ex )
+                    {
+                        log.warn( "Value skipped, not numeric: '" + entry.getValue() );
+                        continue;
+                    }
+                    
+                    final Double current = values.get( entry.getKey() );
+                    value += current != null ? current : 0.0;        
+                    values.put( entry.getKey(), value );
                 }
             }
         }
@@ -137,17 +119,19 @@
         return values;
     }
     
-    public boolean isApplicable( DataElementOperand operand )
-    {
-        return operand.getValueType().equals( VALUE_TYPE_INT ) && operand.getAggregationOperator().equals( AGGREGATION_OPERATOR_SUM );
-    }
-
-    // -------------------------------------------------------------------------
-    // Supportive methods
-    // -------------------------------------------------------------------------
-
-    private boolean isApplicable( DataElementOperand operand, PeriodType periodType )
-    {
-        return operand.getFrequencyOrder() <= periodType.getFrequencyOrder(); // Ignore disaggregation
+    public Collection<DataElementOperand> filterOperands( final Collection<DataElementOperand> operands, final PeriodType periodType )
+    {
+        final Collection<DataElementOperand> filteredOperands = new HashSet<DataElementOperand>();
+        
+        for ( final DataElementOperand operand : operands )
+        {
+            if ( operand.getValueType().equals( VALUE_TYPE_INT ) && operand.getAggregationOperator().equals( AGGREGATION_OPERATOR_SUM ) &&
+                operand.getFrequencyOrder() <= periodType.getFrequencyOrder() ) // Ignore disaggregation
+            {
+                filteredOperands.add( operand );
+            }
+        }
+        
+        return filteredOperands;
     }
 }

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/crosstab/CrossTabService.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/crosstab/CrossTabService.java	2012-08-29 14:39:44 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/crosstab/CrossTabService.java	2012-11-08 08:33:41 +0000
@@ -33,6 +33,7 @@
 import java.util.concurrent.Future;
 
 import org.hisp.dhis.dataelement.DataElementOperand;
+import org.hisp.dhis.datamart.CrossTabDataValue;
 import org.hisp.dhis.organisationunit.OrganisationUnit;
 import org.hisp.dhis.organisationunit.OrganisationUnitGroup;
 import org.hisp.dhis.period.Period;
@@ -44,20 +45,19 @@
 {
     String ID = CrossTabService.class.getName();
 
-    String createCrossTabTable( List<Integer> organisationUnitIds );
+    String createCrossTabTable( List<DataElementOperand> operands );
     
     /**
      * Creates and populates the crosstab table. Operands without data will be
      * removed from the operands argument collection.
      * 
-     * @param operands the collection of DataElementOperands.
+     * @param operands the list of DataElementOperands.
      * @param periodIds the collection of Period identifiers.
      * @param organisationUnitIds the collection of OrganisationUnit identifiers.
-     *        The order of the list must be equal to the order of the table columns.
      * @return a List of random keys for each generated crosstab table. 
      */
-    Future<?> populateCrossTabTable( Collection<DataElementOperand> operands, 
-        Collection<Integer> periodIds, List<Integer> organisationUnitIds, String key );
+    Future<?> populateCrossTabTable( List<DataElementOperand> operands, 
+        Collection<Integer> periodIds, Collection<Integer> organisationUnitIds, String key );
 
     /**
      * Drops the crosstab table.
@@ -74,9 +74,6 @@
      */
     void createAggregatedDataCache( List<DataElementOperand> operands, String key );
     
-    Future<?> populateAggregatedDataCache( List<DataElementOperand> operands,
-        Collection<Period> periods, Collection<OrganisationUnit> organisationUnits, String key );
-    
     /**
      * Drops the aggregated data cache table.
      * @param key the key used in the table name.
@@ -93,30 +90,36 @@
      * @param key the key to use in table name.
      */
     void createAggregatedOrgUnitDataCache( List<DataElementOperand> operands, String key );
-    
-    Future<?> populateAggregatedOrgUnitDataCache( List<DataElementOperand> operands,
-        Collection<Period> periods, Collection<OrganisationUnit> organisationUnits, Collection<OrganisationUnitGroup> organisationUnitGroups, String key );
-    
+
     /**
      * Drops the aggregated org unit data cache table.
      * 
      * @param key the key used in the table name.
      */
     void dropAggregatedOrgUnitDataCache( String key );
-
-    /**
-     * Gets all CrossTabDataValues for the given collection of period identifiers 
-     * and organisation unit identifiers as a map. The map key is a 
-     * concatenation of <period identifier>-<organisation unit identifier>.
-     * 
-     * @param operand the data element operand.
-     * @param periodIds the period identifiers.
-     * @param organisationUnitIds the organisation unit identifiers.
-     * @return collection of CrossTabDataValues.
-     */
-    Map<String, String> getCrossTabDataValues( DataElementOperand operand, 
-        Collection<Integer> periodIds, Collection<Integer> organisationUnitIds, String key );
-
+    
+    /**
+     * Gets all CrossTabDataValues for the given collection of period ids and source ids.
+     * 
+     * @param dataElementIds the data element identifiers.
+     * @param periodIds the period identifiers.
+     * @param sourceIds the source identifiers.
+     * @return collection of CrossTabDataValues.
+     */
+    Collection<CrossTabDataValue> getCrossTabDataValues( Collection<DataElementOperand> operands, Collection<Integer> periodIds, 
+        Collection<Integer> sourceIds, String key );
+
+    /**
+     * Gets all CrossTabDataValues for the given collection of period ids and the source id.
+     * 
+     * @param dataElementIds the data element identifiers.
+     * @param periodIds the period identifiers.
+     * @param sourceId the source identifier.
+     * @return collection of CrossTabDataValues.
+     */
+    Collection<CrossTabDataValue> getCrossTabDataValues( Collection<DataElementOperand> operands, Collection<Integer> periodIds, 
+        int sourceId, String key );
+    
     /**
      * Gets a map of DataElementOperands and corresponding Double aggregated data
      * element value from the cache table. If the group argument is not null it

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/crosstab/DefaultCrossTabService.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/crosstab/DefaultCrossTabService.java	2012-08-29 14:39:44 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/crosstab/DefaultCrossTabService.java	2012-11-08 08:33:41 +0000
@@ -43,6 +43,7 @@
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.hisp.dhis.dataelement.DataElementOperand;
+import org.hisp.dhis.datamart.CrossTabDataValue;
 import org.hisp.dhis.datamart.DataMartManager;
 import org.hisp.dhis.datamart.crosstab.jdbc.CrossTabStore;
 import org.hisp.dhis.jdbc.batchhandler.GenericBatchHandler;
@@ -97,19 +98,19 @@
     // CrossTabService implementation
     // -------------------------------------------------------------------------
     
-    public String createCrossTabTable( List<Integer> organisationUnitIds )
+    public String createCrossTabTable( List<DataElementOperand> operands )
     {
         final String key = RandomStringUtils.randomAlphanumeric( 8 );
         
         crossTabStore.dropCrossTabTable( key );    
-        crossTabStore.createCrossTabTable( organisationUnitIds, key );
+        crossTabStore.createCrossTabTable( operands, key );
 
         return key;
     }
-
+    
     @Async
-    public Future<?> populateCrossTabTable( Collection<DataElementOperand> operands,
-        Collection<Integer> periodIds, List<Integer> organisationUnitIds, String key )
+    public Future<?> populateCrossTabTable( List<DataElementOperand> operands,
+        Collection<Integer> periodIds, Collection<Integer> organisationUnitIds, String key )
     {
         statementManager.initialise();
         
@@ -118,23 +119,22 @@
 
         int rows = 0;
         
-        for ( DataElementOperand operand : operands )
+        for ( final Integer periodId : periodIds )
         {
-            for ( int periodId : periodIds )
+            for ( final Integer sourceId : organisationUnitIds )
             {
-                final Map<Integer, String> map = dataMartManager.getDataValueMap( operand, periodId );
-
-                final List<String> valueList = new ArrayList<String>( operands.size() + 3 );
-
-                valueList.add( String.valueOf( operand.getDataElementId() ) );
-                valueList.add( String.valueOf( operand.getOptionComboId() ) );
+                final Map<DataElementOperand, String> map = dataMartManager.getDataValueMap( periodId, sourceId );
+
+                final List<String> valueList = new ArrayList<String>( operands.size() + 2 );
+
                 valueList.add( String.valueOf( periodId ) );
+                valueList.add( String.valueOf( sourceId ) );
 
                 boolean hasValues = false;
 
-                for ( int organisationUnitId : organisationUnitIds )
+                for ( DataElementOperand operand : operands )
                 {
-                    String value = map.get( organisationUnitId );
+                    String value = map.get( operand );
 
                     if ( value != null && value.length() > MAX_LENGTH )
                     {
@@ -190,55 +190,7 @@
     {
         crossTabStore.createAggregatedDataCache( operands, key );
     }
-
-    @Async
-    public Future<?> populateAggregatedDataCache( List<DataElementOperand> operands,
-        Collection<Period> periods, Collection<OrganisationUnit> organisationUnits, String key )
-    {
-        statementManager.initialise();
-        
-        final BatchHandler<Object> batchHandler = batchHandlerFactory.createBatchHandler( GenericBatchHandler.class ).
-            setTableName( CrossTabStore.AGGREGATEDDATA_CACHE_PREFIX + key ).init();
-        
-        for ( final Period period : periods )
-        {
-            for ( final OrganisationUnit organisationUnit : organisationUnits )
-            {
-                final Map<DataElementOperand, String> map = dataMartManager.getAggregatedValueMap( period.getId(), organisationUnit.getId() );
-
-                final List<String> valueList = new ArrayList<String>( operands.size() + 2 );
-
-                valueList.add( String.valueOf( period.getId() ) );
-                valueList.add( String.valueOf( organisationUnit.getId() ) );
-
-                boolean hasValues = false;
-
-                for ( DataElementOperand operand : operands )
-                {
-                    String value = map.get( operand );
-                    
-                    if ( value != null )
-                    {
-                        hasValues = true;
-                    }
-
-                    valueList.add( value );
-                }
-
-                if ( hasValues )
-                {
-                    batchHandler.addObject( valueList );
-                }
-            }
-        }
-        
-        batchHandler.flush();
-        
-        statementManager.destroy();
-        
-        return null;
-    }
-
+    
     public void dropAggregatedDataCache( String key )
     {
         crossTabStore.dropAggregatedDataCache( key );
@@ -248,70 +200,24 @@
     {
         crossTabStore.createAggregatedOrgUnitDataCache( operands, key );
     }
-
-    @Async
-    public Future<?> populateAggregatedOrgUnitDataCache( List<DataElementOperand> operands,
-        Collection<Period> periods, Collection<OrganisationUnit> organisationUnits, Collection<OrganisationUnitGroup> organisationUnitGroups, String key )
-    {
-        statementManager.initialise();
-        
-        final BatchHandler<Object> batchHandler = batchHandlerFactory.createBatchHandler( GenericBatchHandler.class ).
-            setTableName( CrossTabStore.AGGREGATEDORGUNITDATA_CACHE_PREFIX + key ).init();
-        
-        for ( final Period period : periods )
-        {
-            for ( final OrganisationUnitGroup group : organisationUnitGroups )
-            {
-                for ( final OrganisationUnit organisationUnit : organisationUnits )
-                {
-                    final Map<DataElementOperand, String> map = dataMartManager.getAggregatedOrgUnitValueMap( period.getId(), organisationUnit.getId(), group.getId() );
-    
-                    final List<String> valueList = new ArrayList<String>( operands.size() + 2 );
-    
-                    valueList.add( String.valueOf( period.getId() ) );
-                    valueList.add( String.valueOf( organisationUnit.getId() ) );
-                    valueList.add( String.valueOf( group.getId() ) );
-    
-                    boolean hasValues = false;
-    
-                    for ( DataElementOperand operand : operands )
-                    {
-                        String value = map.get( operand );
-                        
-                        if ( value != null )
-                        {
-                            hasValues = true;
-                        }
-    
-                        valueList.add( value );
-                    }
-    
-                    if ( hasValues )
-                    {
-                        batchHandler.addObject( valueList );
-                    }
-                }
-            }
-        }
-        
-        batchHandler.flush();
-        
-        statementManager.destroy();
-        
-        return null;
-    }
-
+    
     public void dropAggregatedOrgUnitDataCache( String key )
     {
         crossTabStore.dropAggregatedOrgUnitDataCache( key );
     }
     
-    public Map<String, String> getCrossTabDataValues( DataElementOperand operand, 
-        Collection<Integer> periodIds, Collection<Integer> organisationUnitIds, String key )
+    public Collection<CrossTabDataValue> getCrossTabDataValues( Collection<DataElementOperand> operands,
+        Collection<Integer> periodIds, Collection<Integer> sourceIds, String key )
     {
-        return crossTabStore.getCrossTabDataValues( operand, periodIds, organisationUnitIds, key );
+        return crossTabStore.getCrossTabDataValues( operands, periodIds, sourceIds, key );
     }
 
+    public Collection<CrossTabDataValue> getCrossTabDataValues( Collection<DataElementOperand> operands,
+        Collection<Integer> periodIds, int sourceId, String key )
+    {
+        return crossTabStore.getCrossTabDataValues( operands, periodIds, sourceId, key );
+    }
+    
     public Map<DataElementOperand, Double> getAggregatedDataCacheValue( Collection<DataElementOperand> operands, 
         Period period, OrganisationUnit unit, OrganisationUnitGroup group, String key )
     {

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/crosstab/jdbc/CrossTabStore.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/crosstab/jdbc/CrossTabStore.java	2012-08-29 14:39:44 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/crosstab/jdbc/CrossTabStore.java	2012-11-08 08:33:41 +0000
@@ -32,6 +32,7 @@
 import java.util.Map;
 
 import org.hisp.dhis.dataelement.DataElementOperand;
+import org.hisp.dhis.datamart.CrossTabDataValue;
 
 /**
  * @author Lars Helge Overland
@@ -41,10 +42,8 @@
 {
     final String ID = CrossTabStore.class.getName();
     final String CROSSTAB_TABLE_PREFIX = "crosstab_table_";
-    final String COLUMN_PREFIX = "col";
     final String AGGREGATEDDATA_CACHE_PREFIX = "aggregateddata_cache_";
     final String AGGREGATEDORGUNITDATA_CACHE_PREFIX = "aggregatedorgunitdata_cache_";
-    final String SEPARATOR = "-";
     
     /**
      * Creates a crosstab table where the first column is the period identifier,
@@ -53,7 +52,7 @@
      * 
      * @param operands the DataElementOperands.
      */
-    void createCrossTabTable( List<Integer> organisationUnitIds, String key );
+    void createCrossTabTable( List<DataElementOperand> operands, String key );
 
     /**
      * Drops the crosstab table.
@@ -96,17 +95,26 @@
     void dropAggregatedOrgUnitDataCache( String key );
     
     /**
-     * Gets all CrossTabDataValues for the given collection of period identifiers 
-     * and organisation unit identifiers as a map. The map key is a 
-     * concatenation of <period identifier>-<organisation unit identifier>.
-     * 
-     * @param operand the data element operand.
-     * @param periodIds the period identifiers.
-     * @param organisationUnitIds the organisation unit identifiers.
-     * @return collection of CrossTabDataValues.
-     */
-    Map<String, String> getCrossTabDataValues( DataElementOperand operand, 
-        Collection<Integer> periodIds, Collection<Integer> organisationUnitIds, String key );
+     * Gets all CrossTabDataValues for the given collection of period ids and source ids.
+     * 
+     * @param dataElementIds the data element identifiers.
+     * @param periodIds the period identifiers.
+     * @param sourceIds the source identifiers.
+     * @return collection of CrossTabDataValues.
+     */
+    Collection<CrossTabDataValue> getCrossTabDataValues( Collection<DataElementOperand> operands, Collection<Integer> periodIds, 
+        Collection<Integer> sourceIds, String key );
+
+    /**
+     * Gets all CrossTabDataValues for the given collection of period ids and the source id.
+     * 
+     * @param dataElementIds the data element identifiers.
+     * @param periodIds the period identifiers.
+     * @param sourceId the source identifier.
+     * @return collection of CrossTabDataValues.
+     */
+    Collection<CrossTabDataValue> getCrossTabDataValues( Collection<DataElementOperand> operands, Collection<Integer> periodIds, 
+        int sourceId, String key );
 
     /**
      * Gets a map of DataElementOperands and corresponding Double aggregated data

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/crosstab/jdbc/JDBCCrossTabStore.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/crosstab/jdbc/JDBCCrossTabStore.java	2012-08-29 14:39:44 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/crosstab/jdbc/JDBCCrossTabStore.java	2012-11-08 08:33:41 +0000
@@ -31,6 +31,7 @@
 
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
@@ -39,6 +40,7 @@
 import org.amplecode.quick.StatementHolder;
 import org.amplecode.quick.StatementManager;
 import org.hisp.dhis.dataelement.DataElementOperand;
+import org.hisp.dhis.datamart.CrossTabDataValue;
 
 /**
  * @author Lars Helge Overland
@@ -61,20 +63,19 @@
     // CrossTabStore implementation
     // -------------------------------------------------------------------------
 
-    public void createCrossTabTable( List<Integer> organisationUnitIds, String key )
+    public void createCrossTabTable( List<DataElementOperand> operands, String key )
     {
         final StringBuffer sql = new StringBuffer( "CREATE TABLE " + CROSSTAB_TABLE_PREFIX + key + " ( " );
         
-        sql.append( "dataelementid INTEGER NOT NULL, " );
-        sql.append( "categoryoptioncomboid INTEGER NOT NULL, " );
         sql.append( "periodid INTEGER NOT NULL, " );
+        sql.append( "sourceid INTEGER NOT NULL, " );
         
-        for ( Integer id : organisationUnitIds )
+        for ( DataElementOperand operand : operands )
         {
-            sql.append( CrossTabStore.COLUMN_PREFIX + id + " VARCHAR(20), " );
+            sql.append( operand.getColumnName() ).append( " VARCHAR(20), " );
         }
         
-        sql.append( "PRIMARY KEY ( dataelementid, categoryoptioncomboid, periodid ) );" );
+        sql.append( "PRIMARY KEY ( periodid, sourceid ) );" );
         
         statementManager.getHolder().executeUpdate( sql.toString() );
     }
@@ -133,37 +134,58 @@
     // CrossTabDataValue
     // -------------------------------------------------------------------------
 
-    public Map<String, String> getCrossTabDataValues( DataElementOperand operand, 
-        Collection<Integer> periodIds, Collection<Integer> organisationUnitIds, String key )
+    public Collection<CrossTabDataValue> getCrossTabDataValues( Collection<DataElementOperand> operands, 
+        Collection<Integer> periodIds, Collection<Integer> sourceIds, String key )
     {
         final StatementHolder holder = statementManager.getHolder();
         
-        final String sql = 
-            "SELECT * FROM " + CROSSTAB_TABLE_PREFIX + key + " AS c " +
-            "WHERE dataelementid = " + operand.getDataElementId() + " " +
-            "AND categoryoptioncomboid = " + operand.getOptionComboId() + " " +
-            "AND c.periodid IN (" + getCommaDelimitedString( periodIds ) + ")";
+        final String operandIds = getCommadelimitedString( operands );
+        
+        final String sql = "SELECT periodid, sourceid, " + operandIds + " FROM " + CROSSTAB_TABLE_PREFIX + key + " AS c WHERE c.periodid IN (" + 
+            getCommaDelimitedString( periodIds ) + ") AND c.sourceid IN (" + getCommaDelimitedString( sourceIds ) + ")";
         
         try
         {            
             final ResultSet resultSet = holder.getStatement().executeQuery( sql );
             
-            return getCrossTabDataValues( resultSet, organisationUnitIds );
-        }
-        catch ( SQLException ex )
-        {
-            throw new RuntimeException( "Failed to get CrossTabDataValues", ex );
-        }
-        finally
-        {
-            holder.close();
-        }
-    }
-
-    // -------------------------------------------------------------------------
-    // AggregatedDataCacheValue
-    // -------------------------------------------------------------------------
-
+            return getCrossTabDataValues( resultSet, operands );
+        }
+        catch ( SQLException ex )
+        {
+            throw new RuntimeException( "Failed to get CrossTabDataValues", ex );
+        }
+        finally
+        {
+            holder.close();
+        }
+    }
+    
+    public Collection<CrossTabDataValue> getCrossTabDataValues( Collection<DataElementOperand> operands, 
+        Collection<Integer> periodIds, int sourceId, String key )
+    {
+        final StatementHolder holder = statementManager.getHolder();
+
+        final String operandIds = getCommadelimitedString( operands );
+        
+        final String sql = "SELECT periodid, sourceid, " + operandIds + " FROM " + CROSSTAB_TABLE_PREFIX + key + " AS c WHERE c.periodid IN (" + 
+            getCommaDelimitedString( periodIds ) + ") AND c.sourceid = " + sourceId;
+
+        try
+        {
+            final ResultSet resultSet = holder.getStatement().executeQuery( sql );
+            
+            return getCrossTabDataValues( resultSet, operands );
+        }
+        catch ( SQLException ex )
+        {
+            throw new RuntimeException( "Failed to get CrossTabDataValues", ex );
+        }
+        finally
+        {
+            holder.close();
+        }
+    }
+    
     public Map<DataElementOperand, Double> getAggregatedDataCacheValue( Collection<DataElementOperand> operands, 
         int periodId, int sourceId, String key )
     {
@@ -220,24 +242,29 @@
     // Supportive methods
     // -------------------------------------------------------------------------
 
-    private Map<String, String> getCrossTabDataValues( ResultSet resultSet, Collection<Integer> organisationUnitIds )
+    private Collection<CrossTabDataValue> getCrossTabDataValues( ResultSet resultSet, Collection<DataElementOperand> operands )
         throws SQLException
     {
-        final Map<String, String> values = new HashMap<String, String>();
+        final List<CrossTabDataValue> values = new ArrayList<CrossTabDataValue>();
         
         while ( resultSet.next() )
-        {   
-            int periodId = resultSet.getInt( 3 );
-            
-            for ( Integer organisationUnitId : organisationUnitIds )
+        {
+            final CrossTabDataValue value = new CrossTabDataValue();
+            
+            value.setPeriodId( resultSet.getInt( 1 ) );
+            value.setSourceId( resultSet.getInt( 2 ) );
+            
+            for ( DataElementOperand operand : operands )
             {
-                final String columnValue = resultSet.getString( CrossTabStore.COLUMN_PREFIX + organisationUnitId );
+                final String columnValue = resultSet.getString( operand.getColumnName() );
                 
                 if ( columnValue != null )
                 {
-                    values.put( periodId + CrossTabStore.SEPARATOR + organisationUnitId, columnValue );
+                    value.getValueMap().put( operand, columnValue );
                 }
             }
+            
+            values.add( value );
         }
         
         return values;
@@ -263,4 +290,21 @@
         
         return valueMap;
     }
+    
+    private String getCommadelimitedString( Collection<DataElementOperand> operands )
+    {
+        final StringBuilder builder = new StringBuilder();
+        
+        for ( DataElementOperand operand : operands )
+        {
+            builder.append( operand.getColumnName() ).append( "," );
+        }
+        
+        if ( builder.length() > 0 )
+        {
+            builder.deleteCharAt( builder.length() - 1);
+        }
+        
+        return builder.toString();
+    }
 }

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/dataelement/DataElementDataMart.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/dataelement/DataElementDataMart.java	2012-08-29 14:39:44 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/dataelement/DataElementDataMart.java	2012-11-08 08:33:41 +0000
@@ -33,11 +33,11 @@
 import org.amplecode.quick.BatchHandler;
 import org.hisp.dhis.aggregation.AggregatedDataValue;
 import org.hisp.dhis.dataelement.DataElementOperand;
+import org.hisp.dhis.datamart.DataElementOperandList;
 import org.hisp.dhis.organisationunit.OrganisationUnit;
 import org.hisp.dhis.organisationunit.OrganisationUnitGroup;
 import org.hisp.dhis.organisationunit.OrganisationUnitHierarchy;
 import org.hisp.dhis.period.Period;
-import org.hisp.dhis.period.PeriodHierarchy;
 
 /**
  * @author Lars Helge Overland
@@ -46,5 +46,5 @@
 {
     Future<?> exportDataValues( Collection<DataElementOperand> operands, Collection<Period> periods, 
         Collection<OrganisationUnit> organisationUnits, Collection<OrganisationUnitGroup> organisationUnitGroups, 
-        PeriodHierarchy periodHierarchy, OrganisationUnitHierarchy hierarchy, Class<? extends BatchHandler<AggregatedDataValue>> clazz, String key );
+        DataElementOperandList operandList, OrganisationUnitHierarchy hierarchy, Class<? extends BatchHandler<AggregatedDataValue>> clazz, String key );
 }

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/dataelement/DefaultDataElementDataMart.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/dataelement/DefaultDataElementDataMart.java	2012-08-29 14:44:51 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/dataelement/DefaultDataElementDataMart.java	2012-11-08 08:33:41 +0000
@@ -27,13 +27,14 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+import static org.hisp.dhis.datamart.crosstab.jdbc.CrossTabStore.AGGREGATEDDATA_CACHE_PREFIX;
+import static org.hisp.dhis.datamart.crosstab.jdbc.CrossTabStore.AGGREGATEDORGUNITDATA_CACHE_PREFIX;
 import static org.hisp.dhis.system.util.MathUtils.getRounded;
 
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
-import java.util.Set;
+import java.util.Map.Entry;
 import java.util.concurrent.Future;
 
 import org.amplecode.quick.BatchHandler;
@@ -43,16 +44,16 @@
 import org.apache.commons.logging.LogFactory;
 import org.hisp.dhis.aggregation.AggregatedDataValue;
 import org.hisp.dhis.dataelement.DataElementOperand;
-import org.hisp.dhis.datamart.DataMartEngine;
-import org.hisp.dhis.datamart.OrgUnitOperand;
+import org.hisp.dhis.datamart.DataElementOperandList;
 import org.hisp.dhis.datamart.aggregation.cache.AggregationCache;
 import org.hisp.dhis.datamart.aggregation.dataelement.DataElementAggregator;
+import org.hisp.dhis.datamart.DataMartEngine;
+import org.hisp.dhis.jdbc.batchhandler.GenericBatchHandler;
 import org.hisp.dhis.organisationunit.OrganisationUnit;
 import org.hisp.dhis.organisationunit.OrganisationUnitGroup;
 import org.hisp.dhis.organisationunit.OrganisationUnitHierarchy;
 import org.hisp.dhis.period.Period;
-import org.hisp.dhis.period.PeriodHierarchy;
-import org.hisp.dhis.system.util.ConversionUtils;
+import org.hisp.dhis.system.util.SystemUtils;
 import org.springframework.scheduling.annotation.Async;
 
 /**
@@ -76,6 +77,13 @@
         this.batchHandlerFactory = batchHandlerFactory;
     }
     
+    private BatchHandlerFactory inMemoryBatchHandlerFactory;
+        
+    public void setInMemoryBatchHandlerFactory( BatchHandlerFactory inMemoryBatchHandlerFactory )
+    {
+        this.inMemoryBatchHandlerFactory = inMemoryBatchHandlerFactory;
+    }
+
     private AggregationCache aggregationCache;
 
     public void setAggregationCache( AggregationCache aggregationCache )
@@ -83,6 +91,41 @@
         this.aggregationCache = aggregationCache;
     }
 
+    private DataElementAggregator sumIntAggregator;
+
+    public void setSumIntAggregator( DataElementAggregator sumIntDataElementAggregator )
+    {
+        this.sumIntAggregator = sumIntDataElementAggregator;
+    }
+
+    private DataElementAggregator averageIntAggregator;
+
+    public void setAverageIntAggregator( DataElementAggregator averageIntDataElementAggregator )
+    {
+        this.averageIntAggregator = averageIntDataElementAggregator;
+    }
+
+    private DataElementAggregator averageIntSingleValueAggregator;
+
+    public void setAverageIntSingleValueAggregator( DataElementAggregator averageIntSingleValueAggregator )
+    {
+        this.averageIntSingleValueAggregator = averageIntSingleValueAggregator;
+    }
+
+    private DataElementAggregator sumBoolAggregator;
+
+    public void setSumBoolAggregator( DataElementAggregator sumBooleanDataElementAggregator )
+    {
+        this.sumBoolAggregator = sumBooleanDataElementAggregator;
+    }
+
+    private DataElementAggregator averageBoolAggregator;
+
+    public void setAverageBoolAggregator( DataElementAggregator averageBooleanDataElementAggregator )
+    {
+        this.averageBoolAggregator = averageBooleanDataElementAggregator;
+    }
+    
     private StatementManager statementManager;
 
     public void setStatementManager( StatementManager statementManager )
@@ -90,13 +133,6 @@
         this.statementManager = statementManager;
     }
 
-    private Set<DataElementAggregator> aggregators;
-
-    public void setAggregators( Set<DataElementAggregator> aggregators )
-    {
-        this.aggregators = aggregators;
-    }
-    
     // -------------------------------------------------------------------------
     // DataMart functionality
     // -------------------------------------------------------------------------
@@ -104,56 +140,84 @@
     @Async
     public Future<?> exportDataValues( Collection<DataElementOperand> operands, Collection<Period> periods, 
         Collection<OrganisationUnit> organisationUnits, Collection<OrganisationUnitGroup> organisationUnitGroups, 
-        PeriodHierarchy periodHierarchy, OrganisationUnitHierarchy orgUnitHierarchy, Class<? extends BatchHandler<AggregatedDataValue>> clazz, String key )
+        DataElementOperandList operandList, OrganisationUnitHierarchy hierarchy, Class<? extends BatchHandler<AggregatedDataValue>> clazz, String key )
     {
         statementManager.initialise(); // Running in separate thread
         
         final BatchHandler<AggregatedDataValue> batchHandler = batchHandlerFactory.createBatchHandler( clazz ).init();
         
+        final String tableName = organisationUnitGroups != null ? AGGREGATEDORGUNITDATA_CACHE_PREFIX : AGGREGATEDDATA_CACHE_PREFIX;
+        
+        final BatchHandler<Object> cacheHandler = inMemoryBatchHandlerFactory.createBatchHandler( GenericBatchHandler.class ).setTableName( tableName + key ).init();
+        
+        final Map<DataElementOperand, Double> valueMap = new HashMap<DataElementOperand, Double>();
+        
         final AggregatedDataValue aggregatedValue = new AggregatedDataValue();
         
-        final Collection<Integer> organisationUnitIds = ConversionUtils.getIdentifiers( OrganisationUnit.class, organisationUnits );
-
-        final Map<DataElementOperand, DataElementAggregator> operandAggregatorMap = getAggregatorMap( operands );
-        
-        populateHasAggregationLevels( operands );
-        
         organisationUnitGroups = organisationUnitGroups != null ? organisationUnitGroups : DataMartEngine.DUMMY_ORG_UNIT_GROUPS;
         
-        for ( final DataElementOperand dataElementOperand : operands )
+        for ( final Period period : periods )
         {
-            final DataElementAggregator aggregator = operandAggregatorMap.get( dataElementOperand );
-
-            if ( aggregator == null )
-            {
-                continue;
-            }
-            
-            final List<OrgUnitOperand> values = aggregator.getAggregatedValues( dataElementOperand, periods, organisationUnitIds, organisationUnitGroups, periodHierarchy, orgUnitHierarchy, key );
-            
-            for ( OrgUnitOperand orgUnitOperand : values )
-            {
-                final int level = aggregationCache.getLevelOfOrganisationUnit( orgUnitOperand.getOrgUnitId() );
-                
-                final double value = getRounded( orgUnitOperand.getValue(), DECIMALS );
-
-                aggregatedValue.clear();
-                
-                aggregatedValue.setDataElementId( dataElementOperand.getDataElementId() );
-                aggregatedValue.setCategoryOptionComboId( dataElementOperand.getOptionComboId() );
-                aggregatedValue.setPeriodId( orgUnitOperand.getPeriodId() );
-                aggregatedValue.setPeriodTypeId( orgUnitOperand.getPeriodTypeId() );
-                aggregatedValue.setOrganisationUnitId( orgUnitOperand.getOrgUnitId() );
-                aggregatedValue.setOrganisationUnitGroupId( orgUnitOperand.getOrgUnitGroupId() );
-                aggregatedValue.setLevel( level );
-                aggregatedValue.setValue( value );
-                
-                batchHandler.addObject( aggregatedValue );
-            }
+            final Collection<DataElementOperand> sumIntOperands = sumIntAggregator.filterOperands( operands, period.getPeriodType() );
+            final Collection<DataElementOperand> averageIntOperands = averageIntAggregator.filterOperands( operands, period.getPeriodType() );
+            final Collection<DataElementOperand> averageIntSingleValueOperands = averageIntSingleValueAggregator.filterOperands( operands, period.getPeriodType() );
+            final Collection<DataElementOperand> sumBoolOperands = sumBoolAggregator.filterOperands( operands, period.getPeriodType() );
+            final Collection<DataElementOperand> averageBoolOperands = averageBoolAggregator.filterOperands( operands, period.getPeriodType() );
+            
+            for ( OrganisationUnitGroup group : organisationUnitGroups )
+            {
+                for ( final OrganisationUnit unit : organisationUnits )
+                {
+                    operandList.init( period, unit, group );
+                    
+                    final int level = aggregationCache.getLevelOfOrganisationUnit( unit.getId() );
+                    
+                    final Collection<Integer> orgUnitChildren = hierarchy.getChildren( unit.getId(), group );
+                    
+                    valueMap.clear();                
+                    valueMap.putAll( sumIntAggregator.getAggregatedValues( sumIntOperands, period, level, orgUnitChildren, key ) );
+                    valueMap.putAll( averageIntAggregator.getAggregatedValues( averageIntOperands, period, level, orgUnitChildren, key ) );
+                    valueMap.putAll( averageIntSingleValueAggregator.getAggregatedValues( averageIntSingleValueOperands, period, level, orgUnitChildren, key ) );
+                    valueMap.putAll( sumBoolAggregator.getAggregatedValues( sumBoolOperands, period, level, orgUnitChildren, key ) );
+                    valueMap.putAll( averageBoolAggregator.getAggregatedValues( averageBoolOperands, period, level, orgUnitChildren, key ) );
+                    
+                    if ( valueMap.size() > 0 )
+                    {
+                        for ( Entry<DataElementOperand, Double> entry : valueMap.entrySet() )
+                        {
+                            aggregatedValue.clear();
+                            
+                            final double value = getRounded( entry.getValue(), DECIMALS );
+                            
+                            aggregatedValue.setDataElementId( entry.getKey().getDataElementId() );
+                            aggregatedValue.setCategoryOptionComboId( entry.getKey().getOptionComboId() );
+                            aggregatedValue.setPeriodId( period.getId() );
+                            aggregatedValue.setPeriodTypeId( period.getPeriodType().getId() );
+                            aggregatedValue.setOrganisationUnitId( unit.getId() );
+                            aggregatedValue.setOrganisationUnitGroupId( group != null ? group.getId() : 0 );
+                            aggregatedValue.setLevel( level );
+                            aggregatedValue.setValue( value );
+                            
+                            batchHandler.addObject( aggregatedValue );
+                            
+                            operandList.addValue( entry.getKey(), value );
+                        }
+                    }
+                    
+                    if ( operandList.hasValues() )
+                    {
+                        cacheHandler.addObject( operandList.getList() );
+                    }
+                }
+            }
+            
+            log.debug( "Exported data values for period: " + period + ", " + SystemUtils.getMemoryString() );
         }
         
         batchHandler.flush();
         
+        cacheHandler.flush();
+
         statementManager.destroy();
         
         aggregationCache.clearCache();
@@ -162,35 +226,4 @@
         
         return null;
     }
-
-    // -------------------------------------------------------------------------
-    // Supportive methods
-    // -------------------------------------------------------------------------
-
-    private Map<DataElementOperand, DataElementAggregator> getAggregatorMap( Collection<DataElementOperand> operands )
-    {
-        Map<DataElementOperand, DataElementAggregator> map = new HashMap<DataElementOperand, DataElementAggregator>();
-        
-        operand : for ( DataElementOperand operand : operands )
-        {
-            for ( DataElementAggregator aggregator : aggregators )
-            {
-                if ( aggregator.isApplicable( operand ) )
-                {
-                    map.put( operand, aggregator );
-                    continue operand;
-                }
-            }
-        }
-        
-        return map;
-    }
-    
-    private void populateHasAggregationLevels( Collection<DataElementOperand> operands ) //TODO check effect
-    {
-        for ( DataElementOperand operand : operands )
-        {
-            operand.setHasAggregationLevels( operand.hasAggregationLevels() );
-        }
-    }
 }

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/engine/DefaultDataMartEngine.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/engine/DefaultDataMartEngine.java	2012-11-07 08:12:06 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/engine/DefaultDataMartEngine.java	2012-11-08 08:33:41 +0000
@@ -44,6 +44,7 @@
 import org.hisp.dhis.dataelement.DataElementCategoryService;
 import org.hisp.dhis.dataelement.DataElementOperand;
 import org.hisp.dhis.dataelement.DataElementService;
+import org.hisp.dhis.datamart.DataElementOperandList;
 import org.hisp.dhis.datamart.DataMartEngine;
 import org.hisp.dhis.datamart.DataMartManager;
 import org.hisp.dhis.datamart.crosstab.CrossTabService;
@@ -62,7 +63,6 @@
 import org.hisp.dhis.organisationunit.OrganisationUnitHierarchy;
 import org.hisp.dhis.organisationunit.OrganisationUnitService;
 import org.hisp.dhis.period.Period;
-import org.hisp.dhis.period.PeriodHierarchy;
 import org.hisp.dhis.period.PeriodService;
 import org.hisp.dhis.scheduling.TaskId;
 import org.hisp.dhis.setting.SystemSettingManager;
@@ -239,6 +239,7 @@
         // ---------------------------------------------------------------------
 
         organisationUnitService.filterOrganisationUnitsWithoutData( organisationUnits );
+        Collections.shuffle( organisationUnits );
         FilterUtils.filter( dataElements, new AggregatableDataElementFilter() );
         FilterUtils.filter( dataElements, new DataElementWithAggregationFilter() );
         expressionService.filterInvalidIndicators( indicators );
@@ -291,136 +292,203 @@
         indicatorOperands.retainAll( allOperands );
         
         clock.logTime( "Number of operands with data: " + allOperands.size() + ", " + SystemUtils.getMemoryString() );
-        notifier.notify( id, DATAMART, "Populating crosstab table" );
+        notifier.notify( id, DATAMART, "Populating crosstabulation table" );
 
         // ---------------------------------------------------------------------
         // Create crosstabtable
         // ---------------------------------------------------------------------
 
         final Collection<Integer> intersectingPeriodIds = ConversionUtils.getIdentifiers( Period.class, periodService.getIntersectionPeriods( periods ) );
-        final PeriodHierarchy periodHierarchy = periodService.getPeriodHierarchy( periods );
-        final Set<Integer> orgUnitChildrenIds = organisationUnitService.getOrganisationUnitHierarchy().getChildren( organisationUnitIds );
-        final List<Integer> crossTabOrgUnitIds = new ArrayList<Integer>( orgUnitChildrenIds );
-        
-        final String key = crossTabService.createCrossTabTable( crossTabOrgUnitIds );
+        final Set<Integer> childrenIds = organisationUnitService.getOrganisationUnitHierarchy().getChildren( organisationUnitIds );
+        final List<List<Integer>> childrenPages = new PaginatedList<Integer>( childrenIds ).setNumberOfPages( cpuCores ).getPages();
 
-        final List<DataElementOperand> operandList = new ArrayList<DataElementOperand>( allOperands );
-        Collections.shuffle( operandList );
-        
-        final List<List<DataElementOperand>> operandPages = new PaginatedList<DataElementOperand>( operandList ).setNumberOfPages( cpuCores ).getPages();
+        final List<DataElementOperand> crossTabOperands = new ArrayList<DataElementOperand>( allOperands );
+        final String key = crossTabService.createCrossTabTable( crossTabOperands );
         
         List<Future<?>> crossTabFutures = new ArrayList<Future<?>>();
         
-        for ( List<DataElementOperand> operandPage : operandPages )
+        for ( List<Integer> childrenPage : childrenPages )
         {
-            crossTabFutures.add( crossTabService.populateCrossTabTable( operandPage, intersectingPeriodIds, crossTabOrgUnitIds, key ) );
+            crossTabFutures.add( crossTabService.populateCrossTabTable( crossTabOperands, intersectingPeriodIds, childrenPage, key ) );
         }
 
         ConcurrentUtils.waitForCompletion( crossTabFutures );
         
         clock.logTime( "Populated crosstab table, " + SystemUtils.getMemoryString() );
-        notifier.notify( id, DATAMART, "Exporting data element data" );
 
-        final boolean isDataElements = true;
-        
         final boolean isIndicators = indicators != null && indicators.size() > 0;
-
+        
+        // ---------------------------------------------------------------------
+        // 1. Create aggregated data cache
+        // ---------------------------------------------------------------------
+
+        crossTabService.createAggregatedDataCache( indicatorOperands, key );
+        
+        clock.logTime( "Created aggregated data cache, number of indicator operands: " + indicatorOperands.size() + ", operands with data: " + allOperands.size() );
+        notifier.notify( id, DATAMART, "Exporting data for data element data" );
+        
+        // ---------------------------------------------------------------------
+        // 2. Export data element values
+        // ---------------------------------------------------------------------
+
+        List<List<OrganisationUnit>> organisationUnitPages = new PaginatedList<OrganisationUnit>( organisationUnits ).setNumberOfPages( cpuCores ).getPages();
+
+        if ( allOperands.size() > 0 )
+        {
+            final OrganisationUnitHierarchy hierarchy = organisationUnitService.getOrganisationUnitHierarchy().prepareChildren( organisationUnits );
+
+            List<Future<?>> futures = new ArrayList<Future<?>>();
+            
+            for ( List<OrganisationUnit> organisationUnitPage : organisationUnitPages )
+            {
+                futures.add( dataElementDataMart.exportDataValues( allOperands, periods, organisationUnitPage, 
+                    null, new DataElementOperandList( indicatorOperands ), hierarchy, AggregatedDataValueTempBatchHandler.class, key ) );
+            }
+
+            ConcurrentUtils.waitForCompletion( futures );
+        }
+        
+        clock.logTime( "Exported values for data element operands (" + allOperands.size() + "), pages: " + organisationUnitPages.size() + ", " + SystemUtils.getMemoryString() );
+        notifier.notify( id, DATAMART, "Dropping data element index" );
+
+        // ---------------------------------------------------------------------
+        // 3. Drop data element index
+        // ---------------------------------------------------------------------
+
+        dataMartManager.dropDataValueIndex();
+        
+        clock.logTime( "Dropped data element index" );
+        notifier.notify( id, DATAMART, "Deleting existing data element data" );
+        
+        // ---------------------------------------------------------------------
+        // 4. Delete existing aggregated data values
+        // ---------------------------------------------------------------------
+
+        dataMartManager.deleteAggregatedDataValues( periodIds );
+        
+        clock.logTime( "Deleted existing data element data" );
+        notifier.notify( id, DATAMART, "Copying data element data from temporary table" );
+
+        // ---------------------------------------------------------------------
+        // 5. Copy aggregated data values from temporary table
+        // ---------------------------------------------------------------------
+
+        dataMartManager.copyAggregatedDataValuesFromTemp();
+        
+        clock.logTime( "Copied data element data from temporary table" );
+        notifier.notify( id, DATAMART, "Creating data element index" );
+
+        // ---------------------------------------------------------------------
+        // 6. Create data element index
+        // ---------------------------------------------------------------------
+
+        dataMartManager.createDataValueIndex();
+
+        clock.logTime( "Created data element index" );
+        notifier.notify( id, DATAMART, "Exporting data for indicator data" );
+        
+        // ---------------------------------------------------------------------
+        // 7. Export indicator values
+        // ---------------------------------------------------------------------
+
+        if ( isIndicators )
+        {
+            List<Future<?>> futures = new ArrayList<Future<?>>();
+
+            for ( List<OrganisationUnit> organisationUnitPage : organisationUnitPages )
+            {
+                futures.add( indicatorDataMart.exportIndicatorValues( indicators, periods, organisationUnitPage,
+                    null, indicatorOperands, AggregatedIndicatorValueTempBatchHandler.class, key ) );
+            }
+
+            ConcurrentUtils.waitForCompletion( futures );
+        }
+        
+        clock.logTime( "Exported values for indicators (" + indicators.size() + "), pages: " + organisationUnitPages.size() + ", " + SystemUtils.getMemoryString() );
+        notifier.notify( id, DATAMART, "Dropping indicator index" );
+        
+        // ---------------------------------------------------------------------
+        // 8. Drop aggregated data cache and indicator index
+        // ---------------------------------------------------------------------
+
+        crossTabService.dropAggregatedDataCache( key );
+        dataMartManager.dropIndicatorValueIndex();
+
+        clock.logTime( "Dropped indicator index, " + SystemUtils.getMemoryString() );
+        notifier.notify( id, DATAMART, "Deleting existing indicator data" );
+
+        // ---------------------------------------------------------------------
+        // 9. Delete existing aggregated indicator values
+        // ---------------------------------------------------------------------
+
+        dataMartManager.deleteAggregatedIndicatorValues( periodIds );
+        
+        clock.logTime( "Deleted existing indicator data" );
+        notifier.notify( id, DATAMART, "Copying indicator data from temporary table" );
+
+        // ---------------------------------------------------------------------
+        // 10. Copy aggregated data values from temporary table
+        // ---------------------------------------------------------------------
+
+        dataMartManager.copyAggregatedIndicatorValuesFromTemp();
+        
+        clock.logTime( "Copied indicator data from temporary table" );
+        notifier.notify( id, DATAMART, "Creating indicator index" );
+        
+        // ---------------------------------------------------------------------
+        // 11. Create indicator index
+        // ---------------------------------------------------------------------
+        
+        dataMartManager.createIndicatorValueIndex();
+        
+        clock.logTime( "Created indicator index" );        
+        clock.logTime( "Aggregated data export done" );
+        
+        final boolean isGroups = organisationUnitGroups != null && organisationUnitGroups.size() > 0;
+        
         final int groupLevel = (Integer) systemSettingManager.getSystemSetting( KEY_ORGUNITGROUPSET_AGG_LEVEL, DEFAULT_ORGUNITGROUPSET_AGG_LEVEL );
         
-        final boolean isGroups = organisationUnitGroups != null && organisationUnitGroups.size() > 0 && groupLevel > 0;
-
-        if ( isDataElements )
-        {
-            // -----------------------------------------------------------------
-            // 1. Export data element values
-            // -----------------------------------------------------------------
-            
-            if ( operandList.size() > 0 )
-            {
-                final OrganisationUnitHierarchy orgUnitHierarchy = organisationUnitService.getOrganisationUnitHierarchy().prepareChildren( organisationUnits );
-                
-                List<Future<?>> futures = new ArrayList<Future<?>>();
-                
-                for ( List<DataElementOperand> operandPage : operandPages )
-                {
-                    futures.add( dataElementDataMart.exportDataValues( operandPage, periods, organisationUnits, 
-                        null, periodHierarchy, orgUnitHierarchy, AggregatedDataValueTempBatchHandler.class, key ) );
-                }
-                
-                ConcurrentUtils.waitForCompletion( futures );
-            }
-            
-            clock.logTime( "Exported values for data element operands (" + operandList.size() + "), pages: " + operandPages.size() + ", " + SystemUtils.getMemoryString() );
-            notifier.notify( id, DATAMART, "Dropping data element index" );
-
-            // -----------------------------------------------------------------
-            // 2. Drop data element index
-            // -----------------------------------------------------------------
-    
-            dataMartManager.dropDataValueIndex();
-            
-            clock.logTime( "Dropped data element index" );
-            notifier.notify( id, DATAMART, "Deleting existing data element data" );
-            
-            // -----------------------------------------------------------------
-            // 3. Delete existing aggregated data values
-            // -----------------------------------------------------------------
-    
-            dataMartManager.deleteAggregatedDataValues( periodIds );
-            
-            clock.logTime( "Deleted existing data element data" );
-            notifier.notify( id, DATAMART, "Copying data element data from temporary table" );
-    
-            // -----------------------------------------------------------------
-            // 4. Copy aggregated data values from temporary table
-            // -----------------------------------------------------------------
-    
-            dataMartManager.copyAggregatedDataValuesFromTemp();
-            
-            clock.logTime( "Copied data element data from temporary table" );
-            notifier.notify( id, DATAMART, "Creating data element index" );
-    
-            // -----------------------------------------------------------------
-            // 5. Create data element index
-            // -----------------------------------------------------------------
-    
-            dataMartManager.createDataValueIndex();
-    
-            clock.logTime( "Created data element index" );
-        }
-
-        if ( isGroups && isDataElements )
-        {
-            // -----------------------------------------------------------------
-            // 1. Export data element values
-            // -----------------------------------------------------------------
-
-            notifier.notify( id, DATAMART, "Exporting data element org unit data" );
-            
+        if ( isGroups && groupLevel > 0 )
+        {
+            // -----------------------------------------------------------------
+            // 1. Create aggregated data cache
+            // -----------------------------------------------------------------
+            
+            crossTabService.createAggregatedOrgUnitDataCache( indicatorOperands, key );
+            
+            clock.logTime( "Created aggregated org unit data cache" );
+            notifier.notify( id, DATAMART, "Exporting org unit data element data" );
+            
+            // ---------------------------------------------------------------------
+            // 2. Export data element values
+            // ---------------------------------------------------------------------
+
             Collection<OrganisationUnit> groupOrganisationUnits = new HashSet<OrganisationUnit>( organisationUnits );
             
             FilterUtils.filter( groupOrganisationUnits, new OrganisationUnitAboveOrEqualToLevelFilter( groupLevel ) );
             
-            if ( operandList.size() > 0 )
+            organisationUnitPages = new PaginatedList<OrganisationUnit>( groupOrganisationUnits ).setNumberOfPages( cpuCores ).getPages();
+            
+            if ( allOperands.size() > 0 )
             {
-                final OrganisationUnitHierarchy orgUnitHierarchy = organisationUnitService.getOrganisationUnitHierarchy().prepareChildren( groupOrganisationUnits, organisationUnitGroups );
+                final OrganisationUnitHierarchy hierarchy = organisationUnitService.getOrganisationUnitHierarchy().prepareChildren( organisationUnits, organisationUnitGroups );
                 
                 List<Future<?>> futures = new ArrayList<Future<?>>();
                 
-                for ( List<DataElementOperand> operandPage : operandPages )
+                for ( List<OrganisationUnit> organisationUnitPage : organisationUnitPages )
                 {
-                    futures.add( dataElementDataMart.exportDataValues( operandPage, periods, groupOrganisationUnits, 
-                        organisationUnitGroups, periodHierarchy, orgUnitHierarchy, AggregatedOrgUnitDataValueTempBatchHandler.class, key ) );
+                    futures.add( dataElementDataMart.exportDataValues( allOperands, periods, organisationUnitPage, 
+                        organisationUnitGroups, new DataElementOperandList( indicatorOperands ), hierarchy, AggregatedOrgUnitDataValueTempBatchHandler.class, key ) );
                 }
 
                 ConcurrentUtils.waitForCompletion( futures );
             }
             
-            clock.logTime( "Exported values for data element operands (" + operandList.size() + "), pages: " + operandPages.size()  + ", " + SystemUtils.getMemoryString() );
+            clock.logTime( "Exported values for data element operands (" + allOperands.size() + "), pages: " + organisationUnitPages.size()  + ", " + SystemUtils.getMemoryString() );
             notifier.notify( id, DATAMART, "Dropping data element data indexes" );
 
             // -----------------------------------------------------------------
-            // 2. Drop data element index
+            // 3. Drop data element index
             // -----------------------------------------------------------------
 
             dataMartManager.dropOrgUnitDataValueIndex();
@@ -428,158 +496,56 @@
             clock.logTime( "Dropped org unit data element index" );
             notifier.notify( id, DATAMART, "Deleting existing org unit data element data" );
 
-            // -----------------------------------------------------------------
-            // 3. Delete existing aggregated data values
-            // -----------------------------------------------------------------
+            // ---------------------------------------------------------------------
+            // 4. Delete existing aggregated data values
+            // ---------------------------------------------------------------------
 
             dataMartManager.deleteAggregatedOrgUnitDataValues( periodIds );
             
             clock.logTime( "Deleted existing aggregated org unit datavalues" );
             notifier.notify( id, DATAMART, "Copying org unit data element data" );
 
-            // -----------------------------------------------------------------
-            // 4. Copy aggregated org unit data values from temporary table
-            // -----------------------------------------------------------------
+            // ---------------------------------------------------------------------
+            // 5. Copy aggregated org unit data values from temporary table
+            // ---------------------------------------------------------------------
 
             dataMartManager.copyAggregatedOrgUnitDataValuesFromTemp();
             
             clock.logTime( "Copied org unit data element data from temporary table" );
             notifier.notify( id, DATAMART, "Creating org unit data element index" );
 
-            // -----------------------------------------------------------------
-            // 5. Create org unit data element index
-            // -----------------------------------------------------------------
+            // ---------------------------------------------------------------------
+            // 6. Create org unit data element index
+            // ---------------------------------------------------------------------
 
             dataMartManager.createOrgUnitDataValueIndex();
 
             clock.logTime( "Created org unit data element index" );
             notifier.notify( id, DATAMART, "Exporting data for org unit indicator data" );
-        }
-
-        crossTabService.dropCrossTabTable( key );
-        
-        List<List<OrganisationUnit>> organisationUnitPages = new PaginatedList<OrganisationUnit>( organisationUnits ).setNumberOfPages( cpuCores ).getPages();
-        
-        if ( isIndicators )
-        {
-            // -----------------------------------------------------------------
-            // 1. Create and populate aggregated data cache
-            // -----------------------------------------------------------------
-
-            notifier.notify( id, DATAMART, "Populating aggregated data cache" );
-            
-            crossTabService.createAggregatedDataCache( indicatorOperands, key );            
-
-            List<Future<?>> aggregatedDataCacheFutures = new ArrayList<Future<?>>();
-
-            for ( List<OrganisationUnit> organisationUnitPage : organisationUnitPages )
-            {
-                aggregatedDataCacheFutures.add( crossTabService.populateAggregatedDataCache( 
-                    indicatorOperands, periods, organisationUnitPage, key ) );
-            }
-
-            ConcurrentUtils.waitForCompletion( aggregatedDataCacheFutures );
-        
-            clock.logTime( "Created aggregated data cache, number of indicator operands: " + indicatorOperands.size() );
-            notifier.notify( id, DATAMART, "Exporting indicator data" );
-            
-            // -----------------------------------------------------------------
-            // 2. Export indicator values
-            // -----------------------------------------------------------------
-
-            List<Future<?>> futures = new ArrayList<Future<?>>();
-
-            for ( List<OrganisationUnit> organisationUnitPage : organisationUnitPages )
-            {
-                futures.add( indicatorDataMart.exportIndicatorValues( indicators, periods, organisationUnitPage,
-                    null, indicatorOperands, AggregatedIndicatorValueTempBatchHandler.class, key ) );
-            }
-
-            ConcurrentUtils.waitForCompletion( futures );
-        
-            clock.logTime( "Exported values for indicators (" + indicators.size() + "), pages: " + organisationUnitPages.size() + ", " + SystemUtils.getMemoryString() );
-            notifier.notify( id, DATAMART, "Dropping indicator index" );
-            
-            // -----------------------------------------------------------------
-            // 3. Drop aggregated data cache and indicator index
-            // -----------------------------------------------------------------
-    
-            crossTabService.dropAggregatedDataCache( key );
-            dataMartManager.dropIndicatorValueIndex();
-    
-            clock.logTime( "Dropped indicator index, " + SystemUtils.getMemoryString() );
-            notifier.notify( id, DATAMART, "Deleting existing indicator data" );
-    
-            // -----------------------------------------------------------------
-            // 4. Delete existing aggregated indicator values
-            // -----------------------------------------------------------------
-    
-            dataMartManager.deleteAggregatedIndicatorValues( periodIds );
-            
-            clock.logTime( "Deleted existing indicator data" );
-            notifier.notify( id, DATAMART, "Copying indicator data from temporary table" );
-    
-            // -----------------------------------------------------------------
-            // 5. Copy aggregated data values from temporary table
-            // -----------------------------------------------------------------
-    
-            dataMartManager.copyAggregatedIndicatorValuesFromTemp();
-            
-            clock.logTime( "Copied indicator data from temporary table" );
-            notifier.notify( id, DATAMART, "Creating indicator index" );
-            
-            // -----------------------------------------------------------------
-            // 6. Create indicator index
-            // -----------------------------------------------------------------
-            
-            dataMartManager.createIndicatorValueIndex();
-            
-            clock.logTime( "Created indicator index" );
-        }
-
-        if ( isGroups && isIndicators )
-        {
-            // -----------------------------------------------------------------
-            // 1. Create aggregated data cache
-            // -----------------------------------------------------------------
-
-            notifier.notify( id, DATAMART, "Populating aggregated data cache" );
-            
-            crossTabService.createAggregatedOrgUnitDataCache( indicatorOperands, key );
-
-            List<Future<?>> aggregatedDataCacheFutures = new ArrayList<Future<?>>();
-
-            for ( List<OrganisationUnit> organisationUnitPage : organisationUnitPages )
-            {
-                aggregatedDataCacheFutures.add( crossTabService.populateAggregatedOrgUnitDataCache( 
-                    indicatorOperands, periods, organisationUnitPage, organisationUnitGroups, key ) );
-            }
-
-            ConcurrentUtils.waitForCompletion( aggregatedDataCacheFutures );
-        
-            clock.logTime( "Created aggregated org unit data cache" );
-            notifier.notify( id, DATAMART, "Exporting org unit indicator data" );
-
-            // -----------------------------------------------------------------
-            // 2. Export indicator values
-            // -----------------------------------------------------------------
-
-            List<Future<?>> futures = new ArrayList<Future<?>>();
-
-            for ( List<OrganisationUnit> organisationUnitPage : organisationUnitPages )
-            {
-                futures.add( indicatorDataMart.exportIndicatorValues( indicators, periods, organisationUnitPage,
-                    organisationUnitGroups, indicatorOperands, AggregatedOrgUnitIndicatorValueTempBatchHandler.class, key ) );
-            }
-
-            ConcurrentUtils.waitForCompletion( futures );
-        
+            
+            // ---------------------------------------------------------------------
+            // 7. Export indicator values
+            // ---------------------------------------------------------------------
+
+            if ( isIndicators )
+            {
+                List<Future<?>> futures = new ArrayList<Future<?>>();
+
+                for ( List<OrganisationUnit> organisationUnitPage : organisationUnitPages )
+                {
+                    futures.add( indicatorDataMart.exportIndicatorValues( indicators, periods, organisationUnitPage,
+                        organisationUnitGroups, indicatorOperands, AggregatedOrgUnitIndicatorValueTempBatchHandler.class, key ) );
+                }
+
+                ConcurrentUtils.waitForCompletion( futures );
+            }
+            
             clock.logTime( "Exported values for indicators (" + indicators.size() + "), pages: " + organisationUnitPages.size() + ", " + SystemUtils.getMemoryString() );
             notifier.notify( id, DATAMART, "Dropping org unit indicator index" );
 
-            // -----------------------------------------------------------------
-            // 3. Drop aggregated data cache and indicator index
-            // -----------------------------------------------------------------
+            // ---------------------------------------------------------------------
+            // 8. Drop aggregated data cache and indicator index
+            // ---------------------------------------------------------------------
 
             crossTabService.dropAggregatedOrgUnitDataCache( key );
             dataMartManager.dropOrgUnitIndicatorValueIndex();
@@ -587,37 +553,39 @@
             clock.logTime( "Dropped org unit indicator index, " + SystemUtils.getMemoryString() );
             notifier.notify( id, DATAMART, "Deleting existing org unit indicator data" );
 
-            // -----------------------------------------------------------------
-            // 4. Delete existing aggregated indicator values
-            // -----------------------------------------------------------------
+            // ---------------------------------------------------------------------
+            // 9. Delete existing aggregated indicator values
+            // ---------------------------------------------------------------------
 
             dataMartManager.deleteAggregatedOrgUnitIndicatorValues( periodIds );
             
             clock.logTime( "Deleted existing aggregated org unit indicatorvalues" );
             notifier.notify( id, DATAMART, "Copying org unit indicator data from temporary table" );
 
-            // -----------------------------------------------------------------
-            // 5. Copy aggregated org unit indicator values from temp table
-            // -----------------------------------------------------------------
+            // ---------------------------------------------------------------------
+            // 10. Copy aggregated org unit indicator values from temporary table
+            // ---------------------------------------------------------------------
 
             dataMartManager.copyAggregatedOrgUnitIndicatorValuesFromTemp();
             
             clock.logTime( "Copied org unit indicator data from temporary table" );
             notifier.notify( id, DATAMART, "Creating org unit indicator indexes" );
             
-            // -----------------------------------------------------------------
-            // 6. Create org unit indicator index
-            // -----------------------------------------------------------------
+            // ---------------------------------------------------------------------
+            // 11. Create org unit indicator index
+            // ---------------------------------------------------------------------
 
             dataMartManager.createOrgUnitIndicatorValueIndex();
             
             clock.logTime( "Created org unit indicator index" );
+            clock.logTime( "Aggregated org unit data export done" );            
         }
-        
-        // ---------------------------------------------------------------------
-        // Drop temporary tables
-        // ---------------------------------------------------------------------
-
+
+        // ---------------------------------------------------------------------
+        // Drop crosstab and temporary tables
+        // ---------------------------------------------------------------------
+
+        crossTabService.dropCrossTabTable( key );
         dataMartManager.dropTempAggregatedTables();
         
         clock.logTime( "Dropped crosstab table" );

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/indicator/DefaultIndicatorDataMart.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/indicator/DefaultIndicatorDataMart.java	2012-08-29 15:13:58 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/indicator/DefaultIndicatorDataMart.java	2012-11-08 08:33:41 +0000
@@ -133,7 +133,7 @@
         Collection<OrganisationUnit> organisationUnits, Collection<OrganisationUnitGroup> organisationUnitGroups,
         Collection<DataElementOperand> operands, Class<? extends BatchHandler<AggregatedIndicatorValue>> clazz, String key )
     {
-        statementManager.initialise(); // Running in separate thread // TODO initialize file statement manager
+        statementManager.initialise(); // Running in separate thread
         
         final BatchHandler<AggregatedIndicatorValue> batchHandler = batchHandlerFactory.createBatchHandler( clazz ).init();
 

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/resources/META-INF/dhis/beans.xml'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/resources/META-INF/dhis/beans.xml	2012-08-29 14:39:44 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/resources/META-INF/dhis/beans.xml	2012-11-08 08:33:41 +0000
@@ -67,6 +67,11 @@
     <property name="aggregationCache" ref="org.hisp.dhis.datamart.aggregation.cache.AggregationCache" />
   </bean>
 
+  <bean id="org.hisp.dhis.datamart.aggregation.dataelement.AverageIntSingleValueAggregator" class="org.hisp.dhis.datamart.aggregation.dataelement.AverageIntSingleValueAggregator">
+    <property name="crossTabService" ref="org.hisp.dhis.datamart.crosstab.CrossTabService" />
+    <property name="aggregationCache" ref="org.hisp.dhis.datamart.aggregation.cache.AggregationCache" />
+  </bean>
+
   <bean id="org.hisp.dhis.datamart.aggregation.dataelement.AverageBoolAggregator" class="org.hisp.dhis.datamart.aggregation.dataelement.AverageBoolAggregator">
     <property name="crossTabService" ref="org.hisp.dhis.datamart.crosstab.CrossTabService" />
     <property name="aggregationCache" ref="org.hisp.dhis.datamart.aggregation.cache.AggregationCache" />
@@ -76,16 +81,14 @@
 
   <bean id="org.hisp.dhis.datamart.dataelement.DataElementDataMart" class="org.hisp.dhis.datamart.dataelement.DefaultDataElementDataMart">
     <property name="batchHandlerFactory" ref="batchHandlerFactory" />
+    <property name="inMemoryBatchHandlerFactory" ref="inMemoryBatchHandlerFactory" />
     <property name="aggregationCache" ref="org.hisp.dhis.datamart.aggregation.cache.AggregationCache" />
+    <property name="sumIntAggregator" ref="org.hisp.dhis.datamart.aggregation.dataelement.SumIntAggregator" />
+    <property name="averageIntAggregator" ref="org.hisp.dhis.datamart.aggregation.dataelement.AverageIntAggregator" />
+    <property name="averageIntSingleValueAggregator" ref="org.hisp.dhis.datamart.aggregation.dataelement.AverageIntSingleValueAggregator" />
+    <property name="sumBoolAggregator" ref="org.hisp.dhis.datamart.aggregation.dataelement.SumBoolAggregator" />
+    <property name="averageBoolAggregator" ref="org.hisp.dhis.datamart.aggregation.dataelement.AverageBoolAggregator" />
 	<property name="statementManager" ref="inMemoryStatementManager" />
-    <property name="aggregators">
-      <list>
-        <ref bean="org.hisp.dhis.datamart.aggregation.dataelement.SumIntAggregator"/>
-        <ref bean="org.hisp.dhis.datamart.aggregation.dataelement.AverageIntAggregator"/>
-        <ref bean="org.hisp.dhis.datamart.aggregation.dataelement.SumBoolAggregator"/>
-        <ref bean="org.hisp.dhis.datamart.aggregation.dataelement.AverageBoolAggregator"/>
-      </list>
-    </property>
   </bean>
   
   <!-- IndicatorDataMart -->

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/test/java/org/hisp/dhis/datamart/DataMartServiceOrgUnitTest.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/test/java/org/hisp/dhis/datamart/DataMartServiceOrgUnitTest.java	2012-10-31 18:13:34 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/test/java/org/hisp/dhis/datamart/DataMartServiceOrgUnitTest.java	2012-11-08 08:33:41 +0000
@@ -375,8 +375,8 @@
         assertEquals( 1.0, aggregatedOrgUnitDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitA, groupA ) );
         assertEquals( 1.0, aggregatedOrgUnitDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitB, groupA ) );
         assertEquals( 1.0, aggregatedOrgUnitDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitD, groupA ) );
-        //assertEquals( 0.0, aggregatedOrgUnitDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitE, groupA ) );
-        //assertEquals( 0.0, aggregatedOrgUnitDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitF, groupA ) );
+        assertEquals( 0.0, aggregatedOrgUnitDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitE, groupA ) );
+        assertEquals( 0.0, aggregatedOrgUnitDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitF, groupA ) );
         
         assertEquals( 3.0, aggregatedOrgUnitDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitA, groupB ) );
         assertEquals( 3.0, aggregatedOrgUnitDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitB, groupB ) );
@@ -522,7 +522,5 @@
         assertEquals( 22.1, aggregatedOrgUnitDataValueService.getAggregatedIndicatorValue( indicatorA, periodA, unitD, groupB ) );        
         assertEquals( 51.3, aggregatedOrgUnitDataValueService.getAggregatedIndicatorValue( indicatorA, periodD, unitD, groupA ) );        
         assertEquals( 480.6, aggregatedOrgUnitDataValueService.getAggregatedIndicatorValue( indicatorA, periodD, unitD, groupB ), 3.0 );
-        
-        // TODO check why the last value differs on various JVMs
     }
 }

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/test/java/org/hisp/dhis/datamart/DataMartServiceTest.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/test/java/org/hisp/dhis/datamart/DataMartServiceTest.java	2012-08-29 14:39:44 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/test/java/org/hisp/dhis/datamart/DataMartServiceTest.java	2012-11-08 08:33:41 +0000
@@ -329,9 +329,9 @@
         assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitB ), 3.0 );
         assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitC ), 1.0 );
         assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitD ), 1.0 );
-        //assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitE ), 0.0 );
+        assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitE ), 0.0 );
         assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitF ), 2.0 );
-        //assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitG ), 0.0 );
+        assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitG ), 0.0 );
         assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitH ), 1.0 );
         
         assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodD, unitA ), 12.0 );
@@ -343,7 +343,7 @@
         assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodD, unitG ), 1.0 );
         assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodD, unitH ), 3.0 );
     }
-    
+
     @Test
     public void testAverageBoolDataElementDataMart()
     {
@@ -362,13 +362,13 @@
         assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitG ), 0.0 );
         assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodA, unitH ), 100.0 );
         
-        assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodD, unitA ), 66.7, 0.5 );
-        assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodD, unitB ), 66.7, 0.5 );
-        assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodD, unitC ), 66.7, 0.5 );
-        assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodD, unitD ), 66.7, 0.5 );
-        assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodD, unitE ), 33.3, 0.5 );
+        assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodD, unitA ), 66.8, 0.5 );
+        assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodD, unitB ), 66.8, 0.5 );
+        assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodD, unitC ), 65.9, 0.5 );
+        assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodD, unitD ), 67.0, 0.5 );
+        assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodD, unitE ), 33.0, 0.5 );
         assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodD, unitF ), 78.0, 0.5 );
-        assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodD, unitG ), 33.1, 0.5 );
+        assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodD, unitG ), 34.1, 0.5 );
         assertEquals( aggregatedDataValueService.getAggregatedValue( dataElementB, categoryOptionCombo, periodD, unitH ), 100.0, 0.5 );
     }
 

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/test/java/org/hisp/dhis/datamart/crosstab/CrossTabServiceTest.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/test/java/org/hisp/dhis/datamart/crosstab/CrossTabServiceTest.java	2012-08-29 14:39:44 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/test/java/org/hisp/dhis/datamart/crosstab/CrossTabServiceTest.java	2012-11-08 08:33:41 +0000
@@ -29,6 +29,7 @@
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
 
 import java.util.ArrayList;
 import java.util.Calendar;
@@ -50,6 +51,7 @@
 import org.hisp.dhis.dataelement.DataElementCategoryService;
 import org.hisp.dhis.dataelement.DataElementOperand;
 import org.hisp.dhis.dataelement.DataElementService;
+import org.hisp.dhis.datamart.CrossTabDataValue;
 import org.hisp.dhis.datamart.crosstab.jdbc.CrossTabStore;
 import org.hisp.dhis.datavalue.DataValueService;
 import org.hisp.dhis.jdbc.batchhandler.GenericBatchHandler;
@@ -63,6 +65,7 @@
 
 /**
  * @author Lars Helge Overland
+ * @version $Id: CrossTabServiceTest.java 6217 2008-11-06 18:53:04Z larshelg $
  */
 public class CrossTabServiceTest
     extends DhisTest
@@ -75,7 +78,7 @@
 
     private List<DataElementOperand> operands;
     private Collection<Integer> periodIds;
-    private List<Integer> organisationUnitIds;
+    private Collection<Integer> organisationUnitIds;
 
     // -------------------------------------------------------------------------
     // Fixture
@@ -143,7 +146,7 @@
         Character[] characters = { 'A', 'B', 'C', 'D', 'E' };
         
         periodIds = new HashSet<Integer>();
-        organisationUnitIds = new ArrayList<Integer>();
+        organisationUnitIds = new HashSet<Integer>();
         
         Collection<DataElement> dataElements = new HashSet<DataElement>();
         Collection<Period> periods = new HashSet<Period>();
@@ -189,20 +192,27 @@
     public void testPopulateCrossTabValue()
         throws Exception
     {
-        String key = crossTabService.createCrossTabTable( organisationUnitIds );
+        String key = crossTabService.createCrossTabTable( operands );
         crossTabService.populateCrossTabTable( operands, periodIds, organisationUnitIds, key ).get();
         
-        for ( DataElementOperand operand : operands )
+        Collection<CrossTabDataValue> values = crossTabService.getCrossTabDataValues( operands, periodIds, organisationUnitIds, key );
+        
+        assertNotNull( values );
+        
+        assertEquals( 25, values.size() );
+        
+        for ( CrossTabDataValue crossTabValue : values )
         {
-            Map<String, String> values = crossTabService.getCrossTabDataValues( operand, periodIds, organisationUnitIds, key );
-
-            assertNotNull( values );
-
-            assertEquals( 25, values.size() );
-            
-            for ( String valueKey : values.keySet() )
+            assertTrue( crossTabValue.getPeriodId() != 0 );
+            assertTrue( crossTabValue.getSourceId() != 0 );
+            
+            assertNotNull( crossTabValue.getValueMap() );
+            
+            assertEquals( 10, crossTabValue.getValueMap().size() );
+            
+            for ( String value : crossTabValue.getValueMap().values() )
             {
-                assertEquals( "10", values.get( valueKey ) );
+                assertEquals( "10", value );
             }
         }
     }

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/test/java/org/hisp/dhis/datamart/crosstab/CrossTabStoreTest.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/test/java/org/hisp/dhis/datamart/crosstab/CrossTabStoreTest.java	2012-08-25 11:47:17 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/test/java/org/hisp/dhis/datamart/crosstab/CrossTabStoreTest.java	2012-11-08 08:33:41 +0000
@@ -1,7 +1,6 @@
 package org.hisp.dhis.datamart.crosstab;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 import org.apache.commons.lang.RandomStringUtils;
@@ -15,8 +14,6 @@
 {
     private CrossTabStore crossTabStore;
     
-    private List<Integer> organisationUnits;
-    
     private List<DataElementOperand> operands;
     
     private String key = RandomStringUtils.randomAlphanumeric( 8 );
@@ -30,8 +27,6 @@
     {
         crossTabStore = (CrossTabStore) getBean( CrossTabStore.ID );
         
-        organisationUnits = Arrays.asList( 3, 4, 5, 6 );
-        
         operands = new ArrayList<DataElementOperand>();
         operands.add( new DataElementOperand( 1, 1 ) );
         operands.add( new DataElementOperand( 1, 2 ) );
@@ -52,7 +47,7 @@
     @Test
     public void testDropCrossTabTable()
     {
-        crossTabStore.createCrossTabTable( organisationUnits, key );
+        crossTabStore.createCrossTabTable( operands, key );
         
         crossTabStore.dropCrossTabTable( key );
     }

=== added file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/filter/NonCriticalUserAuthorityGroupFilter.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/filter/NonCriticalUserAuthorityGroupFilter.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/filter/NonCriticalUserAuthorityGroupFilter.java	2012-11-08 10:38:24 +0000
@@ -0,0 +1,52 @@
+package org.hisp.dhis.system.filter;
+
+/*
+ * Copyright (c) 2004-2012, 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 org.apache.commons.collections.CollectionUtils;
+import org.hisp.dhis.system.util.Filter;
+import org.hisp.dhis.user.UserAuthorityGroup;
+
+/**
+ * @author Lars Helge Overland
+ */
+public class NonCriticalUserAuthorityGroupFilter
+    implements Filter<UserAuthorityGroup>
+{
+    @Override
+    public boolean retain( UserAuthorityGroup userRole )
+    {
+        if ( userRole == null || userRole.getAuthorities() == null )
+        {
+            return false;
+        }
+        
+        return !CollectionUtils.containsAny( userRole.getAuthorities(), Arrays.asList( UserAuthorityGroup.CRITICAL_AUTHS ) );
+    }
+}

=== modified file 'dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/AccountController.java'
--- dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/AccountController.java	2012-11-04 09:06:31 +0000
+++ dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/AccountController.java	2012-11-08 14:33:17 +0000
@@ -253,6 +253,12 @@
             response.setStatus( HttpServletResponse.SC_BAD_REQUEST );
             return "Email is not specified or invalid";
         }
+        
+        if ( phoneNumber == null || phoneNumber.trim().length() > 30 )
+        {
+            response.setStatus( HttpServletResponse.SC_BAD_REQUEST );
+            return "Phone number is not specified or invalid";
+        }
 
         if ( employer == null || employer.trim().length() > MAX_LENGTH )
         {

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/ajax/jsonUser.vm'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/ajax/jsonUser.vm	2011-11-23 20:45:01 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/ajax/jsonUser.vm	2012-11-08 14:13:17 +0000
@@ -7,6 +7,8 @@
     "email": "$!encoder.jsonEncode( ${user.email} )",
     "phoneNumber": "$!encoder.jsonEncode( ${user.phoneNumber} )",
     "lastLogin": "$!format.formatDate( ${user.userCredentials.lastLogin} )",	
+    "created": "$!format.formatDate( ${user.userCredentials.created} )",
+    "disabled": ${user.userCredentials.disabled},
     #set( $size = ${user.organisationUnits.size()} )
     "organisationUnits": [
     #foreach( $unit in $user.organisationUnits )

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/useraccount/account.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/useraccount/account.js	2012-11-03 20:19:30 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/useraccount/account.js	2012-11-08 14:33:17 +0000
@@ -28,6 +28,10 @@
 			email: true,
 			rangelength: [ 4, 80 ]
 		},
+		phoneNumber: {
+			required: true,
+			rangelength: [ 6, 30 ]			
+		},
 		employer: {
 			required: true,
 			rangelength: [ 2, 80 ]

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/useraccount/account.vm'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/useraccount/account.vm	2012-11-03 20:19:30 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/useraccount/account.vm	2012-11-08 14:33:17 +0000
@@ -48,7 +48,7 @@
     </tr>
     <tr>
         <td><label for="phoneNumber">$i18n.getString( "mobile_phone" )</label></td>
-        <td style="padding-bottom: 5px"><input type="text" id="phoneNumber" name="phoneNumber" placeholder="$i18n.getString( 'optional' )"></td>
+        <td style="padding-bottom: 5px"><input type="text" id="phoneNumber" name="phoneNumber"></td>
     </tr>
     <tr>
         <td><label for="employer">$i18n.getString( "employer" )</label></td>

=== added file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/images/disable.png'
Binary files dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/images/disable.png	1970-01-01 00:00:00 +0000 and dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/images/disable.png	2012-11-08 12:35:18 +0000 differ
=== added file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/images/disable_denied.png'
Binary files dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/images/disable_denied.png	1970-01-01 00:00:00 +0000 and dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/images/disable_denied.png	2012-11-08 14:13:17 +0000 differ
=== modified file 'dhis-2/dhis-web/dhis-web-dashboard-integration/src/main/resources/struts.xml'
--- dhis-2/dhis-web/dhis-web-dashboard-integration/src/main/resources/struts.xml	2012-11-06 07:01:56 +0000
+++ dhis-2/dhis-web/dhis-web-dashboard-integration/src/main/resources/struts.xml	2012-11-08 13:26:59 +0000
@@ -148,7 +148,7 @@
       <result name="success" type="velocity">/main.vm</result>
       <param name="page">/dhis-web-dashboard-integration/profile.vm</param>
       <param name="menu">/dhis-web-commons/about/menuDashboard.vm</param>
-      <param name="javascripts">javascript/profile.js</param>
+      <param name="javascripts">javascript/profile.js,javascript/interpretation.js</param>
       <param name="stylesheets">style/dashboard.css</param>
     </action>
 

=== modified file 'dhis-2/dhis-web/dhis-web-dashboard-integration/src/main/webapp/dhis-web-dashboard-integration/interpretation.vm'
--- dhis-2/dhis-web/dhis-web-dashboard-integration/src/main/webapp/dhis-web-dashboard-integration/interpretation.vm	2012-10-14 18:31:58 +0000
+++ dhis-2/dhis-web/dhis-web-dashboard-integration/src/main/webapp/dhis-web-dashboard-integration/interpretation.vm	2012-11-08 14:13:17 +0000
@@ -2,7 +2,8 @@
 <h3>$i18n.getString( "interpretations" )</h3>
 
 <div id="shareLink">$i18n.getString( "share_your_own_interpretations_from" ) 
-	<a href="../dhis-web-visualizer/app/index.html">$i18n.getString( "data_visualizer" )</a> or 
+	<a href="../dhis-web-visualizer/app/index.html">$i18n.getString( "data_visualizer" )</a>,
+	<a href="../dhis-web-mapping/app/index.html">$i18n.getString( "gis" )</a> or 
 	<a href="../dhis-web-reporting/displayManageTableForm.action">$i18n.getString( "report_table" )</a>
 </div>
 

=== modified file 'dhis-2/dhis-web/dhis-web-dashboard-integration/src/main/webapp/dhis-web-dashboard-integration/interpretationFeed.vm'
--- dhis-2/dhis-web/dhis-web-dashboard-integration/src/main/webapp/dhis-web-dashboard-integration/interpretationFeed.vm	2012-11-07 13:14:27 +0000
+++ dhis-2/dhis-web/dhis-web-dashboard-integration/src/main/webapp/dhis-web-dashboard-integration/interpretationFeed.vm	2012-11-08 14:13:17 +0000
@@ -55,7 +55,7 @@
 	        #foreach( $comment in $comments )
 	        <div #if( $velocityCount <= $commentStartPos ) style="display:none"#end>
 	            <div class="interpretationName">
-	                <span class="bold pointer" onclick="showUserInfo( '${comment.user.id}' )">${comment.user.name}</span>&nbsp;
+	                <a class="bold userLink" href="profile.action?id=${comment.user.uid}">${comment.user.name}</a>&nbsp;
 	                <span class="grey">${format.formatDate( $comment.created )}</span>
 	            </div>
 		        <div class="interpretationText">

=== modified file 'dhis-2/dhis-web/dhis-web-dashboard-integration/src/main/webapp/dhis-web-dashboard-integration/map_view.vm'
--- dhis-2/dhis-web/dhis-web-dashboard-integration/src/main/webapp/dhis-web-dashboard-integration/map_view.vm	2012-11-06 07:01:56 +0000
+++ dhis-2/dhis-web/dhis-web-dashboard-integration/src/main/webapp/dhis-web-dashboard-integration/map_view.vm	2012-11-08 14:13:17 +0000
@@ -18,5 +18,5 @@
 
 #if( $maps.size() == 0 )
 <div class="labelText">$i18n.getString( "insert_links_by_going_to" ) 
-<a href="../dhis-web-mapping/app/index.html">$i18n.getString( "map" )</a></div>
+<a href="../dhis-web-mapping/app/index.html">$i18n.getString( "gis" )</a></div>
 #end
\ No newline at end of file

=== modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/java/org/hisp/dhis/settings/action/system/GetAccessSettingsAction.java'
--- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/java/org/hisp/dhis/settings/action/system/GetAccessSettingsAction.java	2012-11-04 08:37:25 +0000
+++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-settings/src/main/java/org/hisp/dhis/settings/action/system/GetAccessSettingsAction.java	2012-11-08 10:38:24 +0000
@@ -34,6 +34,8 @@
 import org.hisp.dhis.common.comparator.IdentifiableObjectNameComparator;
 import org.hisp.dhis.organisationunit.OrganisationUnit;
 import org.hisp.dhis.organisationunit.OrganisationUnitService;
+import org.hisp.dhis.system.filter.NonCriticalUserAuthorityGroupFilter;
+import org.hisp.dhis.system.util.FilterUtils;
 import org.hisp.dhis.user.UserAuthorityGroup;
 import org.hisp.dhis.user.UserService;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -78,6 +80,7 @@
     {
         userRoles = new ArrayList<UserAuthorityGroup>( userService.getAllUserAuthorityGroups() );
         
+        FilterUtils.filter( userRoles, new NonCriticalUserAuthorityGroupFilter() );
         Collections.sort( userRoles, IdentifiableObjectNameComparator.INSTANCE );
         
         selfRegistrationOrgUnits.addAll( organisationUnitService.getOrganisationUnitsAtLevel( 1 ) );

=== modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/AddUserAction.java'
--- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/AddUserAction.java	2012-10-31 05:41:46 +0000
+++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/AddUserAction.java	2012-11-08 13:26:59 +0000
@@ -180,8 +180,6 @@
         UserCredentials currentUserCredentials = currentUserService.getCurrentUser() != null ? currentUserService
             .getCurrentUser().getUserCredentials() : null;
 
-        // TODO check permissions
-            
         // ---------------------------------------------------------------------
         // Prepare values
         // ---------------------------------------------------------------------

=== added file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/DisableUserAction.java'
--- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/DisableUserAction.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/DisableUserAction.java	2012-11-08 13:26:59 +0000
@@ -0,0 +1,91 @@
+package org.hisp.dhis.user.action;
+
+/*
+ * Copyright (c) 2004-2012, 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 org.hisp.dhis.user.CurrentUserService;
+import org.hisp.dhis.user.User;
+import org.hisp.dhis.user.UserCredentials;
+import org.hisp.dhis.user.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.opensymphony.xwork2.Action;
+
+/**
+ * @author Lars Helge Overland
+ */
+public class DisableUserAction
+    implements Action
+{
+    @Autowired
+    private UserService userService;
+
+    @Autowired
+    private CurrentUserService currentUserService;
+    
+    private String username;
+    
+    public void setUsername( String username )
+    {
+        this.username = username;
+    }
+    
+    private boolean enable = false;
+
+    public void setEnable( boolean enable )
+    {
+        this.enable = enable;
+    }
+
+    public String execute()
+    {
+        UserCredentials credentials = userService.getUserCredentialsByUsername( username );
+        
+        if ( credentials == null )
+        {
+            return ERROR;
+        }
+        
+        User currentUser = currentUserService.getCurrentUser();
+        
+        if ( currentUser == null || currentUser.getUserCredentials() == null )
+        {
+            return ERROR;
+        }
+        
+        if ( !currentUser.getUserCredentials().canModify( credentials ) )
+        {
+            return ERROR;
+        }
+        
+        credentials.setDisabled( !enable );
+        
+        userService.updateUserCredentials( credentials );
+        
+        return SUCCESS;
+    }    
+}

=== modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/EditUserGroupFormAction.java'
--- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/EditUserGroupFormAction.java	2012-06-01 13:19:25 +0000
+++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/EditUserGroupFormAction.java	2012-11-08 12:35:18 +0000
@@ -38,6 +38,9 @@
 
 import java.util.*;
 
+/**
+ * @author Lars Helge Overland
+ */
 public class EditUserGroupFormAction
     implements Action
 {

=== modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/GetUserListAction.java'
--- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/GetUserListAction.java	2011-12-26 10:07:59 +0000
+++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/GetUserListAction.java	2012-11-08 09:46:51 +0000
@@ -109,6 +109,18 @@
         this.months = months;
     }
 
+    private Boolean selfRegistered;
+
+    public Boolean getSelfRegistered()
+    {
+        return selfRegistered;
+    }
+
+    public void setSelfRegistered( Boolean selfRegistered )
+    {
+        this.selfRegistered = selfRegistered;
+    }
+
     // -------------------------------------------------------------------------
     // Action implemantation
     // -------------------------------------------------------------------------
@@ -132,6 +144,13 @@
             userCredentialsList = new ArrayList<UserCredentials>( userService.getInactiveUsers( months, paging
                 .getStartPos(), paging.getPageSize() ) );
         }
+        else if ( Boolean.TRUE.equals( selfRegistered ) )
+        {
+            this.paging = createPaging( userService.getSelfRegisteredUserCredentialsCount() );
+            
+            userCredentialsList = new ArrayList<UserCredentials>( userService.getSelfRegisteredUserCredentials( paging.
+                getStartPos(), paging.getPageSize() ) );
+        }
         else
         {
             this.paging = createPaging( userService.getUserCount() );

=== modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/RemoveUserAction.java'
--- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/RemoveUserAction.java	2012-07-01 07:12:58 +0000
+++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/java/org/hisp/dhis/user/action/RemoveUserAction.java	2012-11-08 13:26:59 +0000
@@ -95,6 +95,16 @@
 
         User currentUser = currentUserService.getCurrentUser();
 
+        if ( currentUser == null || user == null )
+        {
+            return ERROR;
+        }
+        
+        if ( !currentUser.getUserCredentials().canModify( user.getUserCredentials() ) )
+        {
+            return ERROR;
+        }
+        
         boolean isCurrentUser = currentUser != null && currentUser.equals( user );
 
         UserCredentials userCredentials = user.getUserCredentials();

=== modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/META-INF/dhis/beans.xml'
--- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/META-INF/dhis/beans.xml	2012-10-31 14:02:58 +0000
+++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/META-INF/dhis/beans.xml	2012-11-08 12:35:18 +0000
@@ -118,6 +118,8 @@
     <property name="attributeService" ref="org.hisp.dhis.attribute.AttributeService" />
   </bean>
 
+  <bean id="org.hisp.dhis.user.action.DisableUserAction" class="org.hisp.dhis.user.action.DisableUserAction" scope="prototype" />
+
    <!-- PDF -->
 
   <bean id="org.hisp.dhis.user.action.pdf.ExportToPdfAction" class="org.hisp.dhis.user.action.pdf.ExportToPdfAction"

=== modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/org/hisp/dhis/user/i18n_module.properties'
--- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/org/hisp/dhis/user/i18n_module.properties	2012-11-05 03:12:14 +0000
+++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/org/hisp/dhis/user/i18n_module.properties	2012-11-08 12:35:18 +0000
@@ -235,6 +235,7 @@
 fullname=Fullname
 role=Role
 user=User
+self_registered_user=Self Registered User
 user_by_orgunit=User by Organisation Unit
 user_role=User Role
 data_sets=Data sets
@@ -285,4 +286,8 @@
 month=month
 months=months
 select=Select
-object_not_deleted_associated_by_objects=Object not deleted because it is associated by objects of type
\ No newline at end of file
+object_not_deleted_associated_by_objects=Object not deleted because it is associated by objects of type
+created=Created
+disabled=Disabled
+disable=Disable
+enable=Enable
\ No newline at end of file

=== modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/struts.xml'
--- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/struts.xml	2012-10-31 14:02:58 +0000
+++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/resources/struts.xml	2012-11-08 13:26:59 +0000
@@ -78,6 +78,12 @@
       <param name="onExceptionReturn">plainTextError</param>
     </action>
     
+    <action name="disableUser" class="org.hisp.dhis.user.action.DisableUserAction">
+      <result name="success" type="velocity-json">/dhis-web-commons/ajax/jsonResponseSuccess.vm</result>
+      <result name="error" type="velocity-json">/dhis-web-commons/ajax/jsonResponseError.vm</result>
+      <param name="onExceptionReturn">plainTextError</param>
+    </action>
+    
 	<!-- PDF -->
 
     <action name="exportToPdf" class="org.hisp.dhis.user.action.pdf.ExportToPdfAction">

=== modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/webapp/dhis-web-maintenance-user/allUser.vm'
--- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/webapp/dhis-web-maintenance-user/allUser.vm	2012-10-18 13:27:17 +0000
+++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/webapp/dhis-web-maintenance-user/allUser.vm	2012-11-08 12:35:18 +0000
@@ -2,6 +2,8 @@
 <script type="text/javascript">
 	var i18n_confirm_delete = '$encoder.jsEscape( $i18n.getString( "confirm_delete_user" ) , "'")';
 	var i18n_none = '$encoder.jsEscape( $i18n.getString( "none" ) , "'")';
+    var i18n_yes = '$encoder.jsEscape( $i18n.getString( "yes" ) , "'")';
+    var i18n_no = '$encoder.jsEscape( $i18n.getString( "no" ) , "'")';
 	var i18n_edit = '$encoder.jsEscape( $i18n.getString( "edit" ) , "'")';
 	var i18n_remove = '$encoder.jsEscape( $i18n.getString( "remove" ) , "'")';
 	var i18n_show_details = '$encoder.jsEscape( $i18n.getString( "show_details" ) , "'")';
@@ -44,7 +46,7 @@
 				<tr>
 					<th>$i18n.getString( "username" )</th>
 					<th>$i18n.getString( "name" )</th>
-					<th class="{sorter: false}" style="width:80px;">$i18n.getString( "operations" )</th>
+					<th class="{sorter: false}" style="width:120px;">$i18n.getString( "operations" )</th>
 				</tr>
 				</thead>
 				<tbody id="list">
@@ -54,9 +56,20 @@
 					<td>$encoder.htmlEncode( $userCredentials.username )</td>
 					<td>$encoder.htmlEncode( $userCredentials.user.surname ), $encoder.htmlEncode( $userCredentials.user.firstName )</td>
 					<td style="text-align:right">
-					<a href="showUpdateUserForm.action?id=$userCredentials.id" title="$i18n.getString( 'edit' )"><img src="../images/edit.png" alt="$i18n.getString( 'edit' )"/></a>					
-					#if($currentUserName != $userCredentials.username)
-						<a href="javascript:removeUser( $userCredentials.id, '$encoder.jsEncode( $userCredentials.username )' )" title="$i18n.getString( 'remove' )"><img src="../images/delete.png" alt="$i18n.getString( 'remove' )"/></a>
+					<a href="showUpdateUserForm.action?id=$userCredentials.id" title="$i18n.getString( 'edit' )"><img src="../images/edit.png" alt="$i18n.getString( 'edit' )"/></a>
+					
+					#if( $currentUserName != $userCredentials.username && !$userCredentials.disabled )					   
+					<img id="disableImg${userCredentials.id}" src="../images/disable.png" 
+					   onclick="disableUser( $userCredentials.id, '$encoder.jsEncode( $userCredentials.username )' )" style="cursor:pointer" title="$i18n.getString( 'disable' )">
+					#elseif( $currentUserName != $userCredentials.username && $userCredentials.disabled )
+					<img id="disableImg${userCredentials.id}" src="../images/add.png" 
+					   onclick="enableUser( $userCredentials.id, '$encoder.jsEncode( $userCredentials.username )' )" style="cursor:pointer" title="$i18n.getString( 'enable' )">
+					#else
+					<img src="../images/disable_denied.png">
+					#end
+					
+					#if( $currentUserName != $userCredentials.username )
+						<a href="javascript:removeUser( $userCredentials.id, '$encoder.jsEncode( $userCredentials.username )' )" title="$i18n.getString( 'remove' )"><img src="../images/delete.png"></a>
 					#else
 						<img src="../images/delete-denied.png" alt="$i18n.getString( 'remove' )"/>
 					#end
@@ -82,11 +95,13 @@
 				<div style="float:right">
 					<a href="javascript:hideDetails()" title="$i18n.getString( 'hide_details' )"><img src="../images/hide.png" alt="$i18n.getString( 'hide_details' )"/></a>
 				</div>	
-				<p><label>$i18n.getString( "username" ):</label><br/><span id="usernameField"></span></p>
-				<p><label>$i18n.getString( "fullname" ):</label><br/><span id="fullNameField"></span></p>
-				<p><label>$i18n.getString( "email" ):</label><br/><span id="emailField"></span></p>
-                <p><label>$i18n.getString( "phone_number" ):</label><br/><span id="phoneNumberField"></span></p>
-				<p><label>$i18n.getString( "last_login"):</label><br/><span id="lastLoginField"></span></p>
+				<p><label>$i18n.getString( "username" ):</label><br><span id="usernameField"></span></p>
+				<p><label>$i18n.getString( "fullname" ):</label><br><span id="fullNameField"></span></p>
+				<p><label>$i18n.getString( "email" ):</label><br><span id="emailField"></span></p>
+                <p><label>$i18n.getString( "phone_number" ):</label><br><span id="phoneNumberField"></span></p>
+				<p><label>$i18n.getString( "last_login"):</label><br><span id="lastLoginField"></span></p>
+				<p><label>$i18n.getString( "created" ):</label><br><span id="createdField"></span></p>
+                <p><label>$i18n.getString( "disabled" ):</label><br><span id="disabledField"></span></p>
 				<p><label>$i18n.getString( "organisation_units" ):</label><br/><span id="assignedOrgunitField"></span></p>
 				<p><label>$i18n.getString( "roles" ):</label><br/><span id="roleField"></span></p>
 			</div>

=== modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/webapp/dhis-web-maintenance-user/javascript/user.js'
--- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/webapp/dhis-web-maintenance-user/javascript/user.js	2011-10-06 15:39:08 +0000
+++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/webapp/dhis-web-maintenance-user/javascript/user.js	2012-11-08 13:26:59 +0000
@@ -58,6 +58,12 @@
 		var lastLogin = json.user.lastLogin;
 		setInnerHTML( 'lastLoginField', lastLogin ? lastLogin : '[' + i18n_none + ']' );
 		
+		var created = json.user.created;
+		setInnerHTML( 'createdField', created ? created : '[' + i18n_none + ']' );
+		
+		var disabled = json.user.disabled;
+		setInnerHTML( 'disabledField', disabled ? i18n_yes : i18n_no );
+		
 		var organisationUnits = joinNameableObjects( json.user.organisationUnits );
 		setInnerHTML( 'assignedOrgunitField', organisationUnits ? organisationUnits : '[' + i18n_none + ']' );
 		
@@ -74,7 +80,52 @@
 
 function removeUser( userId, username )
 {
-	removeItem( userId, username, i18n_confirm_delete, 'removeUser.action' );
+	removeItem( userId, username, i18n_confirm_delete, "removeUser.action" );
+}
+
+function disableUser( userId, username )
+{
+	var confirmation = confirm( "Are you sure you want to disable this user?\n\n" + username );
+	
+	if ( confirmation )
+	{
+		$.post( "disableUser.action", 
+			{
+				username: username
+			},
+			function( json ) 
+			{
+				if ( json.response == "success" ) {
+					$( "#disableImg" + userId ).attr( "src", "../images/add.png" );
+					$( "#disableImg" + userId ).removeAttr( "onclick" ).off( "click" ).click( function() {
+						enableUser( userId, username )
+					} );
+				}
+			} );
+	}
+}
+
+function enableUser( userId, username )
+{
+	var confirmation = confirm( "Are you sure you want to enable this user?\n\n" + username );
+	
+	if ( confirmation )
+	{
+		$.post( "disableUser.action", 
+			{
+				username: username,
+				enable: true
+			},
+			function( json ) 
+			{
+				if ( json.response == "success" ) {
+					$( "#disableImg" + userId ).attr( "src", "../images/disable.png" );
+					$( "#disableImg" + userId ).removeAttr( "onclick" ).off( "click" ).click( function() {
+						disableUser( userId, username )
+					} );
+				}
+			} );
+	}
 }
 
 // -----------------------------------------------------------------------------

=== modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/webapp/dhis-web-maintenance-user/menu.vm'
--- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/webapp/dhis-web-maintenance-user/menu.vm	2012-05-13 13:32:51 +0000
+++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-user/src/main/webapp/dhis-web-maintenance-user/menu.vm	2012-11-08 09:46:51 +0000
@@ -1,7 +1,8 @@
 <h2>$i18n.getString( "user" )&nbsp;</h2>
 <ul>
-	<li><a href="user.action" title="$i18n.getString( "user_by_orgunit" )">$i18n.getString( "user_by_orgunit" )&nbsp;</a></li>
-	<li><a href="alluser.action" title="$i18n.getString( "user" )">$i18n.getString( "user" )&nbsp;</a></li>
+	<li><a href="user.action">$i18n.getString( "user_by_orgunit" )&nbsp;</a></li>
+	<li><a href="alluser.action">$i18n.getString( "user" )&nbsp;</a></li>
+	<li><a href="alluser.action?selfRegistered=true">$i18n.getString( "self_registered_user" )&nbsp;</a></li>
 	<li><a href="allRole.action" title="$i18n.getString( "user_role" )">$i18n.getString( "user_role" )&nbsp;</a></li>
     <li><a href="deleteCurrentUser.action" title="$i18n.getString( "delete_current_user" )">$i18n.getString( "delete_current_user" )</a></li>
 	<li><a href="getAllUserGroups.action" title="$i18n.getString( "user_group" )" >$i18n.getString( "user_group" )</a></li>