001/*
002 * Copyright 2013 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.springframework.batch.core.jsr.configuration.support;
017
018import java.util.regex.Matcher;
019import java.util.regex.Pattern;
020import org.springframework.beans.factory.config.BeanExpressionContext;
021import org.springframework.beans.factory.config.BeanExpressionResolver;
022import org.springframework.util.StringUtils;
023
024/**
025 * <p>
026 * Support class for parsing JSR-352 expressions. The JSR-352 expression syntax, for
027 * example conditional/elvis statements need to be transformed a bit to be valid SPeL expressions.
028 * </p>
029 *
030 * @author Chris Schaefer
031 * @since 3.0
032 */
033public class JsrExpressionParser {
034        private static final String QUOTE = "'";
035        private static final String NULL = "null";
036        private static final String ELVIS_RHS = ":";
037        private static final String ELVIS_LHS = "\\?";
038        private static final String ELVIS_OPERATOR = "?:";
039        private static final String EXPRESSION_SUFFIX = "}";
040        private static final String EXPRESSION_PREFIX = "#{";
041        private static final String DEFAULT_VALUE_SEPARATOR = ";";
042        private static final Pattern CONDITIONAL_EXPRESSION = Pattern.compile("(((\\bnull\\b)|(#\\{\\w))[^;]+)");
043
044        private BeanExpressionContext beanExpressionContext;
045        private BeanExpressionResolver beanExpressionResolver;
046
047        /**
048         * <p>
049         * Creates a new instance of this expression parser without and expression resolver. Creating
050         * an instance via this constructor will still parse expressions but no resolution of operators
051         * will occur as its expected the caller will.
052         * </p>
053         */
054        public JsrExpressionParser() { }
055
056        /**
057         * <p>
058         * Creates a new instances of this expression parser with the provided expression resolver and context to evaluate
059         * against.
060         * </p>
061         *
062         * @param beanExpressionResolver the expression resolver to use when resolving expressions
063         * @param beanExpressionContext the expression context to resolve expressions against
064         */
065        public JsrExpressionParser(BeanExpressionResolver beanExpressionResolver, BeanExpressionContext beanExpressionContext) {
066                this.beanExpressionContext = beanExpressionContext;
067                this.beanExpressionResolver = beanExpressionResolver;
068        }
069
070        /**
071         * <p>
072         * Parses the provided expression, applying any transformations needed to evaluate as a SPeL expression.
073         * </p>
074         *
075         * @param expression the expression to parse and transform
076         * @return a JSR-352 transformed expression that can be evaluated by a SPeL parser
077         */
078        public String parseExpression(String expression) {
079                String expressionToParse = expression;
080
081                if (StringUtils.countOccurrencesOf(expressionToParse, ELVIS_OPERATOR) > 0) {
082                        expressionToParse = parseConditionalExpressions(expressionToParse);
083                }
084
085                return evaluateExpression(expressionToParse);
086        }
087
088        private String parseConditionalExpressions(String expression) {
089                String expressionToParse = expression;
090
091                Matcher conditionalExpressionMatcher = CONDITIONAL_EXPRESSION.matcher(expressionToParse);
092
093                while (conditionalExpressionMatcher.find()) {
094                        String conditionalExpression = conditionalExpressionMatcher.group(1);
095
096                        String value = conditionalExpression.split(ELVIS_LHS)[0];
097                        String defaultValue = conditionalExpression.split(ELVIS_RHS)[1];
098
099                        StringBuilder parsedExpression = new StringBuilder();
100
101                        if(beanExpressionResolver != null) {
102                                                parsedExpression.append(EXPRESSION_PREFIX)
103                                                .append(evaluateExpression(value))
104                                                .append(ELVIS_OPERATOR)
105                                                .append(QUOTE)
106                                                .append(evaluateExpression(defaultValue))
107                                                .append(QUOTE)
108                                                .append(EXPRESSION_SUFFIX);
109                        } else {
110                                if(NULL.equals(value)) {
111                                        parsedExpression.append(defaultValue);
112                                } else {
113                                        parsedExpression.append(value);
114                                }
115                        }
116
117                        expressionToParse = expressionToParse.replace(conditionalExpression, parsedExpression);
118                }
119
120                return expressionToParse.replace(DEFAULT_VALUE_SEPARATOR, "");
121        }
122
123        private String evaluateExpression(String expression) {
124                if(beanExpressionResolver != null) {
125                        return (String) beanExpressionResolver.evaluate(expression, beanExpressionContext);
126                }
127
128                return expression;
129        }
130}