/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.runtime.core.el.mvel;
import static java.util.Collections.singletonMap;
import static java.util.stream.Collectors.toMap;
import static org.apache.commons.lang.StringUtils.replace;
import static org.mule.runtime.api.el.ValidationResult.failure;
import static org.mule.runtime.api.el.ValidationResult.success;
import static org.mule.runtime.api.metadata.DataType.OBJECT;
import static org.mule.runtime.core.api.el.ExpressionManager.DEFAULT_EXPRESSION_POSTFIX;
import static org.mule.runtime.core.api.el.ExpressionManager.DEFAULT_EXPRESSION_PREFIX;
import static org.mule.runtime.core.el.DefaultExpressionManager.MEL_PREFIX;
import static org.mule.runtime.core.el.DefaultExpressionManager.PREFIX_EXPR_SEPARATOR;
import org.mule.mvel2.CompileException;
import org.mule.mvel2.ParserConfiguration;
import org.mule.mvel2.ast.Function;
import org.mule.mvel2.compiler.ExpressionCompiler;
import org.mule.mvel2.integration.VariableResolverFactory;
import org.mule.mvel2.integration.impl.CachedMapVariableResolverFactory;
import org.mule.mvel2.util.CompilerTools;
import org.mule.runtime.api.el.BindingContext;
import org.mule.runtime.api.el.ValidationResult;
import org.mule.runtime.api.lifecycle.Initialisable;
import org.mule.runtime.api.lifecycle.InitialisationException;
import org.mule.runtime.api.metadata.AbstractDataTypeBuilderFactory;
import org.mule.runtime.api.metadata.DataType;
import org.mule.runtime.api.metadata.TypedValue;
import org.mule.runtime.core.api.Event;
import org.mule.runtime.core.api.MuleContext;
import org.mule.runtime.core.api.construct.FlowConstruct;
import org.mule.runtime.core.api.el.ExtendedExpressionLanguageAdaptor;
import org.mule.runtime.core.api.expression.ExpressionRuntimeException;
import org.mule.runtime.core.config.i18n.CoreMessages;
import org.mule.runtime.core.el.mvel.datatype.MvelDataTypeResolver;
import org.mule.runtime.core.el.mvel.datatype.MvelEnricherDataTypePropagator;
import org.mule.runtime.core.util.IOUtils;
import java.io.IOException;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import javax.activation.DataHandler;
import javax.activation.MimeType;
import javax.inject.Inject;
/**
* Expression language that uses MVEL (http://mvel.codehaus.org/).
*/
public class MVELExpressionLanguage implements ExtendedExpressionLanguageAdaptor, Initialisable {
public static final String OBJECT_FOR_ENRICHMENT = "__object_for_enrichment";
protected ParserConfiguration parserConfiguration;
protected MuleContext muleContext;
protected MVELExpressionExecutor expressionExecutor;
protected VariableResolverFactory staticContext;
protected VariableResolverFactory globalContext;
// Configuration
protected String globalFunctionsString;
protected String globalFunctionsFile;
protected Map<String, Function> globalFunctions = new HashMap<>();
protected Map<String, String> aliases = new HashMap<>();
protected Map<String, Class<?>> imports = new HashMap<>();
protected boolean autoResolveVariables = true;
protected MvelDataTypeResolver dataTypeResolver = new MvelDataTypeResolver();
protected MvelEnricherDataTypePropagator dataTypePropagator = new MvelEnricherDataTypePropagator();
@Inject
public MVELExpressionLanguage(MuleContext muleContext) {
this.muleContext = muleContext;
parserConfiguration = createParserConfiguration(imports);
expressionExecutor = new MVELExpressionExecutor(parserConfiguration);
}
@Override
public void initialise() throws InitialisationException {
parserConfiguration = createParserConfiguration(imports);
expressionExecutor = new MVELExpressionExecutor(parserConfiguration);
loadGlobalFunctions();
createStaticContext();
}
protected void createStaticContext() {
staticContext = new StaticVariableResolverFactory(parserConfiguration, muleContext);
globalContext = new GlobalVariableResolverFactory(getAliases(), getGlobalFunctions(), parserConfiguration, muleContext);
}
protected void loadGlobalFunctions() throws InitialisationException {
// Global functions defined in external file
if (globalFunctionsFile != null) {
try {
globalFunctions.putAll(CompilerTools
.extractAllDeclaredFunctions(new ExpressionCompiler(IOUtils.getResourceAsString(globalFunctionsFile, getClass()))
.compile()));
} catch (IOException e) {
throw new InitialisationException(CoreMessages.failedToLoad(globalFunctionsFile), e, this);
}
}
// Global functions defined in configuration file (take precedence over functions in file)
globalFunctions.putAll(CompilerTools.extractAllDeclaredFunctions(new ExpressionCompiler(globalFunctionsString).compile()));
}
@SuppressWarnings("unchecked")
public <T> T evaluateUntyped(String expression, Map<String, Object> vars) {
MVELExpressionLanguageContext context = createExpressionLanguageContext();
if (vars != null) {
context.setNextFactory(new CachedMapVariableResolverFactory(vars, new DelegateVariableResolverFactory(staticContext,
globalContext)));
} else {
context.setNextFactory(new DelegateVariableResolverFactory(staticContext, globalContext));
}
return (T) evaluateInternal(expression, context);
}
public <T> T evaluateUntyped(String expression, Event event, Event.Builder eventBuilder, FlowConstruct flowConstruct,
Map<String, Object> vars) {
if (event == null) {
return evaluateUntyped(expression, vars);
}
MVELExpressionLanguageContext context = createExpressionLanguageContext();
final DelegateVariableResolverFactory innerDelegate =
new DelegateVariableResolverFactory(globalContext, createVariableVariableResolverFactory(event, eventBuilder));
final DelegateVariableResolverFactory delegate =
new DelegateVariableResolverFactory(staticContext, new EventVariableResolverFactory(parserConfiguration, muleContext,
event, eventBuilder, flowConstruct,
innerDelegate));
if (vars != null) {
context.setNextFactory(new CachedMapVariableResolverFactory(vars, delegate));
} else {
context.setNextFactory(delegate);
}
return evaluateInternal(expression, context);
}
@Override
public void enrich(String expression, Event event, Event.Builder eventBuilder, FlowConstruct flowConstruct,
Object object) {
expression = removeExpressionMarker(expression);
expression = createEnrichmentExpression(expression);
evaluateUntyped(expression, event, eventBuilder, flowConstruct, singletonMap(OBJECT_FOR_ENRICHMENT, object));
}
@Override
public void enrich(String expression, Event event, Event.Builder eventBuilder, FlowConstruct flowConstruct,
TypedValue typedValue) {
expression = removeExpressionMarker(expression);
expression = createEnrichmentExpression(expression);
evaluateUntyped(expression, event, eventBuilder, flowConstruct, singletonMap(OBJECT_FOR_ENRICHMENT, typedValue.getValue()));
final Serializable compiledExpression = expressionExecutor.getCompiledExpression(expression);
event = eventBuilder.build();
dataTypePropagator.propagate(typedValue, event, eventBuilder, compiledExpression);
}
@Override
public Iterator<TypedValue<?>> split(String expression, int bachSize, Event event, FlowConstruct flowConstruct,
BindingContext bindingContext)
throws ExpressionRuntimeException {
TypedValue evaluate = evaluate(expression, event, flowConstruct, bindingContext);
return MVELSplitDataIterator.createFrom(evaluate.getValue(), bachSize);
}
@Override
public Iterator<TypedValue<?>> split(String expression, int bachSize, Event event, BindingContext bindingContext)
throws ExpressionRuntimeException {
TypedValue evaluate = evaluate(expression, event, bindingContext);
return MVELSplitDataIterator.createFrom(evaluate.getValue(), bachSize);
}
@Override
public void addGlobalBindings(BindingContext bindingContext) {
// Do nothing
}
@Override
public TypedValue evaluate(String expression, Event event, BindingContext context) {
return evaluate(expression, event, Event.builder(event), null, context);
}
@Override
public TypedValue evaluate(String expression, DataType expectedOutputType, Event event, BindingContext context)
throws ExpressionRuntimeException {
return evaluate(expression, expectedOutputType, event, null, context, false);
}
@Override
public TypedValue evaluate(String expression, DataType expectedOutputType, Event event, FlowConstruct flowConstruct,
BindingContext context, boolean failOnNull)
throws ExpressionRuntimeException {
return evaluate(expression, event, flowConstruct, context);
}
@Override
public TypedValue evaluate(String expression, Event event, FlowConstruct flowConstruct, BindingContext bindingContext) {
return evaluate(expression, event, Event.builder(event), flowConstruct, bindingContext);
}
@Override
public TypedValue evaluate(String expression, Event event, Event.Builder eventBuilder, FlowConstruct flowConstruct,
BindingContext bindingContext) {
expression = removeExpressionMarker(expression);
Map<String, Object> bindingMap = bindingContext.identifiers().stream().collect(toMap(id -> id,
id -> bindingContext.lookup(id).get()
.getValue()));
final Object value = evaluateUntyped(expression, event, eventBuilder, flowConstruct, bindingMap);
if (value instanceof TypedValue) {
return (TypedValue) value;
} else {
final Serializable compiledExpression = expressionExecutor.getCompiledExpression(expression);
DataType dataType = event != null ? dataTypeResolver.resolve(value, event, compiledExpression) : OBJECT;
return new TypedValue(value, dataType);
}
}
@SuppressWarnings("unchecked")
protected <T> T evaluateInternal(String expression, MVELExpressionLanguageContext variableResolverFactory) {
validate(expression);
expression = removeExpressionMarker(expression);
try {
return (T) expressionExecutor.execute(expression, variableResolverFactory);
} catch (Exception e) {
throw new ExpressionRuntimeException(CoreMessages.expressionEvaluationFailed(e.getMessage(), expression), e);
}
}
@Override
public ValidationResult validate(String expression) {
if (expression.startsWith(DEFAULT_EXPRESSION_PREFIX)) {
if (!expression.endsWith(DEFAULT_EXPRESSION_POSTFIX)) {
return failure("Expression string is not an expression", expression);
}
expression = expression.substring(2, expression.length() - 1);
}
try {
expressionExecutor.validate(expression);
} catch (CompileException e) {
return failure(e.getMessage(), expression);
}
return success();
}
protected MVELExpressionLanguageContext createExpressionLanguageContext() {
return new MVELExpressionLanguageContext(parserConfiguration, muleContext);
}
public static ParserConfiguration createParserConfiguration(Map<String, Class<?>> imports) {
ParserConfiguration ParserConfiguration = new ParserConfiguration();
configureParserConfiguration(ParserConfiguration, imports);
return ParserConfiguration;
}
protected static void configureParserConfiguration(ParserConfiguration parserConfiguration, Map<String, Class<?>> imports) {
// defaults imports
// JRE
parserConfiguration.addPackageImport("java.io");
parserConfiguration.addPackageImport("java.lang");
parserConfiguration.addPackageImport("java.net");
parserConfiguration.addPackageImport("java.util");
parserConfiguration.addImport(BigDecimal.class);
parserConfiguration.addImport(BigInteger.class);
parserConfiguration.addImport(DataHandler.class);
parserConfiguration.addImport(MimeType.class);
parserConfiguration.addImport(Pattern.class);
// Mule
parserConfiguration.addImport(DataType.class);
parserConfiguration.addImport(AbstractDataTypeBuilderFactory.class);
// Global imports
for (Entry<String, Class<?>> importEntry : imports.entrySet()) {
parserConfiguration.addImport(importEntry.getKey(), importEntry.getValue());
}
}
public void setGlobalFunctionsString(String globalFunctionsString) {
this.globalFunctionsString = globalFunctionsString;
}
public void setAliases(Map<String, String> aliases) {
this.aliases = aliases;
}
public void setImports(Map<String, Class<?>> imports) {
this.imports = imports;
}
public void setAutoResolveVariables(boolean autoResolveVariables) {
this.autoResolveVariables = autoResolveVariables;
}
public void setDataTypeResolver(MvelDataTypeResolver dataTypeResolver) {
this.dataTypeResolver = dataTypeResolver;
}
public void addGlobalFunction(String name, Function function) {
this.globalFunctions.put(name, function);
}
public void addImport(String name, Class<?> clazz) {
this.imports.put(name, clazz);
}
public void addAlias(String name, String expression) {
this.aliases.put(name, expression);
}
public void setGlobalFunctionsFile(String globalFunctionsFile) {
this.globalFunctionsFile = globalFunctionsFile;
}
protected VariableResolverFactory createVariableVariableResolverFactory(Event event, Event.Builder eventBuilder) {
if (autoResolveVariables) {
return new VariableVariableResolverFactory(parserConfiguration, muleContext, event, eventBuilder);
} else {
return new NullVariableResolverFactory();
}
}
public Map<String, String> getAliases() {
return aliases;
}
public Map<String, Function> getGlobalFunctions() {
return globalFunctions;
}
public ParserConfiguration getParserConfiguration() {
return parserConfiguration;
}
public static String removeExpressionMarker(String expression) {
if (expression == null) {
throw new IllegalArgumentException(CoreMessages.objectIsNull("expression").getMessage());
}
if (expression.startsWith(DEFAULT_EXPRESSION_PREFIX)) {
expression = expression.substring(2, expression.length() - 1);
}
if (expression.startsWith(MEL_PREFIX + PREFIX_EXPR_SEPARATOR)) {
expression = expression.substring((MEL_PREFIX + PREFIX_EXPR_SEPARATOR).length());
}
return expression;
}
protected String createEnrichmentExpression(String expression) {
if (expression.contains("$")) {
expression = replace(expression, "$", OBJECT_FOR_ENRICHMENT);
} else {
expression = expression + "=" + OBJECT_FOR_ENRICHMENT;
}
return expression;
}
}