=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/BaseAnalyticalObject.java' --- dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/BaseAnalyticalObject.java 2013-05-23 13:38:20 +0000 +++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/BaseAnalyticalObject.java 2013-05-23 16:57:03 +0000 @@ -35,10 +35,12 @@ import static org.hisp.dhis.common.DimensionalObject.ORGUNIT_DIM_ID; import static org.hisp.dhis.common.DimensionalObject.PERIOD_DIM_ID; import static org.hisp.dhis.common.DimensionalObject.CATEGORYOPTIONCOMBO_DIM_ID; +import static org.hisp.dhis.common.DimensionalObject.DIMENSION_SEP; import static org.hisp.dhis.organisationunit.OrganisationUnit.KEY_USER_ORGUNIT; import static org.hisp.dhis.organisationunit.OrganisationUnit.KEY_USER_ORGUNIT_CHILDREN; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -46,6 +48,7 @@ import java.util.List; import java.util.Map; +import org.apache.commons.lang.StringUtils; import org.hisp.dhis.common.adapter.JacksonPeriodDeserializer; import org.hisp.dhis.common.adapter.JacksonPeriodSerializer; import org.hisp.dhis.common.annotation.Scanned; @@ -406,21 +409,60 @@ return categoryDims; } + /** + * Splits the keys of the given map on the dimension identifier separator, + * sorts the identifiers, writes them out as a key and puts the key back into + * the map. + */ + public static void sortKeys( Map valueMap ) + { + Map map = new HashMap(); + + for ( String key : valueMap.keySet() ) + { + if ( key != null ) + { + String[] ids = key.split( DIMENSION_SEP ); + + Collections.sort( Arrays.asList( ids ) ); + + String sortedKey = StringUtils.join( ids, DIMENSION_SEP ); + + map.put( sortedKey, valueMap.get( key ) ); + } + } + + valueMap.clear(); + valueMap.putAll( map ); + } + + /** + * Generates an identifier based on the given lists of NameableObjects. Uses + * the UIDs for each NameableObject, sorts them and writes them out as a key. + */ public static String getIdentifer( List column, List row ) { - StringBuilder id = new StringBuilder(); - - for ( NameableObject item : column ) - { - id.append( item.getUid() ).append( "-" ); - } - - for ( NameableObject item : row ) - { - id.append( item.getUid() ).append( "-" ); - } - - return id.substring( 0, id.length() - 1 ); + List ids = new ArrayList(); + + if ( column != null ) + { + for ( NameableObject item : column ) + { + ids.add( item.getUid() ); + } + } + + if ( row != null ) + { + for ( NameableObject item : row ) + { + ids.add( item.getUid() ); + } + } + + Collections.sort( ids ); + + return StringUtils.join( ids, DIMENSION_SEP ); } public void mergeWith( BaseAnalyticalObject other ) === modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/BaseNameableObject.java' --- dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/BaseNameableObject.java 2013-05-23 13:38:20 +0000 +++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/BaseNameableObject.java 2013-05-23 16:57:03 +0000 @@ -172,7 +172,7 @@ { this.displayDescription = displayDescription; } - + @Override public void mergeWith( IdentifiableObject other ) { === modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/DimensionalObject.java' --- dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/DimensionalObject.java 2013-05-23 13:38:20 +0000 +++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/DimensionalObject.java 2013-05-23 16:57:03 +0000 @@ -44,6 +44,8 @@ final String CATEGORYOPTIONCOMBO_DIM_ID = "co"; final String PERIOD_DIM_ID = "pe"; final String ORGUNIT_DIM_ID = "ou"; + + final String DIMENSION_SEP = "-"; final List DATA_X_DIMS = Arrays.asList( INDICATOR_DIM_ID, DATAELEMENT_DIM_ID, DATASET_DIM_ID, DATAELEMENT_OPERAND_ID ); === modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/reporttable/ReportTable.java' --- dhis-2/dhis-api/src/main/java/org/hisp/dhis/reporttable/ReportTable.java 2013-05-23 12:59:00 +0000 +++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/reporttable/ReportTable.java 2013-05-23 16:57:03 +0000 @@ -616,6 +616,10 @@ final String subtitle = StringUtils.trimToEmpty( getParentOrganisationUnitName() ) + SPACE + StringUtils.trimToEmpty( reportingPeriodName ); + valueMap = new HashMap( valueMap ); + + sortKeys( valueMap ); + grid.setTitle( name + " - " + subtitle ); // --------------------------------------------------------------------- @@ -684,7 +688,7 @@ for ( List column : gridColumns ) { - String key = BaseAnalyticalObject.getIdentifer( column, row ); + String key = getIdentifer( column, row ); Double value = valueMap.get( key ); === added file 'dhis-2/dhis-api/src/test/java/org/hisp/dhis/common/BaseAnalyticalObjectTest.java' --- dhis-2/dhis-api/src/test/java/org/hisp/dhis/common/BaseAnalyticalObjectTest.java 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-api/src/test/java/org/hisp/dhis/common/BaseAnalyticalObjectTest.java 2013-05-23 16:57:03 +0000 @@ -0,0 +1,112 @@ +package org.hisp.dhis.common; + +/* + * 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.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.hisp.dhis.dataelement.DataElementGroup; +import org.junit.Test; +import static org.junit.Assert.*; + +/** +* @author Lars Helge Overland +*/ +public class BaseAnalyticalObjectTest +{ + @Test + public void testSortKeys() + { + Map valueMap = new HashMap(); + + valueMap.put( "b1-a1-c1", 1d ); + valueMap.put( "a2-c2-b2", 2d ); + valueMap.put( "c3-b3-a3", 3d ); + valueMap.put( "a4-b4-c4", 4d ); + + BaseAnalyticalObject.sortKeys( valueMap ); + + assertEquals( 4, valueMap.size() ); + assertTrue( valueMap.containsKey( "a1-b1-c1" ) ); + assertTrue( valueMap.containsKey( "a2-b2-c2" ) ); + assertTrue( valueMap.containsKey( "a3-b3-c3" ) ); + assertTrue( valueMap.containsKey( "a4-b4-c4" ) ); + + assertEquals( 1d, valueMap.get( "a1-b1-c1" ), 0.01 ); + assertEquals( 2d, valueMap.get( "a2-b2-c2" ), 0.01 ); + assertEquals( 3d, valueMap.get( "a3-b3-c3" ), 0.01 ); + assertEquals( 4d, valueMap.get( "a4-b4-c4" ), 0.01 ); + + valueMap = new HashMap(); + + valueMap.put( "b1", 1d ); + valueMap.put( "b2", 2d ); + + BaseAnalyticalObject.sortKeys( valueMap ); + + assertEquals( 2, valueMap.size() ); + assertTrue( valueMap.containsKey( "b1" ) ); + assertTrue( valueMap.containsKey( "b2" ) ); + + assertEquals( 1d, valueMap.get( "b1" ), 0.01 ); + assertEquals( 2d, valueMap.get( "b2" ), 0.01 ); + + valueMap = new HashMap(); + + valueMap.put( null, 1d ); + + BaseAnalyticalObject.sortKeys( valueMap ); + + assertEquals( 0, valueMap.size() ); + } + + @Test + public void testGetIdentifier() + { + DataElementGroup oA = new DataElementGroup(); + DataElementGroup oB = new DataElementGroup(); + DataElementGroup oC = new DataElementGroup(); + + oA.setUid( "a1" ); + oB.setUid( "b1" ); + oC.setUid( "c1" ); + + List column = new ArrayList(); + column.add( oC ); + column.add( oA ); + + List row = new ArrayList(); + row.add( oB ); + + assertEquals( "a1-b1-c1", BaseAnalyticalObject.getIdentifer( column, row ) ); + assertEquals( "b1", BaseAnalyticalObject.getIdentifer( new ArrayList(), row ) ); + assertEquals( "b1", BaseAnalyticalObject.getIdentifer( null, row ) ); + } +} === modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/AnalyticsService.java' --- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/AnalyticsService.java 2013-05-19 18:49:47 +0000 +++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/AnalyticsService.java 2013-05-23 16:57:03 +0000 @@ -27,6 +27,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import java.util.List; import java.util.Map; import java.util.Set; @@ -48,6 +49,20 @@ * @return aggregated data as a Grid object. */ Grid getAggregatedDataValues( DataQueryParams params ); + + /** + * Generates an aggregated value grid for the given query. The grid will + * represent a table with dimensions used as columns and rows as specified + * in columnDimensions and rowDimensions arguments. + * + * @param params the data query parameters. + * @param tableLayout whether to render the grid as a table with columns and rows, + * or as a normalized plain data source. + * @param columns the identifiers of the dimensions to use as columns. + * @param rows the identifiers of the dimensions to use as rows. + * @return aggregated data as a Grid object. + */ + Grid getAggregatedDataValues( DataQueryParams params, boolean tableLayout, List columns, List rows ); /** * Generates a mapping where the key represents the dimensional item identifiers === modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DataQueryParams.java' --- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DataQueryParams.java 2013-05-23 13:38:20 +0000 +++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DataQueryParams.java 2013-05-23 16:57:03 +0000 @@ -33,11 +33,13 @@ import static org.hisp.dhis.common.DimensionType.ORGANISATIONUNIT_GROUPSET; import static org.hisp.dhis.common.DimensionalObject.CATEGORYOPTIONCOMBO_DIM_ID; import static org.hisp.dhis.common.DimensionalObject.DATAELEMENT_DIM_ID; +import static org.hisp.dhis.common.DimensionalObject.DATAELEMENT_OPERAND_ID; import static org.hisp.dhis.common.DimensionalObject.DATASET_DIM_ID; import static org.hisp.dhis.common.DimensionalObject.DATA_X_DIM_ID; import static org.hisp.dhis.common.DimensionalObject.INDICATOR_DIM_ID; import static org.hisp.dhis.common.DimensionalObject.ORGUNIT_DIM_ID; import static org.hisp.dhis.common.DimensionalObject.PERIOD_DIM_ID; +import static org.hisp.dhis.common.DimensionalObject.DIMENSION_SEP; import static org.hisp.dhis.common.NameableObjectUtils.asList; import static org.hisp.dhis.common.NameableObjectUtils.getList; import static org.hisp.dhis.system.util.CollectionUtils.emptyIfNull; @@ -86,9 +88,8 @@ private static final String DIMENSION_NAME_SEP = ":"; private static final String OPTION_SEP = ";"; - public static final String DIMENSION_SEP = "-"; - public static final List DATA_DIMS = Arrays.asList( INDICATOR_DIM_ID, DATAELEMENT_DIM_ID, DATASET_DIM_ID ); + public static final List DATA_DIMS = Arrays.asList( INDICATOR_DIM_ID, DATAELEMENT_DIM_ID, DATAELEMENT_OPERAND_ID, DATASET_DIM_ID ); public static final List FIXED_DIMS = Arrays.asList( DATA_X_DIM_ID, INDICATOR_DIM_ID, DATAELEMENT_DIM_ID, DATASET_DIM_ID, PERIOD_DIM_ID, ORGUNIT_DIM_ID ); public static final int MAX_DIM_OPT_PERM = 10000; @@ -571,7 +572,8 @@ } /** - * Retrieves the options for the given dimension identifier. + * Retrieves the options for the given dimension identifier. Returns null if + * the dimension is not present. */ public List getDimensionOptions( String dimension ) { @@ -581,7 +583,8 @@ } /** - * Retrieves the dimension with the given dimension identifier. + * Retrieves the dimension with the given dimension identifier. Returns null + * if the dimension is not present. */ public DimensionalObject getDimension( String dimension ) { @@ -707,6 +710,20 @@ } /** + * Splits the given string on the ; character and returns the items in a + * list. Returns null if the given string is null. + */ + public static List getDimensionsFromParam( String param ) + { + if ( param == null ) + { + return null; + } + + return Arrays.asList( param.split( OPTION_SEP ) ); + } + + /** * Retrieves the measure criteria from the given string. Criteria are separated * by the option separator, while the criterion filter and value are separated * with the dimension name separator. @@ -944,16 +961,56 @@ // Get and set helpers for dimensions or filter // ------------------------------------------------------------------------- + /** + * Retrieves the options for the the dimension or filter with the given + * identifier. + */ public List getDimensionOrFilter( String key ) { return getDimensionOptions( key ) != null ? getDimensionOptions( key ) : getFilterOptions( key ); } + /** + * Retrieves the options for the given dimension identifier. If the dx dimension + * is specified, all concrete dimensions (in|de|dc|ds) are returned as a single + * dimension. Returns an empty array if the dimension is not present. + */ + public NameableObject[] getDimensionArrayCollapseDx( String dimension ) + { + List items = new ArrayList(); + + if ( DATA_X_DIM_ID.equals( dimension ) ) + { + items.addAll( getDimensionOptionsNullSafe( INDICATOR_DIM_ID ) ); + items.addAll( getDimensionOptionsNullSafe( DATAELEMENT_DIM_ID ) ); + items.addAll( getDimensionOptionsNullSafe( DATAELEMENT_OPERAND_ID ) ); + items.addAll( getDimensionOptionsNullSafe( DATASET_DIM_ID ) ); + } + else + { + items.addAll( getDimensionOptionsNullSafe( dimension ) ); + } + + return items.toArray( new NameableObject[0] ); + } + + /** + * Retrieves the options for the given dimension identifier. Returns an empty + * list if the dimension is not present. + */ + public List getDimensionOptionsNullSafe( String dimension ) + { + return getDimensionOptions( dimension ) != null ? getDimensionOptions( dimension ) : new ArrayList(); + } + + /** + * Indicates whether a dimension or filter with the given identifier exists. + */ public boolean hasDimensionOrFilter( String key ) { return dimensions.indexOf( new BaseDimensionalObject( key ) ) != -1 || filters.indexOf( new BaseDimensionalObject( key ) ) != -1; } - + // ------------------------------------------------------------------------- // Get and set helpers for dimensions // ------------------------------------------------------------------------- === modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DimensionItem.java' --- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DimensionItem.java 2013-05-23 13:38:20 +0000 +++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DimensionItem.java 2013-05-23 16:57:03 +0000 @@ -34,6 +34,8 @@ import org.hisp.dhis.common.NameableObject; import org.hisp.dhis.system.util.CollectionUtils; +import static org.hisp.dhis.common.DimensionalObject.DIMENSION_SEP; + /** * @author Lars Helge Overland */ @@ -94,7 +96,7 @@ { for ( DimensionItem item : items ) { - builder.append( item.getItem().getUid() ).append( DataQueryParams.DIMENSION_SEP ); + builder.append( item.getItem().getUid() ).append( DIMENSION_SEP ); } builder.deleteCharAt( builder.length() - 1 ); === modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/QueryPlanner.java' --- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/QueryPlanner.java 2013-05-07 08:37:33 +0000 +++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/QueryPlanner.java 2013-05-23 16:57:03 +0000 @@ -46,6 +46,19 @@ throws IllegalQueryException; /** + * Validates whether the given table layout is valid for the given query. + * Throws an IllegalQueryException if the query is not valid with a + * descriptive message. Returns normally if the query is valid. + * + * @param params the query. + * @param columns the column dimension identifiers. + * @param rows the row dimension identifiers. + * @throws IllegalQueryException if the query is invalid. + */ + void validateTableLayout( DataQueryParams params, List columns, List rows ) + throws IllegalQueryException; + + /** * Creates a list of DataQueryParams. It is mandatory to group the queries by * the following criteria: 1) partition / year 2) period type 3) organisation * unit level. If the number of queries produced by this grouping is equal or === modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultAnalyticsService.java' --- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultAnalyticsService.java 2013-05-23 13:38:20 +0000 +++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultAnalyticsService.java 2013-05-23 16:57:03 +0000 @@ -30,7 +30,6 @@ import static org.hisp.dhis.analytics.AnalyticsTableManager.ANALYTICS_TABLE_NAME; import static org.hisp.dhis.analytics.AnalyticsTableManager.COMPLETENESS_TABLE_NAME; import static org.hisp.dhis.analytics.AnalyticsTableManager.COMPLETENESS_TARGET_TABLE_NAME; -import static org.hisp.dhis.analytics.DataQueryParams.DIMENSION_SEP; import static org.hisp.dhis.analytics.DataQueryParams.DISPLAY_NAME_CATEGORYOPTIONCOMBO; import static org.hisp.dhis.analytics.DataQueryParams.DISPLAY_NAME_DATA_X; import static org.hisp.dhis.analytics.DataQueryParams.DISPLAY_NAME_ORGUNIT; @@ -45,12 +44,15 @@ import static org.hisp.dhis.common.DimensionalObject.INDICATOR_DIM_ID; import static org.hisp.dhis.common.DimensionalObject.ORGUNIT_DIM_ID; import static org.hisp.dhis.common.DimensionalObject.PERIOD_DIM_ID; +import static org.hisp.dhis.common.DimensionalObject.DIMENSION_SEP; import static org.hisp.dhis.common.DimensionalObjectUtils.toDimension; +import static org.hisp.dhis.common.IdentifiableObjectUtils.getUids; import static org.hisp.dhis.common.NameableObjectUtils.asList; import static org.hisp.dhis.common.NameableObjectUtils.asTypedList; -import static org.hisp.dhis.common.IdentifiableObjectUtils.getUids; import static org.hisp.dhis.organisationunit.OrganisationUnit.KEY_USER_ORGUNIT; import static org.hisp.dhis.organisationunit.OrganisationUnit.KEY_USER_ORGUNIT_CHILDREN; +import static org.hisp.dhis.reporttable.ReportTable.IRT2D; +import static org.hisp.dhis.reporttable.ReportTable.addIfEmpty; import java.util.ArrayList; import java.util.Arrays; @@ -76,6 +78,7 @@ import org.hisp.dhis.analytics.QueryPlanner; import org.hisp.dhis.common.BaseAnalyticalObject; import org.hisp.dhis.common.BaseDimensionalObject; +import org.hisp.dhis.common.CombinationGenerator; import org.hisp.dhis.common.DimensionType; import org.hisp.dhis.common.DimensionalObject; import org.hisp.dhis.common.Grid; @@ -105,6 +108,7 @@ import org.hisp.dhis.period.RelativePeriodEnum; import org.hisp.dhis.period.RelativePeriods; import org.hisp.dhis.period.comparator.AscendingPeriodComparator; +import org.hisp.dhis.reporttable.ReportTable; import org.hisp.dhis.system.grid.ListGrid; import org.hisp.dhis.system.util.ConversionUtils; import org.hisp.dhis.system.util.DebugUtils; @@ -357,6 +361,54 @@ return grid; } + + @Override + public Grid getAggregatedDataValues( DataQueryParams params, boolean tableLayout, List columns, List rows ) + { + if ( !tableLayout ) + { + return getAggregatedDataValues( params ); + } + + queryPlanner.validateTableLayout( params, columns, rows ); + + Map valueMap = getAggregatedDataValueMapping( params ); + + ReportTable reportTable = new ReportTable(); + + List tableColumns = new ArrayList(); + List tableRows = new ArrayList(); + + if ( columns != null ) + { + for ( String dimension : columns ) + { + reportTable.getColumnDimensions().add( dimension ); + + tableColumns.add( params.getDimensionArrayCollapseDx( dimension ) ); + } + } + + if ( rows != null ) + { + for ( String dimension : rows ) + { + reportTable.getRowDimensions().add( dimension ); + + tableRows.add( params.getDimensionArrayCollapseDx( dimension ) ); + } + } + + reportTable.setGridColumns( new CombinationGenerator( tableColumns.toArray( IRT2D ) ).getCombinations() ); + reportTable.setGridRows( new CombinationGenerator( tableRows.toArray( IRT2D ) ).getCombinations() ); + + addIfEmpty( reportTable.getGridColumns() ); + addIfEmpty( reportTable.getGridRows() ); + + Grid grid = reportTable.getGrid( new ListGrid(), valueMap, false ); + + return grid; + } @Override public Map getAggregatedDataValueMapping( DataQueryParams params ) === modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultQueryPlanner.java' --- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultQueryPlanner.java 2013-05-23 13:38:20 +0000 +++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultQueryPlanner.java 2013-05-23 16:57:03 +0000 @@ -140,6 +140,45 @@ } } + public void validateTableLayout( DataQueryParams params, List columns, List rows ) + { + String violation = null; + + if ( ( columns == null || columns.isEmpty() ) && ( rows == null || rows.isEmpty() ) ) + { + violation = "Cannot generate table layout when columns and rows are empty"; + } + + if ( columns != null ) + { + for ( String column : columns ) + { + if ( params.getDimensionArrayCollapseDx( column ).length == 0 ) + { + violation = "Column must be present as dimension in query: " + column; + } + } + } + + if ( rows != null ) + { + for ( String row : rows ) + { + if ( params.getDimensionArrayCollapseDx( row ).length == 0 ) + { + violation = "Row must be present as dimension in query: " + row; + } + } + } + + if ( violation != null ) + { + log.warn( "Validation failed: " + violation ); + + throw new IllegalQueryException( violation ); + } + } + public List planQuery( DataQueryParams params, int optimalQueries, String tableName ) { validate( params ); === modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/JdbcAnalyticsManager.java' --- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/JdbcAnalyticsManager.java 2013-05-23 13:38:20 +0000 +++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/JdbcAnalyticsManager.java 2013-05-23 16:57:03 +0000 @@ -31,7 +31,7 @@ import static org.hisp.dhis.analytics.AggregationType.AVERAGE_INT; import static org.hisp.dhis.analytics.AggregationType.AVERAGE_INT_DISAGGREGATION; import static org.hisp.dhis.analytics.AggregationType.COUNT; -import static org.hisp.dhis.analytics.DataQueryParams.DIMENSION_SEP; +import static org.hisp.dhis.common.DimensionalObject.DIMENSION_SEP; import static org.hisp.dhis.analytics.DataQueryParams.VALUE_ID; import static org.hisp.dhis.analytics.MeasureFilter.EQ; import static org.hisp.dhis.analytics.MeasureFilter.GE; === modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/MockAnalyticsService.java' --- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/MockAnalyticsService.java 2013-05-19 18:49:47 +0000 +++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/MockAnalyticsService.java 2013-05-23 16:57:03 +0000 @@ -27,6 +27,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import java.util.List; import java.util.Map; import java.util.Set; @@ -56,7 +57,13 @@ { throw new NotImplementedException(); } - + + @Override + public Grid getAggregatedDataValues( DataQueryParams params, boolean tableLayout, List columns, List rows ) + { + throw new NotImplementedException(); + } + @Override public Map getAggregatedDataValueMapping( DataQueryParams params ) { === modified file 'dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/DimensionOptionTest.java' --- dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/DimensionOptionTest.java 2013-05-07 08:37:33 +0000 +++ dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/DimensionOptionTest.java 2013-05-23 16:57:03 +0000 @@ -27,6 +27,14 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import static org.hisp.dhis.common.DimensionalObject.DATAELEMENT_DIM_ID; +import static org.hisp.dhis.common.DimensionalObject.DIMENSION_SEP; +import static org.hisp.dhis.common.DimensionalObject.ORGUNIT_DIM_ID; +import static org.hisp.dhis.common.DimensionalObject.PERIOD_DIM_ID; +import static org.hisp.dhis.system.util.TextUtils.EMPTY; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + import java.util.ArrayList; import java.util.List; @@ -37,11 +45,6 @@ import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.*; -import static org.hisp.dhis.analytics.DataQueryParams.*; -import static org.hisp.dhis.system.util.TextUtils.EMPTY; -import static org.hisp.dhis.common.DimensionalObject.*; - /** * @author Lars Helge Overland */ === modified file 'dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/QueryPlannerTest.java' --- dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/QueryPlannerTest.java 2013-05-23 13:38:20 +0000 +++ dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/QueryPlannerTest.java 2013-05-23 16:57:03 +0000 @@ -28,7 +28,7 @@ */ import static org.hisp.dhis.analytics.AnalyticsTableManager.ANALYTICS_TABLE_NAME; -import static org.hisp.dhis.analytics.DataQueryParams.DIMENSION_SEP; +import static org.hisp.dhis.common.DimensionalObject.DIMENSION_SEP; import static org.hisp.dhis.common.DimensionalObject.DATA_X_DIM_ID; import static org.hisp.dhis.common.DimensionalObject.ORGUNIT_DIM_ID; import static org.hisp.dhis.common.DimensionalObject.PERIOD_DIM_ID; === modified file 'dhis-2/dhis-services/dhis-service-reporting/src/main/java/org/hisp/dhis/chart/impl/DefaultChartService.java' --- dhis-2/dhis-services/dhis-service-reporting/src/main/java/org/hisp/dhis/chart/impl/DefaultChartService.java 2013-05-21 11:39:31 +0000 +++ dhis-2/dhis-services/dhis-service-reporting/src/main/java/org/hisp/dhis/chart/impl/DefaultChartService.java 2013-05-23 16:57:03 +0000 @@ -34,6 +34,7 @@ import static org.hisp.dhis.chart.Chart.TYPE_PIE; import static org.hisp.dhis.chart.Chart.TYPE_STACKED_BAR; import static org.hisp.dhis.chart.Chart.TYPE_STACKED_COLUMN; +import static org.hisp.dhis.common.DimensionalObject.DIMENSION_SEP; import static org.hisp.dhis.system.util.ConversionUtils.getArray; import java.awt.BasicStroke; @@ -53,7 +54,6 @@ import org.apache.commons.math.analysis.UnivariateRealInterpolator; import org.apache.commons.math.stat.regression.SimpleRegression; import org.hisp.dhis.analytics.AnalyticsService; -import org.hisp.dhis.analytics.DataQueryParams; import org.hisp.dhis.chart.Chart; import org.hisp.dhis.chart.ChartService; import org.hisp.dhis.chart.ChartStore; @@ -735,7 +735,7 @@ { categoryIndex++; - String key = series.getUid() + DataQueryParams.DIMENSION_SEP + category.getUid(); + String key = series.getUid() + DIMENSION_SEP + category.getUid(); Double value = valueMap.get( key ); === modified file 'dhis-2/dhis-services/dhis-service-reporting/src/test/java/org/hisp/dhis/reporttable/ReportTableGridTest.java' --- dhis-2/dhis-services/dhis-service-reporting/src/test/java/org/hisp/dhis/reporttable/ReportTableGridTest.java 2013-05-20 06:10:29 +0000 +++ dhis-2/dhis-services/dhis-service-reporting/src/test/java/org/hisp/dhis/reporttable/ReportTableGridTest.java 2013-05-23 16:57:03 +0000 @@ -27,7 +27,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import static org.hisp.dhis.analytics.DataQueryParams.DIMENSION_SEP; +import static org.hisp.dhis.common.DimensionalObject.DIMENSION_SEP; import static org.junit.Assert.assertEquals; import java.util.ArrayList; === modified file 'dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/AnalyticsController.java' --- dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/AnalyticsController.java 2013-04-04 18:06:19 +0000 +++ dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/AnalyticsController.java 2013-05-23 16:57:03 +0000 @@ -28,6 +28,7 @@ */ import static org.hisp.dhis.analytics.AnalyticsService.NAMES_META_KEY; +import static org.hisp.dhis.analytics.DataQueryParams.getDimensionsFromParam; import java.util.Map; import java.util.Set; @@ -75,13 +76,16 @@ @RequestParam(required = false) Set filter, @RequestParam(required = false) AggregationType aggregationType, @RequestParam(required = false) String measureCriteria, + @RequestParam(required = false) boolean tableLayout, + @RequestParam(required = false) String columns, + @RequestParam(required = false) String rows, Model model, HttpServletResponse response ) throws Exception { DataQueryParams params = analyticsService.getFromUrl( dimension, filter, aggregationType, measureCriteria, i18nManager.getI18nFormat() ); contextUtils.configureResponse( response, ContextUtils.CONTENT_TYPE_JSON, CacheStrategy.RESPECT_SYSTEM_SETTING ); - Grid grid = analyticsService.getAggregatedDataValues( params ); + Grid grid = analyticsService.getAggregatedDataValues( params, tableLayout, getDimensionsFromParam( columns ), getDimensionsFromParam( rows ) ); model.addAttribute( "model", grid ); model.addAttribute( "viewClass", "detailed" ); return "grid"; @@ -93,13 +97,16 @@ @RequestParam(required = false) Set filter, @RequestParam(required = false) AggregationType aggregationType, @RequestParam(required = false) String measureCriteria, + @RequestParam(required = false) boolean tableLayout, + @RequestParam(required = false) String columns, + @RequestParam(required = false) String rows, Model model, HttpServletResponse response ) throws Exception { DataQueryParams params = analyticsService.getFromUrl( dimension, filter, aggregationType, measureCriteria, i18nManager.getI18nFormat() ); contextUtils.configureResponse( response, ContextUtils.CONTENT_TYPE_XML, CacheStrategy.RESPECT_SYSTEM_SETTING ); - Grid grid = analyticsService.getAggregatedDataValues( params ); + Grid grid = analyticsService.getAggregatedDataValues( params, tableLayout, getDimensionsFromParam( columns ), getDimensionsFromParam( rows ) ); GridUtils.toXml( grid, response.getOutputStream() ); } @@ -109,13 +116,16 @@ @RequestParam(required = false) Set filter, @RequestParam(required = false) AggregationType aggregationType, @RequestParam(required = false) String measureCriteria, + @RequestParam(required = false) boolean tableLayout, + @RequestParam(required = false) String columns, + @RequestParam(required = false) String rows, Model model, HttpServletResponse response ) throws Exception { DataQueryParams params = analyticsService.getFromUrl( dimension, filter, aggregationType, measureCriteria, i18nManager.getI18nFormat() ); contextUtils.configureResponse( response, ContextUtils.CONTENT_TYPE_HTML, CacheStrategy.RESPECT_SYSTEM_SETTING ); - Grid grid = analyticsService.getAggregatedDataValues( params ); + Grid grid = analyticsService.getAggregatedDataValues( params, tableLayout, getDimensionsFromParam( columns ), getDimensionsFromParam( rows ) ); GridUtils.toHtml( substituteMetaData( grid ), response.getWriter() ); } @@ -125,13 +135,16 @@ @RequestParam(required = false) Set filter, @RequestParam(required = false) AggregationType aggregationType, @RequestParam(required = false) String measureCriteria, + @RequestParam(required = false) boolean tableLayout, + @RequestParam(required = false) String columns, + @RequestParam(required = false) String rows, Model model, HttpServletResponse response ) throws Exception { DataQueryParams params = analyticsService.getFromUrl( dimension, filter, aggregationType, measureCriteria, i18nManager.getI18nFormat() ); contextUtils.configureResponse( response, ContextUtils.CONTENT_TYPE_CSV, CacheStrategy.RESPECT_SYSTEM_SETTING, "data.csv", true ); - Grid grid = analyticsService.getAggregatedDataValues( params ); + Grid grid = analyticsService.getAggregatedDataValues( params, tableLayout, getDimensionsFromParam( columns ), getDimensionsFromParam( rows ) ); GridUtils.toCsv( substituteMetaData( grid ), response.getOutputStream() ); } @@ -141,13 +154,16 @@ @RequestParam(required = false) Set filter, @RequestParam(required = false) AggregationType aggregationType, @RequestParam(required = false) String measureCriteria, + @RequestParam(required = false) boolean tableLayout, + @RequestParam(required = false) String columns, + @RequestParam(required = false) String rows, Model model, HttpServletResponse response ) throws Exception { DataQueryParams params = analyticsService.getFromUrl( dimension, filter, aggregationType, measureCriteria, i18nManager.getI18nFormat() ); contextUtils.configureResponse( response, ContextUtils.CONTENT_TYPE_EXCEL, CacheStrategy.RESPECT_SYSTEM_SETTING, "data.xls", true ); - Grid grid = analyticsService.getAggregatedDataValues( params ); + Grid grid = analyticsService.getAggregatedDataValues( params, tableLayout, getDimensionsFromParam( columns ), getDimensionsFromParam( rows ) ); GridUtils.toXls( substituteMetaData( grid ), response.getOutputStream() ); }