← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 1318: Made the ValidationRule-ValidationRuleObject association bi-directional. Re-implemented data inte...

 

------------------------------------------------------------
revno: 1318
committer: Lars Helge Oeverland <larshelge@xxxxxxxxx>
branch nick: trunk
timestamp: Wed 2010-01-20 11:06:43 +0100
message:
  Made the ValidationRule-ValidationRuleObject association bi-directional. Re-implemented data integrity checks using the new bi-directional associations between objects and groups, reducing time to 1/3.
modified:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataelement/DataElementGroupSet.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/indicator/IndicatorGroupSet.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnitGroupSet.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRule.java
  dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/dataintegrity/DefaultDataIntegrityService.java
  dhis-2/dhis-services/dhis-service-administration/src/test/java/org/hisp/dhis/dataintegrity/DataIntegrityServiceTest.java
  dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRule.hbm.xml
  dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/ListUtils.java
  dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java


--
lp:dhis2
https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk

Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription.
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataelement/DataElementGroupSet.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataelement/DataElementGroupSet.java	2009-11-06 09:18:28 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/dataelement/DataElementGroupSet.java	2010-01-20 10:06:43 +0000
@@ -28,6 +28,7 @@
  */
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
 import org.hisp.dhis.dimension.Dimension;
@@ -66,6 +67,22 @@
     {
         return members;
     }
+
+    // -------------------------------------------------------------------------
+    // Logic
+    // -------------------------------------------------------------------------
+
+    public Collection<DataElement> getDataElements()
+    {
+        List<DataElement> dataElements = new ArrayList<DataElement>();
+        
+        for ( DataElementGroup group : members )
+        {
+            dataElements.addAll( group.getMembers() );
+        }
+        
+        return dataElements;
+    }
     
     // -------------------------------------------------------------------------
     // equals and hashCode

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/indicator/IndicatorGroupSet.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/indicator/IndicatorGroupSet.java	2009-11-05 19:04:58 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/indicator/IndicatorGroupSet.java	2010-01-20 10:06:43 +0000
@@ -28,6 +28,7 @@
  */
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
 import org.hisp.dhis.dimension.Dimension;
@@ -59,6 +60,31 @@
     }
 
     // -------------------------------------------------------------------------
+    // Dimension
+    // -------------------------------------------------------------------------
+
+    public List<? extends DimensionOption> getDimensionOptions()
+    {
+        return members;
+    }
+
+    // -------------------------------------------------------------------------
+    // Logic
+    // -------------------------------------------------------------------------
+
+    public Collection<Indicator> getIndicators()
+    {
+        List<Indicator> indicators = new ArrayList<Indicator>();
+        
+        for ( IndicatorGroup group : members )
+        {
+            indicators.addAll( group.getMembers() );
+        }
+        
+        return indicators;
+    }
+    
+    // -------------------------------------------------------------------------
     // equals and hashCode
     // -------------------------------------------------------------------------
 
@@ -98,15 +124,6 @@
     }
 
     // -------------------------------------------------------------------------
-    // Dimension
-    // -------------------------------------------------------------------------
-
-    public List<? extends DimensionOption> getDimensionOptions()
-    {
-        return members;
-    }
-    
-    // -------------------------------------------------------------------------
     // Getters and setters
     // -------------------------------------------------------------------------
 

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnitGroupSet.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnitGroupSet.java	2010-01-19 21:48:28 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnitGroupSet.java	2010-01-20 10:06:43 +0000
@@ -28,6 +28,7 @@
  */
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -80,6 +81,18 @@
     // Logic
     // -------------------------------------------------------------------------
 
