package com.hubspot.jinjava.el; import static org.apache.commons.lang3.exception.ExceptionUtils.getRootCauseMessage; import java.util.List; import javax.el.ELException; import javax.el.ExpressionFactory; import javax.el.PropertyNotFoundException; import javax.el.ValueExpression; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.ImmutableMap; import com.hubspot.jinjava.el.ext.NamedParameter; import com.hubspot.jinjava.interpret.DisabledException; import com.hubspot.jinjava.interpret.InterpretException; import com.hubspot.jinjava.interpret.JinjavaInterpreter; import com.hubspot.jinjava.interpret.TemplateError; import com.hubspot.jinjava.interpret.TemplateError.ErrorItem; import com.hubspot.jinjava.interpret.TemplateError.ErrorReason; import com.hubspot.jinjava.interpret.TemplateError.ErrorType; import com.hubspot.jinjava.interpret.TemplateSyntaxException; import com.hubspot.jinjava.interpret.errorcategory.BasicTemplateErrorCategory; import com.hubspot.jinjava.lib.fn.ELFunctionDefinition; import de.odysseus.el.tree.TreeBuilderException; /** * Resolves Jinja expressions. */ public class ExpressionResolver { private final JinjavaInterpreter interpreter; private final ExpressionFactory expressionFactory; private final JinjavaInterpreterResolver resolver; private final JinjavaELContext elContext; public ExpressionResolver(JinjavaInterpreter interpreter, ExpressionFactory expressionFactory) { this.interpreter = interpreter; this.expressionFactory = expressionFactory; this.resolver = new JinjavaInterpreterResolver(interpreter); this.elContext = new JinjavaELContext(resolver); for (ELFunctionDefinition fn : interpreter.getContext().getAllFunctions()) { this.elContext.setFunction(fn.getNamespace(), fn.getLocalName(), fn.getMethod()); } } /** * Resolve expression against current context. * * @param expression * Jinja expression. * @return Value of expression. */ public Object resolveExpression(String expression) { if (StringUtils.isBlank(expression)) { return ""; } interpreter.getContext().addResolvedExpression(expression.trim()); try { String elExpression = "#{" + expression.trim() + "}"; ValueExpression valueExp = expressionFactory.createValueExpression(elContext, elExpression, Object.class); Object result = valueExp.getValue(elContext); validateResult(result); return result; } catch (PropertyNotFoundException e) { interpreter.addError(new TemplateError(ErrorType.WARNING, ErrorReason.UNKNOWN, ErrorItem.PROPERTY, e.getMessage(), "", interpreter.getLineNumber(), e, BasicTemplateErrorCategory.UNKNOWN, ImmutableMap.of("exception", e.getMessage()))); } catch (TreeBuilderException e) { interpreter.addError(TemplateError.fromException(new TemplateSyntaxException(expression, "Error parsing '" + expression + "': " + StringUtils.substringAfter(e.getMessage(), "': "), interpreter.getLineNumber(), e))); } catch (ELException e) { interpreter.addError(TemplateError.fromException(new TemplateSyntaxException(expression, e.getMessage(), interpreter.getLineNumber(), e))); } catch (DisabledException e) { interpreter.addError(new TemplateError(ErrorType.FATAL, ErrorReason.DISABLED, ErrorItem.FUNCTION, e.getMessage(), expression, interpreter.getLineNumber(), e)); } catch (Exception e) { interpreter.addError(TemplateError.fromException(new InterpretException( String.format("Error resolving expression [%s]: " + getRootCauseMessage(e), expression), e, interpreter.getLineNumber()))); } return ""; } private void validateResult(Object result) { if (result instanceof NamedParameter) { throw new ELException("Unexpected '=' operator (use {% set %} tag for variable assignment)"); } } /** * Resolve property of bean. * * @param object * Bean. * @param propertyNames * Names of properties to resolve recursively. * @return Value of property. */ public Object resolveProperty(Object object, List<String> propertyNames) { // Always wrap base object. Object value = resolver.wrap(object); for (String propertyName : propertyNames) { if (value == null) { return null; } value = resolver.getValue(elContext, value, propertyName); } return value; } }