← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 20475: Impl new framework for resource table generation. Better object-orientation. Creates table with _...

 

------------------------------------------------------------
revno: 20475
committer: Lars Helge Overland <larshelge@xxxxxxxxx>
branch nick: dhis2
timestamp: Fri 2015-10-02 15:58:22 +0200
message:
  Impl new framework for resource table generation. Better object-orientation. Creates table with _temp suffix, populates, then swaps with real table. Avoids gap when resource tables are not present during generation, causing havoc for sql views and system functions which depend on them.
added:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/resourcetable/ResourceTable.java
  dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/resourcetable/table/
  dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/resourcetable/table/CategoryOptionComboNameResourceTable.java
  dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/resourcetable/table/OrganisationUnitStructureResourceTable.java
modified:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/resourcetable/ResourceTableStore.java
  dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/resourcetable/DefaultResourceTableService.java
  dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/resourcetable/jdbc/JdbcResourceTableStore.java
  dhis-2/dhis-services/dhis-service-administration/src/main/resources/META-INF/dhis/beans.xml


--
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
=== added file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/resourcetable/ResourceTable.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/resourcetable/ResourceTable.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/resourcetable/ResourceTable.java	2015-10-02 13:58:22 +0000
@@ -0,0 +1,117 @@
+package org.hisp.dhis.resourcetable;
+
+/*
+ * Copyright (c) 2004-2015, 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.List;
+import java.util.Optional;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hisp.dhis.common.CodeGenerator;
+
+/**
+ * @author Lars Helge Overland
+ */
+public abstract class ResourceTable<T>
+{
+    protected static final Log log = LogFactory.getLog( ResourceTable.class );
+    
+    protected static final String TEMP_TABLE_SUFFIX = "_temp";
+    
+    protected String tableName;
+    
+    protected List<T> objects;
+    
+    protected String columnQuote;
+
+    // -------------------------------------------------------------------------
+    // Constructors
+    // -------------------------------------------------------------------------
+
+    protected ResourceTable()
+    {
+    }
+    
+    public ResourceTable( String tableName, List<T> objects, String columnQuote )
+    {
+        this.tableName = tableName;
+        this.objects = objects;
+        this.columnQuote = columnQuote;
+    }
+
+    // -------------------------------------------------------------------------
+    // Public methods
+    // -------------------------------------------------------------------------
+
+    public final String getTableName()
+    {
+        return tableName;
+    }
+    
+    public final String getTempTableName()
+    {
+        return tableName + TEMP_TABLE_SUFFIX;
+    }
+    
+    public final String getSwapTablesStatement()
+    {
+        final String sql = 
+            "drop table " + getTableName() + ";" +
+            "alter table " + getTempTableName() + " rename to " + getTableName() + ";";
+        
+        return sql;
+    }
+
+    // -------------------------------------------------------------------------
+    // Protected methods
+    // -------------------------------------------------------------------------
+
+    protected String getRandomSuffix()
+    {
+        return CodeGenerator.generateCode( 5 );
+    }
+    
+    // -------------------------------------------------------------------------
+    // Abstract methods
+    // -------------------------------------------------------------------------
+
+    public abstract String getCreateTempTableStatement();
+    
+    public abstract Optional<String> getPopulateTempTableStatement();
+    
+    public abstract Optional<List<Object[]>> getPopulateTempTableContent();
+    
+    /**
+     * Get a SQL index create statement. Note that the index name must have a 
+     * random component to avoid uniqueness conflicts.
+     * 
+     * @return an optional SQL statement.
+     */
+    public abstract Optional<String> getCreateIndexStatement();
+}

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/resourcetable/ResourceTableStore.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/resourcetable/ResourceTableStore.java	2015-09-16 14:49:50 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/resourcetable/ResourceTableStore.java	2015-10-02 13:58:22 +0000
@@ -54,6 +54,13 @@
     String TABLE_NAME_DATA_APPROVAL_MIN_LEVEL = "_dataapprovalminlevel";
     
     /**
+     * Generates the given resource table.
+     * 
+     * @param resourceTable the resource table.
+     */
+    void generateResourceTable( ResourceTable<?> resourceTable );
+    
+    /**
      * Performs a batch update.
      * 
      * @param columns the number of columns in the table to update.

=== modified file 'dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/resourcetable/DefaultResourceTableService.java'
--- dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/resourcetable/DefaultResourceTableService.java	2015-09-23 12:23:52 +0000
+++ dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/resourcetable/DefaultResourceTableService.java	2015-10-02 13:58:22 +0000
@@ -29,19 +29,15 @@
  */
 
 import static org.hisp.dhis.dataapproval.DataApprovalLevelService.APPROVAL_LEVEL_HIGHEST;
