/*
* (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.beans.FeatureDescriptor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.el.ArrayELResolver;
import javax.el.BeanELResolver;
import javax.el.CompositeELResolver;
import javax.el.ELContext;
import javax.el.ELResolver;
import javax.el.ExpressionFactory;
import javax.el.FunctionMapper;
import javax.el.ListELResolver;
import javax.el.MapELResolver;
import javax.el.ValueExpression;
import javax.el.VariableMapper;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
/** Evaluates expressions that may contain variables from for loops.
*
* Not an OSGi Service as it carries state and is not multi-threading safe.
*
* @author ghenzler */
public class YamlMacroElEvaluator {
private ExpressionFactory expressionFactory;
private ELContext context;
private Map<? extends Object, ? extends Object> vars = new HashMap<Object, Object>();
public YamlMacroElEvaluator() {
try {
Class<?> expressionFactoryClass = getClass().getClassLoader().loadClass("org.apache.el.ExpressionFactoryImpl");
expressionFactory = (ExpressionFactory) expressionFactoryClass.newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
throw new IllegalStateException("Could not init EL: " + e);
}
final VariableMapper variableMapper = new ElVariableMapper();
final ElFunctionMapper functionMapper = new ElFunctionMapper();
final CompositeELResolver compositeELResolver = new CompositeELResolver();
compositeELResolver.add(new BaseELResolver());
compositeELResolver.add(new ArrayELResolver());
compositeELResolver.add(new ListELResolver());
compositeELResolver.add(new BeanELResolver());
compositeELResolver.add(new MapELResolver());
context = new ELContext() {
@Override
public ELResolver getELResolver() {
return compositeELResolver;
}
@Override
public FunctionMapper getFunctionMapper() {
return functionMapper;
}
@Override
public VariableMapper getVariableMapper() {
return variableMapper;
}
};
}
public <T> T evaluateEl(String el, Class<T> expectedResultType, Map<? extends Object, ? extends Object> variables) {
vars = variables;
ValueExpression expression = expressionFactory.createValueExpression(context, el, expectedResultType);
T value = (T) expression.getValue(context);
return value;
}
class ElFunctionMapper extends FunctionMapper {
private Map<String, Method> functionMap = new HashMap<String, Method>();
public ElFunctionMapper() {
try {
Method[] exportedMethods = new Method[] {
StringUtils.class.getMethod("split", new Class<?>[] { String.class, String.class }),
StringUtils.class.getMethod("join", new Class<?>[] { Object[].class, String.class }),
ArrayUtils.class.getMethod("subarray", new Class<?>[] { Object[].class, int.class, int.class }),
StringUtils.class.getMethod("upperCase", new Class<?>[] { String.class }),
StringUtils.class.getMethod("lowerCase", new Class<?>[] { String.class }),
StringUtils.class.getMethod("substringAfter", new Class<?>[] { String.class, String.class }),
StringUtils.class.getMethod("substringBefore", new Class<?>[] { String.class, String.class }),
StringUtils.class.getMethod("substringAfterLast", new Class<?>[] { String.class, String.class }),
StringUtils.class.getMethod("substringBeforeLast", new Class<?>[] { String.class, String.class }),
StringUtils.class.getMethod("contains", new Class<?>[] { String.class, String.class }),
StringUtils.class.getMethod("endsWith", new Class<?>[] { String.class, String.class }),
StringUtils.class.getMethod("startsWith", new Class<?>[] { String.class, String.class }),
StringUtils.class.getMethod("replace", new Class<?>[] { String.class, String.class, String.class })
};
for (Method method : exportedMethods) {
functionMap.put(method.getName(), method);
}
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Class StringUtils/ArrayUtils is missing expected methods", e);
}
}
@Override
public Method resolveFunction(String prefix, String localName) {
String key = (StringUtils.isNotBlank(prefix) ? prefix + ":" : "") + localName;
return functionMap.get(key);
}
}
class ElVariableMapper extends VariableMapper {
@Override
public ValueExpression resolveVariable(String paramString) {
Object value = vars.get(paramString);
return value != null ? expressionFactory.createValueExpression(value, value.getClass()) : null;
}
@Override
public ValueExpression setVariable(String paramString, ValueExpression paramValueExpression) {
throw new UnsupportedOperationException();
}
}
/** extra base resolver needed to allow to put maps on root level, see
* http://illegalargumentexception.blogspot.com.es/2008/04/java-using-el-outside-j2ee.html */
class BaseELResolver extends ELResolver {
private ELResolver delegate = new MapELResolver();
public BaseELResolver() {
}
@Override
public Object getValue(ELContext context, Object base, Object property) {
if (base == null) {
base = vars;
}
return delegate.getValue(context, base, property);
}
@Override
public Class<?> getCommonPropertyType(ELContext context, Object base) {
if (base == null) {
base = vars;
}
return delegate.getCommonPropertyType(context, base);
}
@Override
public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context,
Object base) {
if (base == null) {
base = vars;
}
return delegate.getFeatureDescriptors(context, base);
}
@Override
public Class<?> getType(ELContext context, Object base, Object property) {
if (base == null) {
base = vars;
}
return delegate.getType(context, base, property);
}
@Override
public boolean isReadOnly(ELContext context, Object base, Object property) {
if (base == null) {
base = vars;
}
return delegate.isReadOnly(context, base, property);
}
@Override
public void setValue(ELContext context, Object base, Object property, Object value) {
if (base == null) {
base = vars;
}
delegate.setValue(context, base, property, value);
}
}
}