001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.language.spel;
018
019import org.apache.camel.CamelContext;
020import org.apache.camel.Exchange;
021import org.apache.camel.ExpressionEvaluationException;
022import org.apache.camel.spring.SpringCamelContext;
023import org.apache.camel.spring.util.RegistryBeanResolver;
024import org.apache.camel.support.ExpressionSupport;
025import org.springframework.context.ApplicationContext;
026import org.springframework.context.expression.BeanFactoryResolver;
027import org.springframework.context.expression.MapAccessor;
028import org.springframework.expression.BeanResolver;
029import org.springframework.expression.EvaluationContext;
030import org.springframework.expression.Expression;
031import org.springframework.expression.ParserContext;
032import org.springframework.expression.common.TemplateParserContext;
033import org.springframework.expression.spel.standard.SpelExpressionParser;
034import org.springframework.expression.spel.support.StandardEvaluationContext;
035
036/**
037 * Class responsible for evaluating
038 * <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions"> Spring
039 * Expression Language (SpEL)</a> in the context of Camel.
040 */
041public class SpelExpression extends ExpressionSupport {
042
043    private final String expressionString;
044    private final Class<?> type;
045    private final BeanResolver beanResolver;
046
047    // SpelExpressionParser is thread-safe according to the docs
048    private final SpelExpressionParser expressionParser;
049
050    private volatile Expression expression;
051
052    public SpelExpression(String expressionString, Class<?> type) {
053        this(expressionString, type, null);
054    }
055
056    public SpelExpression(String expressionString, Class<?> type, BeanResolver beanResolver) {
057        this.expressionString = expressionString;
058        this.type = type;
059        this.beanResolver = beanResolver;
060        this.expressionParser = new SpelExpressionParser();
061    }
062
063    public static SpelExpression spel(String expression) {
064        return new SpelExpression(expression, Object.class);
065    }
066
067    @Override
068    public <T> T evaluate(Exchange exchange, Class<T> tClass) {
069        if (expression == null) {
070            init(exchange.getContext());
071        }
072        try {
073            EvaluationContext evaluationContext = createEvaluationContext(exchange);
074            Object value = expression.getValue(evaluationContext);
075            // Let Camel handle the type conversion
076            return exchange.getContext().getTypeConverter().convertTo(tClass, value);
077        } catch (Exception e) {
078            throw new ExpressionEvaluationException(this, exchange, e);
079        }
080    }
081
082    private EvaluationContext createEvaluationContext(Exchange exchange) {
083        StandardEvaluationContext evaluationContext = new StandardEvaluationContext(new RootObject(exchange));
084        evaluationContext.addPropertyAccessor(new MapAccessor());
085        if (beanResolver != null) {
086            evaluationContext.setBeanResolver(beanResolver);
087        } else if (exchange.getContext() instanceof SpringCamelContext) {
088            // Support references (like @foo) in expressions to beans defined in the Registry/ApplicationContext
089            ApplicationContext applicationContext = ((SpringCamelContext) exchange.getContext()).getApplicationContext();
090            evaluationContext.setBeanResolver(new BeanFactoryResolver(applicationContext));
091        } else {
092            evaluationContext.setBeanResolver(new RegistryBeanResolver(exchange.getContext().getRegistry()));
093        }
094        return evaluationContext;
095    }
096
097    private Expression parseExpression() {
098        // Support template parsing with #{ } delimiters
099        ParserContext parserContext = new TemplateParserContext();
100        Expression expression = expressionParser.parseExpression(expressionString, parserContext);
101        return expression;
102    }
103
104    public Class<?> getType() {
105        return type;
106    }
107
108    @Override
109    protected String assertionFailureMessage(Exchange exchange) {
110        return expressionString;
111    }
112
113    @Override
114    public void init(CamelContext context) {
115        expression = parseExpression();
116    }
117
118    @Override
119    public String toString() {
120        return "SpelExpression[" + expressionString + "]";
121    }
122}