+    public Collection<OrganisationUnit> getOrganisationUnits()
+    {
+        List<OrganisationUnit> units = new ArrayList<OrganisationUnit>();
+        
+        for ( OrganisationUnitGroup group : organisationUnitGroups )
+        {
+            units.addAll( group.getMembers() );
+        }
+        
+        return units;
+    }
+    
     public boolean isMemberOfOrganisationUnitGroups( OrganisationUnit organisationUnit )
     {
         for ( OrganisationUnitGroup group : organisationUnitGroups )

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRule.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRule.java	2009-06-10 22:25:07 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRule.java	2010-01-20 10:06:43 +0000
@@ -29,7 +29,9 @@
 
 import java.io.Serializable;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 import org.hisp.dhis.expression.Expression;
 
@@ -66,6 +68,8 @@
     
     private Expression rightSide;
     
+    private Set<ValidationRuleGroup> groups = new HashSet<ValidationRuleGroup>();
+    
     // -------------------------------------------------------------------------
     // Constructors
     // ------------------------------------------------------------------------- 
@@ -200,6 +204,16 @@
         this.rightSide = rightSide;
     }
 
+    public Set<ValidationRuleGroup> getGroups()
+    {
+        return groups;
+    }
+
+    public void setGroups( Set<ValidationRuleGroup> groups )
+    {
+        this.groups = groups;
+    }
+
     // -------------------------------------------------------------------------
     // Operator
     // -------------------------------------------------------------------------  

=== modified file 'dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/dataintegrity/DefaultDataIntegrityService.java'
--- dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/dataintegrity/DefaultDataIntegrityService.java	2010-01-19 23:07:17 +0000
+++ dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/dataintegrity/DefaultDataIntegrityService.java	2010-01-20 10:06:43 +0000
@@ -27,35 +27,37 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+import static org.hisp.dhis.system.util.ListUtils.getDuplicates;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 import org.hisp.dhis.dataelement.DataElement;
-import org.hisp.dhis.dataelement.DataElementGroup;
 import org.hisp.dhis.dataelement.DataElementService;
-import org.hisp.dhis.dataintegrity.DataIntegrityService;
 import org.hisp.dhis.dataset.DataSet;
 import org.hisp.dhis.dataset.DataSetService;
 import org.hisp.dhis.expression.ExpressionService;
 import org.hisp.dhis.indicator.Indicator;
-import org.hisp.dhis.indicator.IndicatorGroup;
 import org.hisp.dhis.indicator.IndicatorService;
 import org.hisp.dhis.organisationunit.OrganisationUnit;
 import org.hisp.dhis.organisationunit.OrganisationUnitGroup;
 import org.hisp.dhis.organisationunit.OrganisationUnitGroupService;
 import org.hisp.dhis.organisationunit.OrganisationUnitGroupSet;
 import org.hisp.dhis.organisationunit.OrganisationUnitService;
+import org.hisp.dhis.organisationunit.comparator.OrganisationUnitNameComparator;
 import org.hisp.dhis.period.PeriodType;
+import org.hisp.dhis.system.filter.OrganisationUnitGroupWithoutGroupSetFilter;
+import org.hisp.dhis.system.util.Filter;
+import org.hisp.dhis.system.util.FilterUtils;
 import org.hisp.dhis.validation.ValidationRule;
