=== added file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/RegexUtils.java' --- dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/RegexUtils.java 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/RegexUtils.java 2015-08-25 14:09:25 +0000 @@ -0,0 +1,64 @@ +package org.hisp.dhis.common; + +/* + * 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.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Lars Helge Overland + */ +public class RegexUtils +{ + /** + * Return the matches in the given input based on the given pattern. + * + * @param pattern the pattern. + * @param input the input. + * @param group the group, can be null. + * @return a set of matches. + */ + public static Set getMatches( Pattern pattern, String input, Integer group ) + { + group = group != null ? group : 0; + + Set set = new HashSet<>(); + + Matcher matcher = pattern.matcher( input ); + + while ( matcher.find() ) + { + set.add( matcher.group( group ) ); + } + + return set; + } +} === modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/program/ProgramIndicator.java' --- dhis-2/dhis-api/src/main/java/org/hisp/dhis/program/ProgramIndicator.java 2015-08-25 09:48:55 +0000 +++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/program/ProgramIndicator.java 2015-08-25 14:09:25 +0000 @@ -28,6 +28,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import java.util.Set; import java.util.regex.Pattern; import org.hisp.dhis.analytics.AggregationType; @@ -36,6 +37,7 @@ import org.hisp.dhis.common.DxfNamespaces; import org.hisp.dhis.common.IdentifiableObject; import org.hisp.dhis.common.MergeStrategy; +import org.hisp.dhis.common.RegexUtils; import org.hisp.dhis.common.view.DetailedView; import org.hisp.dhis.common.view.ExportView; @@ -44,6 +46,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import com.google.common.collect.Sets; /** * @author Chau Thu Tran @@ -132,7 +135,21 @@ { return aggregationType != null ? aggregationType : AggregationType.AVERAGE; } - + + /** + * Returns a set of data element and attribute identifiers part of the given + * input expression. + * + * @param input the expression. + * @return a set of UIDs. + */ + public static Set getDataElementAndAttributeIdentifiers( String input ) + { + return Sets.union( + RegexUtils.getMatches( DATAELEMENT_PATTERN, input, 2 ), + RegexUtils.getMatches( ATTRIBUTE_PATTERN, input, 1 ) ); + } + // ------------------------------------------------------------------------- // Getters && Setters // ------------------------------------------------------------------------- === modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/program/ProgramIndicatorService.java' --- dhis-2/dhis-api/src/main/java/org/hisp/dhis/program/ProgramIndicatorService.java 2015-08-21 03:14:08 +0000 +++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/program/ProgramIndicatorService.java 2015-08-25 14:09:25 +0000 @@ -33,6 +33,7 @@ import java.util.Set; import org.hisp.dhis.constant.Constant; +import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.trackedentity.TrackedEntityAttribute; /** @@ -156,7 +157,7 @@ * {@link ProgramIndicator.INVALID_VARIABLES_IN_EXPRESSION}. */ String filterIsValid( String filter ); - + /** * Get all {@link ProgramStageDataElement} part of the expression. * === added directory 'dhis-2/dhis-api/src/test/java/org/hisp/dhis/program' === added file 'dhis-2/dhis-api/src/test/java/org/hisp/dhis/program/ProgramIndicatorTest.java' --- dhis-2/dhis-api/src/test/java/org/hisp/dhis/program/ProgramIndicatorTest.java 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-api/src/test/java/org/hisp/dhis/program/ProgramIndicatorTest.java 2015-08-25 14:09:25 +0000 @@ -0,0 +1,51 @@ +package org.hisp.dhis.program; + +/* + * 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.junit.Assert.assertEquals; + +import java.util.Set; +import org.junit.Test; +import com.google.common.collect.Sets; + +/** +* @author Lars Helge Overland +*/ +public class ProgramIndicatorTest +{ + @Test + public void testGetIdentifiers() + { + String expression = "#{chG8sINMf11.yD5mUKAm3aK} + #{chG8sINMf11.UaGD9u0kaur} - A{y1Bhi6xHtVk}"; + + Set expected = Sets.newHashSet( "yD5mUKAm3aK", "UaGD9u0kaur", "y1Bhi6xHtVk" ); + + assertEquals( expected, ProgramIndicator.getDataElementAndAttributeIdentifiers( expression ) ); + } +} === modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/program/DefaultProgramIndicatorService.java' --- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/program/DefaultProgramIndicatorService.java 2015-08-25 09:48:55 +0000 +++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/program/DefaultProgramIndicatorService.java 2015-08-25 14:09:25 +0000 @@ -353,7 +353,7 @@ while ( matcher.find() ) { String key = matcher.group( 1 ); - String uid = statementBuilder.columnQuote( matcher.group( 2 ) ); + String val = matcher.group( 2 ); if ( ProgramIndicator.KEY_DATAELEMENT.equals( key ) ) { @@ -363,17 +363,26 @@ } else if ( ProgramIndicator.KEY_ATTRIBUTE.equals( key ) ) { - matcher.appendReplacement( buffer, uid ); + matcher.appendReplacement( buffer, statementBuilder.columnQuote( val ) ); } else if ( ProgramIndicator.KEY_CONSTANT.equals( key ) ) { - Constant constant = constantService.getConstant( uid ); + Constant constant = constantService.getConstant( val ); if ( constant != null ) { matcher.appendReplacement( buffer, String.valueOf( constant.getValue() ) ); } } + else if ( ProgramIndicator.KEY_PROGRAM_VARIABLE.equals( key ) ) + { + String sql = getVariableAsSql( val, expression ); + + if ( sql != null ) + { + matcher.appendReplacement( buffer, sql ); + } + } } expression = TextUtils.appendTail( matcher, buffer ); @@ -758,6 +767,47 @@ return MathUtils.calculateExpression( expression ); } + + /** + * Creates a SQL select clause from the given program indicator variable + * based on the given expression. Wraps the count variables with + * nullif to avoid potential division by zero. + * + * @param var the program indicator variable. + * @param expression the program indicator expression. + * @return a SQL select clause. + */ + private String getVariableAsSql( String var, String expression ) + { + if ( ProgramIndicator.VAR_EXECUTION_DATE.equals( var ) ) + { + return "executiondate"; + } + else if ( ProgramIndicator.VAR_VALUE_COUNT.equals( var ) ) + { + String sql = "nullif(("; + + for ( String uid : ProgramIndicator.getDataElementAndAttributeIdentifiers( expression ) ) + { + sql += "case when " + statementBuilder.columnQuote( uid ) + " is not null then 1 else 0 end + "; + } + + return TextUtils.removeLast( sql, "+" ) + "),0)"; + } + else if ( ProgramIndicator.VAR_ZERO_POS_VALUE_COUNT.equals( var ) ) + { + String sql = "nullif(("; + + for ( String uid : ProgramIndicator.getDataElementAndAttributeIdentifiers( expression ) ) + { + sql += "case when " + statementBuilder.columnQuote( uid ) + " > 0 then 1 else 0 end + "; + } + + return TextUtils.removeLast( sql, "+" ) + "),0)"; + } + + return null; + } private boolean isZeroOrPositive( String value ) { === modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/user/DefaultUserService.java' --- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/user/DefaultUserService.java 2015-08-25 13:54:00 +0000 +++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/user/DefaultUserService.java 2015-08-25 14:09:25 +0000 @@ -267,19 +267,19 @@ public boolean validateUserQueryParams( UserQueryParams params ) { - if ( params.isCanManage() && (params.getUser() == null || !params.getUser().hasManagedGroups()) ) + if ( params.isCanManage() && ( params.getUser() == null || !params.getUser().hasManagedGroups() ) ) { log.warn( "Cannot get managed users as user does not have any managed groups" ); return false; } - if ( params.isAuthSubset() && (params.getUser() == null || !params.getUser().getUserCredentials().hasAuthorities()) ) + if ( params.isAuthSubset() && ( params.getUser() == null || !params.getUser().getUserCredentials().hasAuthorities() ) ) { log.warn( "Cannot get users with authority subset as user does not have any authorities" ); return false; } - if ( params.isDisjointRoles() && (params.getUser() == null || !params.getUser().getUserCredentials().hasUserAuthorityGroups()) ) + if ( params.isDisjointRoles() && ( params.getUser() == null || !params.getUser().getUserCredentials().hasUserAuthorityGroups() ) ) { log.warn( "Cannot get users with disjoint roles as user does not have any user roles" ); return false; === modified file 'dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/util/TextUtils.java' --- dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/util/TextUtils.java 2015-08-21 03:35:34 +0000 +++ dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/util/TextUtils.java 2015-08-25 14:09:25 +0000 @@ -193,8 +193,8 @@ } /** - * Removes the last occurence of the word "or" from the given string, - * including potential trailing spaces, case-insentitive. + * Removes the last occurrence of the word "or" from the given string, + * including potential trailing spaces, case-insensitive. * * @param string the string. * @return the chopped string. @@ -207,8 +207,8 @@ } /** - * Removes the last occurence of the word "and" from the given string, - * including potential trailing spaces, case-insentitive. + * Removes the last occurrence of the word "and" from the given string, + * including potential trailing spaces, case-insensitive. * * @param string the string. * @return the chopped string. @@ -221,7 +221,7 @@ } /** - * Removes the last occurence of comma (",") from the given string, + * Removes the last occurrence of comma (",") from the given string, * including potential trailing spaces. * * @param string the string. @@ -233,6 +233,20 @@ return StringUtils.removeEndIgnoreCase( string, "," ); } + + /** + * Removes the last occurrence of the the given string, including potential + * trailing spaces. + * + * @param string the string. + * @return the chopped string. + */ + public static String removeLast( String string, String remove ) + { + string = StringUtils.stripEnd( string, " " ); + + return StringUtils.removeEndIgnoreCase( string, remove ); + } /** * Trims the given string from the end. === modified file 'dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-program/src/main/webapp/dhis-web-maintenance-program/programIndicatorForm.vm' --- dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-program/src/main/webapp/dhis-web-maintenance-program/programIndicatorForm.vm 2015-08-25 09:48:55 +0000 +++ dhis-2/dhis-web/dhis-web-maintenance/dhis-web-maintenance-program/src/main/webapp/dhis-web-maintenance-program/programIndicatorForm.vm 2015-08-25 14:09:25 +0000 @@ -15,8 +15,8 @@
  • $i18n.getString("data_elements")
  • #if($program.programType=='WITH_REGISTRATION')
  • $i18n.getString("attributes")
  • -
  • $i18n.getString("variables")
  • #end +
  • $i18n.getString("variables")
  • $i18n.getString("constants")
  • @@ -43,7 +43,7 @@ - #if($program.programType=='WITH_REGISTRATION') + #if($program.programType=='WITH_REGISTRATION')
    @@ -65,6 +65,8 @@
    + #end +
    @@ -87,8 +89,6 @@
    - - #end
    @@ -158,10 +158,10 @@
    @@ -187,7 +187,7 @@
    - #if($program.type!='3') + #if($program.programType=='WITH_REGISTRATION')
    @@ -209,6 +209,8 @@
    + #end +
    @@ -231,8 +233,6 @@
    - - #end