azkaban-aplcache

Adding Expression resolution code

4/29/2014 8:43:27 PM

Details

diff --git a/src/main/java/azkaban/utils/PropsUtils.java b/src/main/java/azkaban/utils/PropsUtils.java
index 37cd003..45501c6 100644
--- a/src/main/java/azkaban/utils/PropsUtils.java
+++ b/src/main/java/azkaban/utils/PropsUtils.java
@@ -31,6 +31,9 @@ import java.util.regex.Pattern;
 import azkaban.executor.ExecutableFlowBase;
 import azkaban.flow.CommonJobProperties;
 
+import org.apache.commons.jexl2.Expression;
+import org.apache.commons.jexl2.JexlEngine;
+import org.apache.commons.jexl2.MapContext;
 import org.apache.commons.lang.StringUtils;
 import org.joda.time.DateTime;
 
@@ -144,7 +147,7 @@ public class PropsUtils {
 		return false;
 	}
 
-	private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\$\\{([a-zA-Z_.0-9]+)\\}");
+	private static final Pattern VARIABLE_REPLACEMENT_PATTERN = Pattern.compile("\\$\\{([a-zA-Z_.0-9]+)\\}");
 
 	public static Props resolveProps(Props props) {
 		if (props == null) return null;
@@ -157,9 +160,16 @@ public class PropsUtils {
 			
 			visitedVariables.add(key);
 			String replacedValue = resolveVariableReplacement(value, props, visitedVariables);
+			String expressedValue = resolveVariableExpression(replacedValue);
 			visitedVariables.clear();
 			
-			resolvedProps.put(key, replacedValue);
+			resolvedProps.put(key, expressedValue);
+		}
+		
+		for (String key : resolvedProps.getKeySet()) {
+			String value = resolvedProps.get(key);
+			String expressedValue = resolveVariableExpression(value);
+			resolvedProps.put(key, expressedValue);
 		}
 		
 		return resolvedProps;
@@ -169,7 +179,7 @@ public class PropsUtils {
 		StringBuffer buffer = new StringBuffer();
 		int startIndex = 0;
 		
-		Matcher matcher = VARIABLE_PATTERN.matcher(value);
+		Matcher matcher = VARIABLE_REPLACEMENT_PATTERN.matcher(value);
 		while (matcher.find(startIndex)) {
 			if (startIndex < matcher.start()) {
 				// Copy everything up front to the buffer
@@ -207,6 +217,35 @@ public class PropsUtils {
 		
 		return buffer.toString();
 	}
+
+	private static final Pattern VARIABLE_EXPRESSION_PATTERN = Pattern.compile("\\$\\(([a-zA-Z_.0-9\\s\\+\\-\\*\\/\"']+)\\)");
+	private static String resolveVariableExpression(String value) {
+		Matcher matcher = VARIABLE_EXPRESSION_PATTERN.matcher(value);
+		JexlEngine jexl = new JexlEngine();
+		
+		if (!matcher.find()) {
+			System.out.println("No matches found");
+			return value;
+		}
+		
+		StringBuilder builder = new StringBuilder();
+		
+		int startIndex = 0;
+		while (matcher.find(startIndex)) {
+			int start = matcher.start();
+			int end = matcher.end();
+			builder.append(value.substring(startIndex, start));
+			
+			String group = matcher.group(1);
+
+			Expression e = jexl.createExpression(group);
+			Object result = e.evaluate(new MapContext());
+			builder.append(result);
+			startIndex = end;
+		}
+		builder.append(value.substring(startIndex));
+		return resolveVariableExpression(builder.toString());
+	}
 	
 	public static Props addCommonFlowProperties(Props parentProps, final ExecutableFlowBase flow) {
 		Props props = new Props(parentProps);
diff --git a/unit/java/azkaban/test/utils/PropsUtilsTest.java b/unit/java/azkaban/test/utils/PropsUtilsTest.java
index 09edb89..5676e95 100644
--- a/unit/java/azkaban/test/utils/PropsUtilsTest.java
+++ b/unit/java/azkaban/test/utils/PropsUtilsTest.java
@@ -51,6 +51,36 @@ public class PropsUtilsTest {
 	}
 	
 	@Test
+	public void testExpressionResolution() throws IOException {
+		Props props = Props.of(
+			"normkey", "normal",
+			"num1", "1",
+			"num2", "2",
+			"num3", "3",
+			"variablereplaced", "${num1}",
+			"expression1", "$(1+10)",
+			"expression2", "$(1+10)*2",
+			"expression3", "$((${num1} + ${num3})*10)",
+			"expression4", "$(${num1} + ${expression3})",
+			"expression5", "$($($(2+3)) + 3) + $(${expression3} + 1))",
+			"expression6", "$(1 + ${normkey}))"
+		);
+
+		Props resolved = PropsUtils.resolveProps(props);
+		Assert.assertEquals("normal", resolved.get("normkey"));
+		Assert.assertEquals("1", resolved.get("num1"));
+		Assert.assertEquals("2", resolved.get("num2"));
+		Assert.assertEquals("3", resolved.get("num3"));
+		Assert.assertEquals("1", resolved.get("variablereplaced"));
+		Assert.assertEquals("11", resolved.get("expression1"));
+		Assert.assertEquals("11*2", resolved.get("expression2"));
+		Assert.assertEquals("40", resolved.get("expression3"));
+		Assert.assertEquals("41", resolved.get("expression4"));
+		Assert.assertEquals("11 + 41", resolved.get("expression5"));
+		Assert.assertEquals("1 + normal", resolved.get("expression6"));
+	}
+	
+	@Test
 	public void testCyclesResolveProps() throws IOException {
 		Props propsGrandParent = new Props();
 		Props propsParent = new Props(propsGrandParent);