Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

4.x improvements #3235

Merged
merged 3 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions hawtio-system/src/main/java/io/hawt/util/Strings.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,74 @@ public static String webContextPath(final String first, final String... more) {
return cleanedPath.length() == 1 ? "" : cleanedPath;
}

/**
* Simple, recursively-safe property placeholder resolver. Only system properties are used (for now). De-facto
* standard {@code ${...}} syntax is used. Unresolvable properties are not replaced and separators pass to
* resulting value.
*
* @param value
* @return
*/
public static String resolvePlaceholders(String value) {
if (value == null || !value.contains("$")) {
return value;
}

StringBuilder result = new StringBuilder();
int l = value.length();
for (int pos1 = 0; pos1 < l; pos1++) {
char c1 = value.charAt(pos1);
char c2 = pos1 == l - 1 ? '\0' : value.charAt(pos1 + 1);
if (c1 == '$' && c2 == '{') {
// find matching }
// - if found, resolve and continue with the rest of the value
// - if not found, just proceed (possibly until next "${")
int depth = 1;
int pos2 = pos1 + 2;
while (depth > 0 && pos2 < l) {
if (value.charAt(pos2) == '$' && pos2 < l - 1 && value.charAt(pos2 + 1) == '{') {
depth++;
pos2 += 2;
} else if (value.charAt(pos2) == '}') {
depth--;
pos2++;
} else {
pos2++;
}
}
if (depth > 0) {
// no matching '}'
result.append('$');
} else {
pos1 = resolve(value, result, pos1, pos2) - 1;
}
} else {
result.append(c1);
}
}

return result.toString();
}

/**
* Single iteration resolve method. {@code from} indicates <code>${</code> placeholder start
*
* @param value
* @param result
* @param from
* @param to
* @return
*/
private static int resolve(String value, StringBuilder result, int from, int to) {
// "from" always points to "${" and "to" points to _matching_ "}"
String key = resolvePlaceholders(value.substring(from + 2, to - 1));
String v = System.getProperty(key);
if (v == null) {
result.append("${").append(key).append("}");
} else {
result.append(v);
}
return to;
}

}
61 changes: 61 additions & 0 deletions hawtio-system/src/test/java/io/hawt/util/StringsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.params.provider.Arguments.arguments;
Expand Down Expand Up @@ -43,6 +44,66 @@ public void split() {
}
}

@Nested
class PropertiesTest {

@Test
public void oneLevelSystemProperties() {
System.setProperty("hawtio.what", "world");
assertThat(Strings.resolvePlaceholders(null), nullValue());
assertThat(Strings.resolvePlaceholders("Hello"), is("Hello"));
assertThat(Strings.resolvePlaceholders("Hello ${hawtio.what}!"), is("Hello world!"));
assertThat(Strings.resolvePlaceholders("${hawtio.what}"), is("world"));
assertThat(Strings.resolvePlaceholders("${hawtio.what}, hello ${hawtio.what}!"), is("world, hello world!"));
}

@Test
public void multibyteSystemProperties() {
System.setProperty("hawtio.what", "世界");
System.setProperty("こんにちは", "Hello");
assertThat(Strings.resolvePlaceholders(null), nullValue());
assertThat(Strings.resolvePlaceholders("こんにちは、${hawtio.what}!"), is("こんにちは、世界!"));
assertThat(Strings.resolvePlaceholders("${こんにちは} world!"), is("Hello world!"));
}

@Test
public void valuesWithPlaceholdersAreNotResolved() {
System.setProperty("hawtio.what", "${world}");
assertThat(Strings.resolvePlaceholders("Hello ${hawtio.what}!"), is("Hello ${world}!"));
tadayosi marked this conversation as resolved.
Show resolved Hide resolved
assertThat(Strings.resolvePlaceholders("Hello ${world}!"), is("Hello ${world}!"));
}

@Test
public void mismatchedPlaceholders() {
System.setProperty("nestedProperty", "what");
System.setProperty("hawtio.what", "world");
assertThat(Strings.resolvePlaceholders("Hello ${hawtio.what!"), is("Hello ${hawtio.what!"));
assertThat(Strings.resolvePlaceholders("Hello ${hawtio.${nestedProperty}!"), is("Hello ${hawtio.what!"));
}

@Test
public void nestedSystemProperties() {
System.setProperty("doubleProperty", "hawtio.what");
System.setProperty("nestedProperty", "what");
System.setProperty("hawtio.what", "world");
assertThat(Strings.resolvePlaceholders("Hello ${hawtio.${nestedProperty}}!"), is("Hello world!"));
assertThat(Strings.resolvePlaceholders("${hawtio.${nestedProperty}}"), is("world"));
assertThat(Strings.resolvePlaceholders("${hawtio.${nestedProperty}}, hello ${hawtio.${nestedProperty}}!"), is("world, hello world!"));
assertThat(Strings.resolvePlaceholders("${doubleProperty}"), is("hawtio.what"));
assertThat(Strings.resolvePlaceholders("${${doubleProperty}}"), is("world"));
assertThat(Strings.resolvePlaceholders("${${${doubleProperty}}}"), is("${world}"));
}

@Test
public void recursiveSystemProperties() {
System.setProperty("nestedProperty", "what");
System.setProperty("hawtio.what", "hawtio.${nestedProperty}");
assertThat(Strings.resolvePlaceholders("Hello ${hawtio.${nestedProperty}}!"), is("Hello hawtio.${nestedProperty}!"));
tadayosi marked this conversation as resolved.
Show resolved Hide resolved
// no risk, because we don't resolve properties in system property values at all
// assertThrows(IllegalArgumentException.class,
// () -> Strings.resolvePlaceholders("Hello ${hawtio.${nestedProperty}}!"));
}
}
tadayosi marked this conversation as resolved.
Show resolved Hide resolved

public static class CleanPathTest {

Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
<commons-io-version>2.15.1</commons-io-version>
<commons-logging-version>1.3.0</commons-logging-version>
<hamcrest-version>2.2</hamcrest-version>
<httpcore-version>4.4.16</httpcore-version>
<httpclient-version>4.5.14</httpclient-version>
<jackson-version>2.16.1</jackson-version>
<junit5-version>5.10.1</junit5-version>
Expand Down
15 changes: 15 additions & 0 deletions tests/hawtio-test-suite/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@
<xtf.version>0.31</xtf.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>${httpcore-version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient-version}</version>
</dependency>
</dependencies>
</dependencyManagement>
tadayosi marked this conversation as resolved.
Show resolved Hide resolved

<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
Expand Down
Loading