diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/RegexUtils.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/RegexUtils.java index 761f666a0d7e..d36c309c9525 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/RegexUtils.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/common/RegexUtils.java @@ -28,9 +28,11 @@ package org.hisp.dhis.common; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.commons.lang3.ObjectUtils; /** * @author Lars Helge Overland @@ -45,7 +47,9 @@ public class RegexUtils { * @return a set of matches. */ public static Set getMatches(Pattern pattern, String input, Integer group) { - group = group != null ? group : 0; + Objects.requireNonNull(pattern); + + int gr = ObjectUtils.firstNonNull(group, 0); Set set = new HashSet<>(); @@ -53,30 +57,10 @@ public static Set getMatches(Pattern pattern, String input, Integer grou Matcher matcher = pattern.matcher(input); while (matcher.find()) { - set.add(matcher.group(group)); + set.add(matcher.group(gr)); } } return set; } - - /** - * Return the matches in the given input based on the given pattern and group name. - * - * @param pattern the pattern. - * @param input the input. - * @param groupName the group name, not null. - * @return a set of matches. - */ - public static Set getMatches(Pattern pattern, String input, String groupName) { - Set set = new HashSet<>(); - - Matcher matcher = pattern.matcher(input); - - while (matcher.find()) { - set.add(matcher.group(groupName)); - } - - return set; - } } diff --git a/dhis-2/dhis-api/src/test/java/org/hisp/dhis/common/RegexUtilsTest.java b/dhis-2/dhis-api/src/test/java/org/hisp/dhis/common/RegexUtilsTest.java new file mode 100644 index 000000000000..fd9b99c90be6 --- /dev/null +++ b/dhis-2/dhis-api/src/test/java/org/hisp/dhis/common/RegexUtilsTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2004-2024, 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. + */ +package org.hisp.dhis.common; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Set; +import java.util.regex.Pattern; +import org.junit.jupiter.api.Test; + +/** + * @author Lars Helge Overland + */ +class RegexUtilsTest { + @Test + void testGetMatchesWithGroup() { + assertEquals( + Set.of("animal", "target"), + RegexUtils.getMatches( + Pattern.compile("\\$\\{([^}]+)\\}"), "The ${animal} jumped over the ${target}.", 1)); + } + + @Test + void testGetMatchesWithNullInput() { + assertEquals(Set.of(), RegexUtils.getMatches(Pattern.compile("\\$\\{([^}]+)\\}"), null, 1)); + } + + @Test + void testGetMatchesWithNullGroup() { + assertEquals( + Set.of("${animal}", "${target}"), + RegexUtils.getMatches( + Pattern.compile("\\$\\{([^}]+)\\}"), "The ${animal} jumped over the ${target}.", null)); + } +} diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/AbstractJdbcTableManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/AbstractJdbcTableManager.java index 04481c69983c..242b63c84a5d 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/AbstractJdbcTableManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/AbstractJdbcTableManager.java @@ -702,18 +702,23 @@ protected String qualify(String name) { } /** - * Replaces variables in the given template string with the given variables to qualify and the - * given map of variable keys and values. + * Replaces variables in the given template string. + * + *

Variables which are present in the given template and in the given map of variables are + * replaced with the corresponding map value. + * + *

Variables which are present in the given template but not present in the given map of + * variables will be qualified using qualify(String). * * @param template the template string. - * @param qualifyVariables the list of variables to qualify. - * @param variables the map of variables and values. - * @return a resolved string. + * @param variables the map of variable names and values. + * @return a string with replaced variables. */ - protected String replaceQualify( - String template, List qualifyVariables, Map variables) { + protected String replaceQualify(String template, Map variables) { Map map = new HashMap<>(variables); - qualifyVariables.forEach(v -> map.put(v, qualify(v))); + Set variableNames = TextUtils.getVariableNames(template); + variableNames.forEach(name -> map.putIfAbsent(name, qualify(name))); + return TextUtils.replace(template, map); } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManagerTest.java b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManagerTest.java index 0edb4038355d..17c337a2f90f 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManagerTest.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManagerTest.java @@ -44,7 +44,6 @@ import java.util.List; import java.util.Map; import org.hisp.dhis.analytics.AnalyticsTableHookService; -import org.hisp.dhis.analytics.AnalyticsTableManager; import org.hisp.dhis.analytics.AnalyticsTableType; import org.hisp.dhis.analytics.AnalyticsTableUpdateParams; import org.hisp.dhis.analytics.partition.PartitionManager; @@ -85,6 +84,7 @@ @ExtendWith(MockitoExtension.class) class JdbcAnalyticsTableManagerTest { @Mock private SystemSettingsProvider settingsProvider; + @Mock private SystemSettings settings; @Mock private JdbcTemplate jdbcTemplate; @@ -95,7 +95,7 @@ class JdbcAnalyticsTableManagerTest { @Spy private final SqlBuilder sqlBuilder = new PostgreSqlBuilder(); - private AnalyticsTableManager subject; + private JdbcAnalyticsTableManager subject; @BeforeEach public void setUp() { @@ -118,6 +118,32 @@ public void setUp() { sqlBuilder); } + @Test + void testReplaceQualify() { + String template = + """ + from ${datavalue} dv \ + inner join ${dataelement} de on dv.dataelementid = de.dataelementid \ + inner join ${period} pe on pe \ + where de.valuetype in (${value_type}) \ + and de.aggregationtype in (${agg_type});"""; + + Map variables = + Map.of( + "value_type", "'INTEGER','NUMERIC'", + "agg_type", "'SUM','AVERAGE'"); + + String expected = + """ + from "datavalue" dv \ + inner join "dataelement" de on dv.dataelementid = de.dataelementid \ + inner join "period" pe on pe \ + where de.valuetype in ('INTEGER','NUMERIC') \ + and de.aggregationtype in ('SUM','AVERAGE');"""; + + assertEquals(expected, subject.replaceQualify(template, variables)); + } + @Test void testGetRegularAnalyticsTable() { Date startTime = new DateTime(2019, 3, 1, 10, 0).toDate(); diff --git a/dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/util/TextUtils.java b/dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/util/TextUtils.java index ff7d781452d2..e8f5f00e16ac 100644 --- a/dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/util/TextUtils.java +++ b/dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/util/TextUtils.java @@ -37,9 +37,11 @@ import java.util.Map; import java.util.Set; import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.StringSubstitutor; +import org.hisp.dhis.common.RegexUtils; import org.slf4j.helpers.MessageFormatter; /** @@ -68,6 +70,8 @@ public class TextUtils { private static final Character DOUBLE_QUOTE = '\"'; + private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\$\\{([^}]+)\\}"); + /** * Remove all non-alphanumeric characters within string * @@ -654,4 +658,15 @@ public static String removeAnyTrailingSlash(String string) { public static String format(String pattern, Object... arguments) { return MessageFormatter.arrayFormat(pattern, arguments).getMessage(); } + + /** + * Returns the names of the variables in the given input. The definition of a variable is + * ${variableName}. + * + * @param input the input potentially containing variables. + * @return a set of variable names. + */ + public static Set getVariableNames(String input) { + return RegexUtils.getMatches(VARIABLE_PATTERN, input, 1); + } } diff --git a/dhis-2/dhis-support/dhis-support-commons/src/test/java/org/hisp/dhis/commons/util/TextUtilsTest.java b/dhis-2/dhis-support/dhis-support-commons/src/test/java/org/hisp/dhis/commons/util/TextUtilsTest.java index 08a136914da3..c3d9cd7c915f 100644 --- a/dhis-2/dhis-support/dhis-support-commons/src/test/java/org/hisp/dhis/commons/util/TextUtilsTest.java +++ b/dhis-2/dhis-support/dhis-support-commons/src/test/java/org/hisp/dhis/commons/util/TextUtilsTest.java @@ -38,6 +38,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.jupiter.api.Test; /** @@ -267,4 +268,16 @@ void testEmptyIfTrue() { assertEquals("", TextUtils.emptyIfTrue("foo", true)); assertEquals("foo", TextUtils.emptyIfTrue("foo", false)); } + + @Test + void testGetVariableNames() { + assertEquals( + Set.of("animal", "target"), + TextUtils.getVariableNames("The ${animal} jumped over the ${target}.")); + } + + @Test + void testGetVariableNamesWithNullInput() { + assertEquals(Set.of(), TextUtils.getVariableNames(null)); + } }