-import static org.hisp.dhis.resourcetable.ResourceTableStore.TABLE_NAME_CATEGORY_OPTION_COMBO_NAME;
 import static org.hisp.dhis.resourcetable.ResourceTableStore.TABLE_NAME_DATA_ELEMENT_STRUCTURE;
 import static org.hisp.dhis.resourcetable.ResourceTableStore.TABLE_NAME_DATE_PERIOD_STRUCTURE;
-import static org.hisp.dhis.resourcetable.ResourceTableStore.TABLE_NAME_ORGANISATION_UNIT_STRUCTURE;
 import static org.hisp.dhis.resourcetable.ResourceTableStore.TABLE_NAME_PERIOD_STRUCTURE;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 import org.apache.commons.logging.Log;
@@ -62,7 +58,7 @@
 import org.hisp.dhis.dataelement.DataElementGroupSet;
 import org.hisp.dhis.dataset.DataSet;
 import org.hisp.dhis.indicator.IndicatorGroupSet;
-import org.hisp.dhis.organisationunit.OrganisationUnit;
+import org.hisp.dhis.jdbc.StatementBuilder;
 import org.hisp.dhis.organisationunit.OrganisationUnitGroupSet;
 import org.hisp.dhis.organisationunit.OrganisationUnitLevel;
 import org.hisp.dhis.organisationunit.OrganisationUnitService;
@@ -72,6 +68,8 @@
 import org.hisp.dhis.period.PeriodService;
 import org.hisp.dhis.period.PeriodType;
 import org.hisp.dhis.resourcetable.statement.CreateCategoryOptionGroupSetTableStatement;
+import org.hisp.dhis.resourcetable.table.CategoryOptionComboNameResourceTable;
+import org.hisp.dhis.resourcetable.table.OrganisationUnitStructureResourceTable;
 import org.hisp.dhis.sqlview.SqlView;
 import org.hisp.dhis.sqlview.SqlViewService;
 import org.springframework.transaction.annotation.Transactional;
@@ -136,100 +134,33 @@
     {
         this.dataApprovalLevelService = dataApprovalLevelService;
     }
-
+    
+    private StatementBuilder statementBuilder;
+    
+    public void setStatementBuilder( StatementBuilder statementBuilder )
+    {
+        this.statementBuilder = statementBuilder;
+    }
+    
     // -------------------------------------------------------------------------
-    // OrganisationUnitStructure
+    // ResourceTableService implementation
     // -------------------------------------------------------------------------
 
     @Override
     @Transactional
     public void generateOrganisationUnitStructures()
     {
-        int maxLevel = organisationUnitService.getMaxOfOrganisationUnitLevels();
-
-        log.info( "Using " + maxLevel + " organisation unit levels for org unit structure table" );
-        
-        resourceTableStore.createOrganisationUnitStructure( maxLevel );
-
-        List<Object[]> batchArgs = new ArrayList<>();
-
-        for ( int i = 0; i < maxLevel; i++ )
-        {
-            int level = i + 1;
-
-            Collection<OrganisationUnit> units = organisationUnitService.getOrganisationUnitsAtLevel( level );
-
-            for ( OrganisationUnit unit : units )
-            {
-                List<Object> values = new ArrayList<>();
-
-                values.add( unit.getId() );
-                values.add( unit.getUid() );
-                values.add( level );
-
-                Map<Integer, Integer> identifiers = new HashMap<>();
-                Map<Integer, String> uids = new HashMap<>();
-
-                for ( int j = level; j > 0; j-- )
-                {
-                    identifiers.put( j, unit.getId() );
-                    uids.put( j, unit.getUid() );
-
-                    unit = unit.getParent();
-                }
-
-                for ( int k = 1; k <= maxLevel; k++ )
-                {
-                    values.add( identifiers.get( k ) != null ? identifiers.get( k ) : null );
-                    values.add( uids.get( k ) );
-                }
-
-                batchArgs.add( values.toArray() );
-            }
-        }
-
-        resourceTableStore.batchUpdate( (maxLevel * 2) + 3, TABLE_NAME_ORGANISATION_UNIT_STRUCTURE, batchArgs );
-
-        log.info( "Organisation unit structure table generated" );
+        resourceTableStore.generateResourceTable( new OrganisationUnitStructureResourceTable( 
+            "_orgunitstructure", null, statementBuilder.getColumnQuote(), 
+            organisationUnitService, organisationUnitService.getMaxOfOrganisationUnitLevels() ) );
     }
