Skip to content

Commit

Permalink
Merge pull request jpos#518 from barspi/feature/negate-op-in-environm…
Browse files Browse the repository at this point in the history
…ent-expressions

Feature/negate op in environment expressions
  • Loading branch information
ar authored Apr 14, 2023
2 parents 6c9d6b7 + 6db2454 commit 1c9d75b
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 16 deletions.
55 changes: 40 additions & 15 deletions jpos/src/main/java/org/jpos/core/Environment.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
package org.jpos.core;

import org.jpos.iso.ISOUtil;
import org.jpos.util.Caller;
import org.jpos.util.Loggeable;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.scanner.ScannerException;
Expand All @@ -37,8 +36,8 @@ public class Environment implements Loggeable {
private static final String SYSTEM_PREFIX = "sys";
private static final String ENVIRONMENT_PREFIX = "env";

private static Pattern valuePattern = Pattern.compile("^(.*)(\\$)([\\w]*)\\{([-\\w.]+)(:(.*?))?\\}(.*)$");
// make groups easier to read :-) 11112222233333333 44444444445566665 7777
private static Pattern valuePattern = Pattern.compile("^(.*)(\\$)([\\w]*)\\{([-!\\w.]+)(:(.*?))?\\}(.*)$");
// make groups easier to read :-) 11112222233333333 4444444444455666665 7777

private static Pattern verbPattern = Pattern.compile("^\\$verb\\{([\\w\\W]+)\\}$");
private static Environment INSTANCE;
Expand All @@ -60,6 +59,14 @@ public class Environment implements Loggeable {
}
}

protected static Map<String,String> notMap = new HashMap<>();
static {
notMap.put("false", "true");
notMap.put("true", "false");
notMap.put("yes", "no");
notMap.put("no", "yes");
}

