/*
* (C) Copyright 2015 Netcentric AG.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package biz.netcentric.cq.tools.actool.configreader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.jcr.Session;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import biz.netcentric.cq.tools.actool.history.AcInstallationLog;
@Service
@Component
public class YamlMacroProcessorImpl implements YamlMacroProcessor {
private static final Logger LOG = LoggerFactory.getLogger(YamlMacroProcessorImpl.class);
private final Pattern forLoopPattern = Pattern.compile(
"for +(\\w+) +in +(?:\\[([,/\\s\\w\\-]+)\\]|children +of +([^\\s]+)|(\\$\\{[^\\}]+\\}))",
Pattern.CASE_INSENSITIVE);
private final Pattern ifPattern = Pattern.compile("if +(\\$\\{[^\\}]+\\})", Pattern.CASE_INSENSITIVE);
final Pattern variableDefPattern = Pattern.compile("DEF +([a-z0-9]+)=(?:\\[(.+)\\]|(\"?)([^\"]*)(\\3))",
Pattern.CASE_INSENSITIVE);
private static final String COMMA_SEPARATED_LIST_SPLITTER = "\\s*,\\s*";
YamlMacroElEvaluator elEvaluator = new YamlMacroElEvaluator();
@Reference
YamlMacroChildNodeObjectsProvider yamlMacroChildNodeObjectsProvider;
@Override
public List<LinkedHashMap> processMacros(List<LinkedHashMap> yamlList, AcInstallationLog installLog, Session session) {
return (List<LinkedHashMap>) transform(yamlList, installLog, session);
}
private Object transform(Object o, AcInstallationLog installLog, Session session) {
return transform(o, new HashMap<String, Object>(), installLog, session);
}
private Object transform(Object o, Map<String, Object> variables, AcInstallationLog installLog, Session session) {
if (o == null) {
return null;
} else if (o instanceof String) {
String str = (String) o;
Matcher variableDefMatcher = variableDefPattern.matcher(str);
if (variableDefMatcher.find()) {
return evaluateDefStatement(variables, variableDefMatcher);
}
String result = elEvaluator.evaluateEl(str, String.class, variables);
return result;
} else if (o instanceof Boolean) {
return (Boolean) o;
} else if (o instanceof List) {
List list = (List) o;
List transformedList = new LinkedList();
for (Object val : list) {
Object transformedObject = transform(val, variables, installLog, session);
addToListWithPotentialUnfolding(transformedList, transformedObject);
}
return transformedList;
} else if (o instanceof Map) {
Map map = (Map) o;
Map resultMap = new LinkedHashMap();
for (Object key : map.keySet()) {
Object objVal = map.get(key);
Matcher forMatcher = forLoopPattern.matcher(key.toString());
if (forMatcher.matches()) {
// map is skipped and value returned directly
return evaluateForStatement(variables, objVal, forMatcher, installLog, session);
}
Matcher ifMatcher = ifPattern.matcher(key.toString());
if (ifMatcher.matches()) {
// map is skipped and value returned directly
return evaluateIfStatement(variables, objVal, ifMatcher, installLog, session);
}
// default: transform both key and value
Object transformedKey = transform(key, variables, installLog, session);
Object transformedVal = transform(objVal, variables, installLog, session);
if (transformedVal != null) {
resultMap.put(transformedKey, transformedVal);
}
}
return resultMap;
} else {
throw new IllegalStateException("Unexpected class " + o.getClass() + " in object structure produced by yaml: " + o);
}
}
private Object evaluateDefStatement(Map<String, Object> variables, Matcher variableDefMatcher) {
String varName = variableDefMatcher.group(1);
String varValueArr = variableDefMatcher.group(2);
String varValueStr = variableDefMatcher.group(4);
Object varValueEvaluated;
if (varValueStr != null) {
varValueEvaluated = elEvaluator.evaluateEl(varValueStr, Object.class, variables);
} else if (varValueArr != null) {
List<Object> result = new ArrayList<Object>();
String[] arrayVals = varValueArr.split(COMMA_SEPARATED_LIST_SPLITTER);
for (String arrayVal : arrayVals) {
Object arrayValEvaluated = elEvaluator.evaluateEl(arrayVal, Object.class, variables);
result.add(arrayValEvaluated);
}
varValueEvaluated = result;
} else {
throw new IllegalStateException("None of the def value types were set even though RegEx matched");
}
variables.put(varName, varValueEvaluated);
return null;
}
private Object evaluateForStatement(Map<String, Object> variables, Object objVal, Matcher forMatcher,
AcInstallationLog installLog, Session session) {
String varName = StringUtils.trim(forMatcher.group(1));
String valueOfInClause = StringUtils.trim(forMatcher.group(2));
String pathOfChildrenOfClause = StringUtils.trim(forMatcher.group(3));
String variableForInClause = StringUtils.trim(forMatcher.group(4));
List<?> iterationValues;
if(valueOfInClause != null) {
iterationValues = Arrays.asList(valueOfInClause.split(COMMA_SEPARATED_LIST_SPLITTER));
} else if(pathOfChildrenOfClause!=null) {
// allow variables in root path also
pathOfChildrenOfClause = elEvaluator.evaluateEl(pathOfChildrenOfClause, String.class, variables);
iterationValues = yamlMacroChildNodeObjectsProvider.getValuesForPath(pathOfChildrenOfClause, installLog, session);
} else if(variableForInClause!=null) {
iterationValues = elEvaluator.evaluateEl(variableForInClause, List.class, variables);
} else {
throw new IllegalStateException("None of the loop type variables were set even though RegEx matched");
}
List toBeUnfoldedList = unfoldLoop(variables, objVal, varName, iterationValues, installLog, session);
return toBeUnfoldedList;
}
private Object evaluateIfStatement(Map<String, Object> variables, Object objVal, Matcher ifMatcher,
AcInstallationLog installLog, Session session) {
String condition = ifMatcher.group(1).trim();
boolean expressionIsTrue = elEvaluator.evaluateEl(condition, Boolean.class, variables);
List toBeUnfoldedList = unfoldIf(variables, objVal, expressionIsTrue, installLog, session);
return toBeUnfoldedList;
}
private void addToListWithPotentialUnfolding(List transformedList, Object transformedObject) {
if (transformedObject == null) {
return; // this happens for vars with DEF - those are evaluated already, entry must be left out
} else if (transformedObject instanceof ToBeUnfoldedList) {
// add entries individually (for for loops)
ToBeUnfoldedList toBeUnfoldedList = (ToBeUnfoldedList) transformedObject;
for (Object object : toBeUnfoldedList) {
transformedList.add(object);
}
} else {
// add transformed object as is
transformedList.add(transformedObject);
}
}
private List unfoldLoop(Map<String, Object> variables, Object val, String varName, List<?> varValues,
AcInstallationLog installLog, Session session) {
List resultList = new ToBeUnfoldedList();
for (Object varValue : varValues) {
Map<String, Object> variablesAtThisScope = new HashMap<String, Object>(variables);
variablesAtThisScope.put(varName, varValue);
unfold(val, resultList, variablesAtThisScope, installLog, session);
}
return resultList;
}
private List unfoldIf(Map<String, Object> variables, Object val, boolean expressionIsTrue,
AcInstallationLog installLog, Session session) {
List resultList = new ToBeUnfoldedList();
if (expressionIsTrue) {
unfold(val, resultList, variables, installLog, session);
} // otherwise return empty list
return resultList;
}
private void unfold(Object val, List resultList, Map<String, Object> variablesAtThisScope,
AcInstallationLog installLog, Session session) {
if (val instanceof List) {
List origList = (List) val;
for (Object origListItem : origList) {
Object transformedListItem = transform(origListItem, variablesAtThisScope, installLog, session);
addToListWithPotentialUnfolding(resultList, transformedListItem);
}
} else {
Object transformedListItem = transform(val, variablesAtThisScope, installLog, session);
addToListWithPotentialUnfolding(resultList, transformedListItem);
}
}
// marker class
private class ToBeUnfoldedList extends LinkedList {
}
public static void main(String[] args) throws Exception {
Map<Object, Object> userMap = new HashMap<Object, Object>();
userMap.put("x", new Integer(123));
userMap.put("y", new Integer(456));
userMap.put("TEST", "a long test value");
String expr = "x= ---- ${upperCase(splitByWholeSeparator(TEST,'long')[1])}";
String val = new YamlMacroElEvaluator().evaluateEl(expr, String.class, userMap);
System.out.println("the value for " + expr + " =>> " + val);
}
}