-
-    // -------------------------------------------------------------------------
-    // DataElementCategoryOptionComboName
-    // -------------------------------------------------------------------------
-
+    
     @Override
     @Transactional
     public void generateCategoryOptionComboNames()
     {
-        resourceTableStore.createDataElementCategoryOptionComboName();
-
-        Collection<DataElementCategoryCombo> combos = categoryService.getAllDataElementCategoryCombos();
-
-        List<Object[]> batchArgs = new ArrayList<>();
-
-        for ( DataElementCategoryCombo combo : combos )
-        {
-            if ( !combo.isValid() )
-            {
-                log.warn( "Ignoring category combo, not valid: " + combo );
-                continue;
-            }
-            
-            for ( DataElementCategoryOptionCombo coc : combo.getSortedOptionCombos() )
-            {
-                List<Object> values = new ArrayList<>();
-                
-                values.add( coc.getId() );
-                values.add( coc.getName() );
-                values.add( coc.isIgnoreApproval() ? APPROVAL_LEVEL_HIGHEST : null );
-
-                batchArgs.add( values.toArray() );
-            }
-        }
-
-        resourceTableStore.batchUpdate( 3, TABLE_NAME_CATEGORY_OPTION_COMBO_NAME, batchArgs );
-
-        log.info( "Category option combo name table generated" );
+        resourceTableStore.generateResourceTable( new CategoryOptionComboNameResourceTable( 
+            "_categoryoptioncomboname", idObjectManager.getAll( DataElementCategoryCombo.class ), statementBuilder.getColumnQuote() ) );
     }
 
     @Override

=== modified file 'dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/resourcetable/jdbc/JdbcResourceTableStore.java'
--- dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/resourcetable/jdbc/JdbcResourceTableStore.java	2015-09-23 13:58:30 +0000
+++ dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/resourcetable/jdbc/JdbcResourceTableStore.java	2015-10-02 13:58:22 +0000
@@ -29,10 +29,12 @@
  */
 
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.hisp.dhis.commons.util.TextUtils;
 import org.hisp.dhis.dataelement.CategoryOptionGroupSet;
 import org.hisp.dhis.dataelement.DataElementCategory;
 import org.hisp.dhis.dataelement.DataElementGroupSet;
@@ -47,7 +49,7 @@
 import org.hisp.dhis.resourcetable.statement.CreateDataElementGroupSetTableStatement;
 import org.hisp.dhis.resourcetable.statement.CreateIndicatorGroupSetTableStatement;
 import org.hisp.dhis.resourcetable.statement.CreateOrganisationUnitGroupSetTableStatement;
-import org.hisp.dhis.commons.util.TextUtils;
+import org.hisp.dhis.resourcetable.ResourceTable;
 import org.springframework.jdbc.BadSqlGrammarException;
 import org.springframework.jdbc.core.JdbcTemplate;
 
@@ -81,6 +83,65 @@
     // ResourceTableStore implementation
     // -------------------------------------------------------------------------
 