-import org.hisp.dhis.validation.ValidationRuleGroup;
 import org.hisp.dhis.validation.ValidationRuleService;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
 
 /**
  * @author Lars Helge Overland
@@ -130,56 +132,28 @@
 
     public Collection<DataElement> getDataElementsWithoutDataSet()
     {
-        Collection<DataSet> dataSets = dataSetService.getAllDataSets();
-        
         Collection<DataElement> dataElements = dataElementService.getAllDataElements();
         
-        Iterator<DataElement> iterator = dataElements.iterator();
-        
-        while ( iterator.hasNext() )
-        {
-            final DataElement element = iterator.next();
-            
-            for ( DataSet dataSet : dataSets )
+        return FilterUtils.filter( dataElements, new Filter<DataElement>()
             {
-                if ( dataSet.getDataElements().contains( element ) )
+                public boolean retain( DataElement object )
                 {
-                    iterator.remove();
-                    
-                    break;
+                    return object.getDataSets() == null || object.getDataSets().size() == 0;
                 }
-            }
-        }
-        
-        return dataElements;
+            } );
     }
 
-    // TODO re-implement using new model
-    
     public Collection<DataElement> getDataElementsWithoutGroups()
     {
-        Collection<DataElementGroup> groups = dataElementService.getAllDataElementGroups();
-        
         Collection<DataElement> dataElements = dataElementService.getAllDataElements();
         
-        Iterator<DataElement> iterator = dataElements.iterator();
-        
-        while ( iterator.hasNext() )
+        return FilterUtils.filter( dataElements, new Filter<DataElement>()
         {
-            final DataElement element = iterator.next();
-            
-            for ( DataElementGroup group : groups )
+            public boolean retain( DataElement object )
             {
-                if ( group.getMembers().contains( element ) )
-                {
-                    iterator.remove();
-                    
-                    break;
-                }
+                return object.getGroups() == null || object.getGroups().size() == 0;
             }
-        }
-        
-        return dataElements;
+        } );
     }
     
     public Map<DataElement, Collection<DataSet>> getDataElementsAssignedToDataSetsWithDifferentPeriodTypes()
@@ -221,19 +195,13 @@
     {
         Collection<DataSet> dataSets = dataSetService.getAllDataSets();
         
-        Iterator<DataSet> iterator = dataSets.iterator();
-        
-        while ( iterator.hasNext() )
-        {
-            final DataSet dataSet = iterator.next();
-            
-            if ( dataSet.getSources().size() > 0 )
+        return FilterUtils.filter( dataSets, new Filter<DataSet>()
             {
-                iterator.remove();
-            }
-        }
-        
-        return dataSets;
+                public boolean retain( DataSet object )
+                {
+                    return object.getSources() == null || object.getSources().size() == 0;
+                }
+            } );
     }
     
     // -------------------------------------------------------------------------
@@ -267,28 +235,15 @@
 
     public Collection<Indicator> getIndicatorsWithoutGroups()
     {
-        Collection<IndicatorGroup> groups = indicatorService.getAllIndicatorGroups();
-        
         Collection<Indicator> indicators = indicatorService.getAllIndicators();
         
-        Iterator<Indicator> iterator = indicators.iterator();
-        
-        while ( iterator.hasNext() )
-        {
-            final Indicator indicator = iterator.next();
-            
-            for ( IndicatorGroup group : groups )
+        return FilterUtils.filter( indicators, new Filter<Indicator>()
             {
-                if ( group.getMembers().contains( indicator ) )
+                public boolean retain( Indicator object )
                 {
-                    iterator.remove();
-                    
-                    break;
+                    return object.getGroups() == null || object.getGroups().size() == 0;
                 }
-            }
-        }
-        
-        return indicators;
+            } );
     }
     
     public Map<Indicator, String> getInvalidIndicatorNumerators()
@@ -371,65 +326,43 @@
     {
         Collection<OrganisationUnit> organisationUnits = organisationUnitService.getAllOrganisationUnits();
         
-        Set<OrganisationUnit> orphans = new HashSet<OrganisationUnit>();
-        
-        for ( OrganisationUnit unit : organisationUnits )
-        {
-            if ( unit.getParent() == null && ( unit.getChildren() == null || unit.getChildren().size() == 0 ) )
+        return FilterUtils.filter( organisationUnits, new Filter<OrganisationUnit>()
             {
-                orphans.add( unit );
-            }
-        }
-        
-        return orphans;
+                public boolean retain( OrganisationUnit object )
+                {
+                    return object.getParent() == null && ( object.getChildren() == null || object.getChildren().size() == 0 );
+                }
+            } );
     }
 
     public Collection<OrganisationUnit> getOrganisationUnitsWithoutGroups()
     {
-        Collection<OrganisationUnitGroup> groups = organisationUnitGroupService.getAllOrganisationUnitGroups();
-        
         Collection<OrganisationUnit> organisationUnits = organisationUnitService.getAllOrganisationUnits();
         
-        Iterator<OrganisationUnit> iterator = organisationUnits.iterator();
-        
-        while ( iterator.hasNext() )
-        {
-            final OrganisationUnit unit = iterator.next();
-            
-            for ( OrganisationUnitGroup group : groups )
+        return FilterUtils.filter( organisationUnits, new Filter<OrganisationUnit>()
             {
-                if ( group.getMembers().contains( unit ) )
+                public boolean retain( OrganisationUnit object )
                 {
-                    iterator.remove();
-                    
-                    break;
+                    return object.getGroups() == null || object.getGroups().size() == 0;
                 }
-            }
-        }
-        
-        return organisationUnits;
+            } );
     }
 
     public Collection<OrganisationUnit> getOrganisationUnitsViolatingCompulsoryGroupSets()
     {
         Collection<OrganisationUnitGroupSet> groupSets = organisationUnitGroupService.getCompulsoryOrganisationUnitGroupSets();
-        
+                
         Collection<OrganisationUnit> organisationUnits = organisationUnitService.getAllOrganisationUnits();
         
         Set<OrganisationUnit> targets = new HashSet<OrganisationUnit>();
         
-        for ( OrganisationUnitGroupSet groupSet : groupSets )
+        for ( OrganisationUnit unit : organisationUnits )
         {
-            organisationUnit: for ( OrganisationUnit unit : organisationUnits )
+            for ( OrganisationUnitGroupSet groupSet : groupSets )
             {
-                for ( OrganisationUnitGroup group : groupSet.getOrganisationUnitGroups() )
-                {                    
-                    if ( group.getMembers().contains( unit ) )
-                    {
-                        continue organisationUnit;
-                    }
-                    
-                    targets.add( unit ); // Unit is not a member of any groups in the compulsory group set                    
+                if ( !CollectionUtils.containsAny( groupSet.getOrganisationUnitGroups(), unit.getGroups() ) )
+                {
+                    targets.add( unit );
                 }
             }
         }
@@ -438,32 +371,14 @@
     }
 
     public Collection<OrganisationUnit> getOrganisationUnitsViolatingExclusiveGroupSets()
-    {
+    {        
         Collection<OrganisationUnitGroupSet> groupSets = organisationUnitGroupService.getAllOrganisationUnitGroupSets();
-        
-        Collection<OrganisationUnit> organisationUnits = organisationUnitService.getAllOrganisationUnits();
-        
+
         Set<OrganisationUnit> targets = new HashSet<OrganisationUnit>();
 
         for ( OrganisationUnitGroupSet groupSet : groupSets )
         {
-            organisationUnit: for ( OrganisationUnit unit : organisationUnits )
-            {
-                int memberships = 0;
-                
-                for ( OrganisationUnitGroup group : groupSet.getOrganisationUnitGroups() )
-                {
-                    if ( group.getMembers().contains( unit ) )
-                    {
-                        if ( ++memberships > 1 )
-                        {
-                            targets.add( unit ); // Unit is member of more than one group in the exclusive group set
-                            
-                            continue organisationUnit;
-                        }
-                    }
-                }
-            }
+            targets.addAll( getDuplicates( new ArrayList<OrganisationUnit>( groupSet.getOrganisationUnits() ), new OrganisationUnitNameComparator() ) );
         }
         
         return targets;
@@ -471,28 +386,9 @@
 
     public Collection<OrganisationUnitGroup> getOrganisationUnitGroupsWithoutGroupSets()
     {
-        Collection<OrganisationUnitGroupSet> groupSets = organisationUnitGroupService.getAllOrganisationUnitGroupSets();
-        
         Collection<OrganisationUnitGroup> groups = organisationUnitGroupService.getAllOrganisationUnitGroups();
         
-        Iterator<OrganisationUnitGroup> iterator = groups.iterator();
-        
-        while ( iterator.hasNext() )
-        {
-            final OrganisationUnitGroup group = iterator.next();
-            
-            for ( OrganisationUnitGroupSet groupSet : groupSets )
-            {
-                if ( groupSet.getOrganisationUnitGroups().contains( group ) )
-                {
-                    iterator.remove();
-                    
-                    break;
-                }
-            }
-        }
-        
-        return groups;
+        return FilterUtils.filter( groups, new OrganisationUnitGroupWithoutGroupSetFilter() );
     }
 
     // -------------------------------------------------------------------------
@@ -501,28 +397,15 @@
 
     public Collection<ValidationRule> getValidationRulesWithoutGroups()
     {
-        Collection<ValidationRuleGroup> groups = validationRuleService.getAllValidationRuleGroups();
-        
         Collection<ValidationRule> validationRules = validationRuleService.getAllValidationRules();
         
-        Iterator<ValidationRule> iterator = validationRules.iterator();
-        
-        while ( iterator.hasNext() )
-        {
-            final ValidationRule rule = iterator.next();
-            
-            for ( ValidationRuleGroup group : groups )
+        return FilterUtils.filter( validationRules, new Filter<ValidationRule>()
             {
-                if ( group.getMembers().contains( rule ) )
+                public boolean retain( ValidationRule object )
                 {
-                    iterator.remove();
-                    
-                    break;
+                    return object.getGroups() == null || object.getGroups().size() == 0;
                 }
-            }
-        }
-        
-        return validationRules;
+            } );
     }
     
     public Map<ValidationRule, String> getInvalidValidationRuleLeftSideExpressions()

=== modified file 'dhis-2/dhis-services/dhis-service-administration/src/test/java/org/hisp/dhis/dataintegrity/DataIntegrityServiceTest.java'
--- dhis-2/dhis-services/dhis-service-administration/src/test/java/org/hisp/dhis/dataintegrity/DataIntegrityServiceTest.java	2010-01-19 21:48:28 +0000
+++ dhis-2/dhis-services/dhis-service-administration/src/test/java/org/hisp/dhis/dataintegrity/DataIntegrityServiceTest.java	2010-01-20 10:06:43 +0000
@@ -88,6 +88,7 @@
     private OrganisationUnitGroup unitGroupA;
     private OrganisationUnitGroup unitGroupB;
     private OrganisationUnitGroup unitGroupC;
+    private OrganisationUnitGroup unitGroupD;
     
     private OrganisationUnitGroupSet unitGroupSetA;
     private OrganisationUnitGroupSet unitGroupSetB;    
@@ -121,7 +122,7 @@
         dataElementService.addDataElement( elementA );
         dataElementService.addDataElement( elementB );
         dataElementService.addDataElement( elementC );
-                
+        
         indicatorTypeA = createIndicatorType( 'A' );
         
         indicatorService.addIndicatorType( indicatorTypeA );
@@ -163,14 +164,18 @@
 
         dataSetA.getDataElements().add( elementA );
         dataSetA.getDataElements().add( elementB );
+        elementA.getDataSets().add( dataSetA );
+        elementB.getDataSets().add( dataSetA );
         
         dataSetA.getSources().add( unitA );
+        unitA.getDataSets().add( dataSetA );
         
         dataSetB.getDataElements().add( elementA );
+        elementA.getDataSets().add( dataSetB );        
         
         dataSetService.addDataSet( dataSetA );
         dataSetService.addDataSet( dataSetB );
-              
+        
         // ---------------------------------------------------------------------
         // Groups
         // ---------------------------------------------------------------------
@@ -178,30 +183,43 @@
         elementGroupA = createDataElementGroup( 'A' );
         
         elementGroupA.getMembers().add( elementA );
+        elementA.getGroups().add( elementGroupA );
         
         dataElementService.addDataElementGroup( elementGroupA );
         
         indicatorGroupA = createIndicatorGroup( 'A' );
         
         indicatorGroupA.getMembers().add( indicatorA );
+        indicatorA.getGroups().add( indicatorGroupA );
         
         indicatorService.addIndicatorGroup( indicatorGroupA );
         
         unitGroupA = createOrganisationUnitGroup( 'A' );
         unitGroupB = createOrganisationUnitGroup( 'B' );
         unitGroupC = createOrganisationUnitGroup( 'C' );
+        unitGroupD = createOrganisationUnitGroup( 'D' );
         
         unitGroupA.getMembers().add( unitA );
         unitGroupA.getMembers().add( unitB );
         unitGroupA.getMembers().add( unitC );
+        unitA.getGroups().add( unitGroupA );
+        unitB.getGroups().add( unitGroupA );
+        unitC.getGroups().add( unitGroupA );
         
         unitGroupB.getMembers().add( unitA );
         unitGroupB.getMembers().add( unitB );
         unitGroupB.getMembers().add( unitF );
+        unitA.getGroups().add( unitGroupB );
+        unitB.getGroups().add( unitGroupB );
+        unitF.getGroups().add( unitGroupB );
+        
+        unitGroupC.getMembers().add( unitA );
+        unitA.getGroups().add( unitGroupC );
         
         organisationUnitGroupService.addOrganisationUnitGroup( unitGroupA );
         organisationUnitGroupService.addOrganisationUnitGroup( unitGroupB );
         organisationUnitGroupService.addOrganisationUnitGroup( unitGroupC );
+        organisationUnitGroupService.addOrganisationUnitGroup( unitGroupD );
         
         unitGroupSetA = createOrganisationUnitGroupSet( 'A' );
         unitGroupSetB = createOrganisationUnitGroupSet( 'B' );
@@ -209,10 +227,13 @@
         unitGroupSetA.setCompulsory( true );        
         unitGroupSetB.setCompulsory( false );
         
-        unitGroupSetA.getOrganisationUnitGroups().add( unitGroupA );    
+        unitGroupSetA.getOrganisationUnitGroups().add( unitGroupA );
+        unitGroupA.setGroupSet( unitGroupSetA );
         
-        unitGroupSetB.getOrganisationUnitGroups().add( unitGroupA );
         unitGroupSetB.getOrganisationUnitGroups().add( unitGroupB );
+        unitGroupSetB.getOrganisationUnitGroups().add( unitGroupC );
+        unitGroupB.setGroupSet( unitGroupSetB );
+        unitGroupC.setGroupSet( unitGroupSetB );
                 
         organisationUnitGroupService.addOrganisationUnitGroupSet( unitGroupSetA );
         organisationUnitGroupService.addOrganisationUnitGroupSet( unitGroupSetB );
@@ -235,7 +256,7 @@
     {
         Collection<DataElement> expected = dataIntegrityService.getDataElementsWithoutGroups();
         
-        assertTrue( equals( expected, elementB, elementC ) );
+        assertTrue( message( expected ), equals( expected, elementB, elementC ) );
     }
 
     @Test
@@ -253,7 +274,7 @@
     {
         Collection<DataSet> expected = dataIntegrityService.getDataSetsNotAssignedToOrganisationUnits();
         
-        assertTrue( equals( expected, dataSetB ) );
+        assertTrue( message( expected ), equals( expected, dataSetB ) );
     }
 
     @Test
@@ -261,7 +282,7 @@
     {
         Collection<Indicator> expected = dataIntegrityService.getIndicatorsWithIdenticalFormulas();
         
-        assertTrue( equals( expected, indicatorC ) );
+        assertTrue( message( expected ), equals( expected, indicatorC ) );
     }
 
     @Test
@@ -269,7 +290,7 @@
     {
         Collection<Indicator> expected = dataIntegrityService.getIndicatorsWithoutGroups();
         
-        assertTrue( equals( expected, indicatorB, indicatorC ) );
+        assertTrue( message( expected ), equals( expected, indicatorB, indicatorC ) );
     }
 
     @Test
@@ -277,7 +298,7 @@
     {
         Collection<OrganisationUnit> expected = dataIntegrityService.getOrganisationUnitsWithCyclicReferences();
         
-        assertTrue( equals( expected, unitA, unitB, unitC ) );
+        assertTrue( message( expected ), equals( expected, unitA, unitB, unitC ) );
     }
 
     @Test
@@ -285,7 +306,7 @@
     {
         Collection<OrganisationUnit> expected = dataIntegrityService.getOrphanedOrganisationUnits();
         
-        assertTrue( equals( expected, unitF ) );
+        assertTrue( message( expected ), equals( expected, unitF ) );
     }
 
     @Test
@@ -293,7 +314,7 @@
     {
         Collection<OrganisationUnit> expected = dataIntegrityService.getOrganisationUnitsWithoutGroups();
         
-        assertTrue( equals( expected, unitD, unitE ) );
+        assertTrue( message( expected ), equals( expected, unitD, unitE ) );
     }
 
     @Test
@@ -301,7 +322,7 @@
     {
         Collection<OrganisationUnit> expected = dataIntegrityService.getOrganisationUnitsViolatingCompulsoryGroupSets();
         
-        assertTrue( equals( expected, unitD, unitE, unitF ) );
+        assertTrue( message( expected ), equals( expected, unitD, unitE, unitF ) );
     }
 
     @Test
@@ -309,7 +330,7 @@
     {
         Collection<OrganisationUnit> expected = dataIntegrityService.getOrganisationUnitsViolatingExclusiveGroupSets();
         
-        assertTrue( equals( expected, unitA, unitB ) );
+        assertTrue( message( expected ), equals( expected, unitA ) );
     }
 
     @Test
@@ -317,6 +338,6 @@
     {
         Collection<OrganisationUnitGroup> expected = dataIntegrityService.getOrganisationUnitGroupsWithoutGroupSets();
         
-        assertTrue( equals( expected, unitGroupC ) );
+        assertTrue( message( expected ), equals( expected, unitGroupD ) );
     }
 }

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRule.hbm.xml'
--- dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRule.hbm.xml	2009-12-17 07:57:00 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRule.hbm.xml	2010-01-20 10:06:43 +0000
@@ -27,6 +27,11 @@
     <many-to-one name="rightSide" column="rightexpressionid" 
       class="org.hisp.dhis.expression.Expression" cascade="all"/>
       
+    <set name="groups" table="validationrulegroupmembers" inverse="true">
+      <key column="validationruleid"/>
+      <many-to-many class="org.hisp.dhis.validation.ValidationRuleGroup" column="validationgroupid"/>
+    </set>
+        
   </class>
 
 </hibernate-mapping>
\ No newline at end of file

=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/ListUtils.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/ListUtils.java	2010-01-19 23:07:17 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/ListUtils.java	2010-01-20 10:06:43 +0000
@@ -27,6 +27,7 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
@@ -73,7 +74,7 @@
      * @param comparator the comparator.
      * @return a set of duplicates from the given list.
      */
-    public static <T> Set<T> getDuplicates( List<T> list, Comparator<T> comparator )
+    public static <T> Collection<T> getDuplicates( List<T> list, Comparator<T> comparator )
     {
         Set<T> duplicates = new HashSet<T>();
         

=== modified file 'dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java'
--- dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java	2010-01-19 21:48:28 +0000
+++ dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java	2010-01-20 10:06:43 +0000
@@ -240,6 +240,11 @@
 
         return true;
     }
+    
+    public static String message( Object object )
+    {
+        return "Expected was: " + ( ( object != null ) ? "[" + object.toString() + "]" : "[null]" );
+    }
 
     // -------------------------------------------------------------------------
     // Dependency injection methods