private Environment() throws IOException {
name = System.getProperty ("jpos.env");
name = name == null ? "default" : name;
Expand Down Expand Up @@ -127,9 +134,14 @@ public String getProperty (String s) {
return s; // return the whole thing

while (m != null && m.matches()) {
boolean negated = false;
String previousR = r;
String gPrefix = m.group(3);
String gValue = m.group(4);
if (gValue.startsWith("!")) {
negated = true;
gValue = gValue.substring(1);
}
gPrefix = gPrefix != null ? gPrefix : "";
switch (gPrefix) {
case CFG_PREFIX:
Expand All @@ -143,19 +155,20 @@ public String getProperty (String s) {
break;
default:
if (gPrefix.length() == 0) {
r = System.getenv(gValue); // ENV has priority
r = System.getenv(gValue); // ENV has priority
r = r == null ? System.getenv(gValue.replace('.', '_').toUpperCase()) : r;
r = r == null ? System.getProperty(gValue) : r; // then System.property
r = r == null ? propRef.get().getProperty(gValue) : r; // then jPOS --environment
r = r == null ? System.getProperty(gValue) : r; // then System.property
r = r == null ? propRef.get().getProperty(gValue) : r; // then jPOS --environment
} else {
return s; // do nothing - unknown prefix
}
}

if (r == null) {
String defValue = m.group(6);
String defValue = null;
if (r == null) { // unresolved property
defValue = m.group(6);
if (defValue != null)
r = defValue;
r = defValue; // use default value from now on
}

if (r != null) {
Expand All @@ -165,15 +178,26 @@ public String getProperty (String s) {
r = p.get(r.substring(l));
}
}

if (negated && r != null &&
defValue == null) // we don't want to negate a default literal boolean!
{
String rNorm = r.trim().toLowerCase();
r = notMap.getOrDefault(rNorm, r); // if not a booleanish string, return unchanged
}

if (m.group(1) != null) {
r = m.group(1) + r;
}
if (m.group(7) != null)
r = r + m.group(7);

m = valuePattern.matcher(r);
}
else
} else { // property was undefined/unresolved and no default was provided
if (negated)
r = "true"; // a negated undefined is interpreted as true
m = null;
}

if (Objects.equals(r, previousR))
break;
Expand Down Expand Up @@ -241,14 +265,15 @@ private boolean readCfg (String n, Properties properties) throws IOException {

@SuppressWarnings("unchecked")
public static void flat (Properties properties, String prefix, Map<String,Object> c, boolean dereference) {
for (Object o : c.entrySet()) {
Map.Entry<String,Object> entry = (Map.Entry<String,Object>) o;
for (Map.Entry<String,Object> entry : c.entrySet()) {
String p = prefix == null ? entry.getKey() : (prefix + "." + entry.getKey());
if (entry.getValue() instanceof Map) {
flat(properties, p, (Map) entry.getValue(), dereference);
flat(properties, p, (Map<String,Object>)entry.getValue(), dereference);
} else {
Object obj = entry.getValue();
properties.put (p, "" + (dereference && obj instanceof String ? Environment.get((String) obj) : entry.getValue()));
properties.put (p, (dereference && obj instanceof String ?
Environment.get((String) obj) :
entry.getValue().toString()));
}
}
}
Expand Down
86 changes: 86 additions & 0 deletions jpos/src/test/java/org/jpos/core/EnvironmentTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.junit.jupiter.api.TestInstance;

import java.io.IOException;
import java.util.Properties;

import static org.junit.jupiter.api.Assertions.*;

Expand Down Expand Up @@ -90,4 +91,89 @@ public void testLoop() {
System.setProperty("loop", "${loop}");
assertEquals("${loop}", Environment.get("${loop}"));
}

@Test
public void multiExpr() {
assertEquals("the numbers UNO and DOS and NaN",
Environment.get("the numbers ${test.one} and ${test.two} and ${test.three:NaN}"));
}

@Test
public void testNegateExprFromEnvironment() {
assertEquals("true", Environment.get("${test.true_boolean}"),
"${test.true_boolean} should return \"true\"");

assertEquals("false", Environment.get("${!test.true_boolean}"),
"${!test.true_boolean} should return \"false\"");

assertEquals("true", Environment.get("${!test.false_boolean}"),
"${!test.false_boolean} should return \"true\"");

// In the yaml file the definition is "test.no_upper: NO",
// but it's converted to a boolean false by yaml parser.
// This is converted into a string "false" by the Environment flattening process.
assertEquals("true", Environment.get("${!test.no_upper}"),
"test.no_upper: NO, so ${!test.no_upper} should return \"true\"");

// The system properties are already strings, soy "YES" is maintained as is
System.setProperty("enabled.value", "YES");
assertEquals("no", Environment.get("${!enabled.value}"),
"enabled.value=\"YES\", so ${!enabled.value} should return \"no\"");

assertEquals("DOS", Environment.get("${!test.two}"),
"${!test.two} should return DOS, since negate operator is ignored for non-boolean strings");
}

@Test
public void testNegateExprFromSimpleConfiguration() {
Properties props = new Properties();
props.put("literal-true", "true");
props.put("literal-NO", "NO");

// In the yaml file the definition is "test.two: DOS",
props.put( "expr-test-two", "${test.two}"); // must return false, since getBoolean is false for non-booleanish values
props.put("neg-expr-test-two", "${!test.two}"); // same as above, the negation op has no effect on non-booleanish values

props.put( "expr-test-true-no-def", "${test.true_boolean}");
props.put( "expr-test-true-def", "${test.true_boolean:false}"); // must return true, ignoring default

props.put( "expr-test-no_upper-no-def", "${test.no_upper}");
props.put("neg-expr-test-false-def", "${!test.false_boolean:false}"); // must return true, ignoring default

// unresolved properties (they aren't defined anywhere)
props.put( "undefined-no-def", "${__undefined__}");
props.put( "undefined-def", "${__undefined__:true}");
props.put("neg-undefined-no-def", "${!__undefined__}");
props.put("neg-undefined-def", "${!__undefined__:true}");

SimpleConfiguration conf = new SimpleConfiguration(props);


assertTrue(conf.getBoolean("literal-true"), "literal-true");
assertFalse(conf.getBoolean("literal-NO"), "literal-NO");

assertFalse(conf.getBoolean( "expr-test-two"), "expr-test-two: ${test.two} must return \"false\" for getBoolean");
assertFalse(conf.getBoolean("neg-expr-test-two"), "neg-expr-test-two: ${!test.two} must return \"false\" for getBoolean");

assertTrue(conf.getBoolean("expr-test-true-no-def"), "expr-test-true-no-def");
assertTrue(conf.getBoolean("expr-test-true-def"), "expr-test-true-def must be true, ignoring default");


assertFalse(conf.getBoolean("expr-test-no_upper-no-def"),
"expr-test-no_upper-no-def");
assertTrue(conf.getBoolean("neg-expr-test-false-def"),
"neg-expr-test-false-def: ${!test.false_boolean:false} must be true, ignoring default");


assertFalse(conf.getBoolean("undefined-no-def"),
"undefined-no-def must be false, since it can't resolve");
assertTrue(conf.getBoolean("undefined-def"),
"undefined-def must be true, since the default is true and must be honored");

assertTrue(conf.getBoolean("neg-undefined-no-def"),
"neg-undefined-no-def: ${!__undefined__} must be true, since it's the opposite of undefined");
assertTrue(conf.getBoolean("neg-undefined-def"),
"neg-undefined-def: ${!__undefined__:true} must be true, since the default is true and must be honored");
}

}
14 changes: 13 additions & 1 deletion jpos/src/test/resources/org/jpos/core/testenv.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
test:
value: "from testenv.yml"

true_boolean: true
false_boolean: false
yes_lower: yes # converted to true by yaml parser
no_upper: NO # converted to false by yaml parser

annotation:
envstring: "from testenv.yml"

one: UNO
two: DOS

---
system:
property:
test:
sys: ${test.value}
sys: ${test.value}

0 comments on commit 1c9d75b

Please sign in to comment.