+    public void generateResourceTable( ResourceTable<?> resourceTable )
+    {
+        final String createTableSql = resourceTable.getCreateTempTableStatement();
+        final Optional<String> populateTableSql = resourceTable.getPopulateTempTableStatement();
+        final Optional<List<Object[]>> populateTableContent = resourceTable.getPopulateTempTableContent();
+        final Optional<String> createIndexSql = resourceTable.getCreateIndexStatement();
+
+        // ---------------------------------------------------------------------
+        // Create table
+        // ---------------------------------------------------------------------
+
+        log.info( "Create table SQL: " + createTableSql );
+        
+        jdbcTemplate.execute( createTableSql );
+
+        // ---------------------------------------------------------------------
+        // Populate table
+        // ---------------------------------------------------------------------
+
+        if ( populateTableSql.isPresent() )
+        {
+            log.info( "Populate table SQL: " + populateTableSql.get() );
+            
+            jdbcTemplate.execute( populateTableSql.get() );
+        }
+        else if ( populateTableContent.isPresent() )
+        {
+            List<Object[]> content = populateTableContent.get();
+            
+            log.info( "Populate table content rows: " + content.size() );
+            
+            if ( content.size() > 0 )
+            {
+                int columns = content.get( 0 ).length;
+                
+                batchUpdate( columns, resourceTable.getTempTableName(), content );
+            }
+        }
+
+        // ---------------------------------------------------------------------
+        // Create index
+        // ---------------------------------------------------------------------
+
+        if ( createIndexSql.isPresent() )
+        {
+            log.info( "Create index SQL: " + createIndexSql.get() );
+            
+            jdbcTemplate.execute( createIndexSql.get() );
+        }
+        
+        // ---------------------------------------------------------------------
+        // Swap tables
+        // ---------------------------------------------------------------------
+
+        jdbcTemplate.execute( resourceTable.getSwapTablesStatement() );
+        
+        log.info( "Swapped resource table, done: " + resourceTable.getTableName() );
+    }
+    
     @Override
     public void batchUpdate( int columns, String tableName, List<Object[]> batchArgs )
     {

=== added directory 'dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/resourcetable/table'
=== added file 'dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/resourcetable/table/CategoryOptionComboNameResourceTable.java'
--- dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/resourcetable/table/CategoryOptionComboNameResourceTable.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/resourcetable/table/CategoryOptionComboNameResourceTable.java	2015-10-02 13:58:22 +0000
@@ -0,0 +1,99 @@
+package org.hisp.dhis.resourcetable.table;
+
+/*
+ * Copyright (c) 2004-2015, 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.dataapproval.DataApprovalLevelService.APPROVAL_LEVEL_HIGHEST;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import org.hisp.dhis.dataelement.DataElementCategoryCombo;
+import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
+import org.hisp.dhis.resourcetable.ResourceTable;
+
+/**
+ * @author Lars Helge Overland
+ */
+public class CategoryOptionComboNameResourceTable
+    extends ResourceTable<DataElementCategoryCombo>
+{
+    public CategoryOptionComboNameResourceTable( String tableName, List<DataElementCategoryCombo> objects, String columnQuote )
+    {
+        super( tableName, objects, columnQuote );
+    }
+    
+    @Override
+    public String getCreateTempTableStatement()
+    {
+        return "create table " + getTempTableName() + 
+            " (categoryoptioncomboid integer not null primary key, " +
+            "categoryoptioncomboname varchar(255), approvallevel integer)";
+    }
+
+    @Override
+    public Optional<String> getPopulateTempTableStatement()
+    {
+        return Optional.empty();
+    }
+
+    @Override
+    public Optional<List<Object[]>> getPopulateTempTableContent()
+    {
+        List<Object[]> batchArgs = new ArrayList<>();
+
+        for ( DataElementCategoryCombo combo : objects )
+        {
+            if ( !combo.isValid() )
+            {
+                log.warn( "Ignoring category combo, not valid: " + combo );
+                continue;
+            }
+            
+            for ( DataElementCategoryOptionCombo coc : combo.getSortedOptionCombos() )
+            {
+                List<Object> values = new ArrayList<>();
+                
+                values.add( coc.getId() );
+                values.add( coc.getName() );
+                values.add( coc.isIgnoreApproval() ? APPROVAL_LEVEL_HIGHEST : null );
+
+                batchArgs.add( values.toArray() );
+            }
+        }
+        
+        return Optional.of( batchArgs );
+    }
+
+    @Override
+    public Optional<String> getCreateIndexStatement()
+    {
+        return Optional.empty();
+    }
+}

=== added file 'dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/resourcetable/table/OrganisationUnitStructureResourceTable.java'
--- dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/resourcetable/table/OrganisationUnitStructureResourceTable.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/resourcetable/table/OrganisationUnitStructureResourceTable.java	2015-10-02 13:58:22 +0000
@@ -0,0 +1,135 @@
+package org.hisp.dhis.resourcetable.table;
+
+/*
+ * Copyright (c) 2004-2015, 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.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.hisp.dhis.organisationunit.OrganisationUnit;
+import org.hisp.dhis.organisationunit.OrganisationUnitService;
+import org.hisp.dhis.resourcetable.ResourceTable;
+
+/**
+ * @author Lars Helge Overland
+ */
+public class OrganisationUnitStructureResourceTable
+    extends ResourceTable<OrganisationUnit>
+{
+    private OrganisationUnitService organisationUnitService; // Nasty
+    
+    private int organisationUnitLevels;
+    
+    public OrganisationUnitStructureResourceTable( String tableName, List<OrganisationUnit> objects, 
+        String columnQuote, OrganisationUnitService organisationUnitService, int organisationUnitLevels )
+    {
+        super( tableName, objects, columnQuote );
+        this.organisationUnitService = organisationUnitService;
+        this.organisationUnitLevels = organisationUnitLevels;
+    }
+    
+    @Override
+    public String getCreateTempTableStatement()
+    {
+        StringBuilder sql = new StringBuilder();
+        
+        sql.append( "create table " ).append( getTempTableName() ).
+            append( " (organisationunitid integer not null primary key, organisationunituid character(11), level integer" );
+        
+        for ( int k = 1 ; k <= organisationUnitLevels; k++ )
+        {
+            sql.append( ", " ).append( columnQuote ).append( "idlevel" + k ).append( columnQuote ).append (" integer, " ).
+                append( columnQuote ).append( "uidlevel" + k ).append( columnQuote ).append( " character(11)" );
+        }
+        
+        return sql.append( ");" ).toString();
+    }
+
+    @Override
+    public Optional<String> getPopulateTempTableStatement()
+    {
+        return Optional.empty();
+    }
+
+    @Override
+    public Optional<List<Object[]>> getPopulateTempTableContent()
+    {
+        List<Object[]> batchArgs = new ArrayList<>();
+
+        for ( int i = 0; i < organisationUnitLevels; i++ )
+        {
+            int level = i + 1;
+
+            Collection<OrganisationUnit> units = organisationUnitService.getOrganisationUnitsAtLevel( level );
+
+            for ( OrganisationUnit unit : units )
+            {
+                List<Object> values = new ArrayList<>();
+
+                values.add( unit.getId() );
+                values.add( unit.getUid() );
+                values.add( level );
+
+                Map<Integer, Integer> identifiers = new HashMap<>();
+                Map<Integer, String> uids = new HashMap<>();
+
+                for ( int j = level; j > 0; j-- )
+                {
+                    identifiers.put( j, unit.getId() );
+                    uids.put( j, unit.getUid() );
+
+                    unit = unit.getParent();
+                }
+
+                for ( int k = 1; k <= organisationUnitLevels; k++ )
+                {
+                    values.add( identifiers.get( k ) != null ? identifiers.get( k ) : null );
+                    values.add( uids.get( k ) );
+                }
+
+                batchArgs.add( values.toArray() );
+            }
+        }
+        
+        return Optional.of( batchArgs );
+    }
+
+    @Override
+    public Optional<String> getCreateIndexStatement()
+    {
+        String name = "in_orgunitstructure_organisationunituid_" + getRandomSuffix();
+        
+        String sql = "create unique index " + name + " on " + getTempTableName() + "(organisationunituid)";
+        
+        return Optional.of( sql );
+    }
+}

=== modified file 'dhis-2/dhis-services/dhis-service-administration/src/main/resources/META-INF/dhis/beans.xml'
--- dhis-2/dhis-services/dhis-service-administration/src/main/resources/META-INF/dhis/beans.xml	2015-09-24 07:35:03 +0000
+++ dhis-2/dhis-services/dhis-service-administration/src/main/resources/META-INF/dhis/beans.xml	2015-10-02 13:58:22 +0000
@@ -17,6 +17,7 @@
     <property name="periodService" ref="org.hisp.dhis.period.PeriodService" />
     <property name="sqlViewService" ref="org.hisp.dhis.sqlview.SqlViewService" />
     <property name="dataApprovalLevelService" ref="org.hisp.dhis.dataapproval.DataApprovalLevelService" />
+    <property name="statementBuilder" ref="statementBuilder" />
   </bean>
   
   <!-- Data integrity -->