From e1f6de38e04a5227fea2d4df193a5b50beaf2d00 Mon Sep 17 00:00:00 2001 From: Chris Schaefer Date: Sun, 8 Sep 2013 19:59:35 -0400 Subject: [PATCH] BATCH-2002: Initial support for complex conditional replacements --- .../BatchPropertyBeanPostProcessor.java | 15 +-- .../support/JsrExpressionParser.java | 99 +++++++++++++++++++ .../xml/JobPropertySubstitutionTests.java | 15 ++- .../JobPropertySubstitutionTests-context.xml | 2 + 4 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/JsrExpressionParser.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyBeanPostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyBeanPostProcessor.java index d65e4b156..d986d61c1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyBeanPostProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyBeanPostProcessor.java @@ -57,7 +57,6 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanExpressionContext; -import org.springframework.beans.factory.config.BeanExpressionResolver; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.expression.StandardBeanExpressionResolver; @@ -74,11 +73,9 @@ import org.springframework.util.ReflectionUtils; * @since 3.0 */ public class BatchPropertyBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { + private JsrExpressionParser jsrExpressionParser; private BatchPropertyContext batchPropertyContext; private Log logger = LogFactory.getLog(getClass()); - private ConfigurableListableBeanFactory beanFactory; - private BeanExpressionContext beanExpressionContext; - private BeanExpressionResolver expressionResolver = new StandardBeanExpressionResolver(); private Set> requiredAnnotations = new HashSet>(); public BatchPropertyBeanPostProcessor() { @@ -189,7 +186,7 @@ public class BatchPropertyBeanPostProcessor implements BeanPostProcessor, BeanFa if (batchArtifactProperties.containsKey(propertyKey)) { String propertyValue = (String) batchArtifactProperties.get(propertyKey); - return (String) expressionResolver.evaluate(propertyValue, beanExpressionContext); + return jsrExpressionParser.parseExpression(propertyValue); } return null; @@ -221,8 +218,12 @@ public class BatchPropertyBeanPostProcessor implements BeanPostProcessor, BeanFa "BatchPropertyBeanPostProcessor requires a ConfigurableListableBeanFactory"); } - this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; - this.beanExpressionContext = new BeanExpressionContext(this.beanFactory, this.beanFactory.getBean(StepScope.class)); + ConfigurableListableBeanFactory configurableListableBeanFactory = (ConfigurableListableBeanFactory) beanFactory; + + BeanExpressionContext beanExpressionContext = new BeanExpressionContext(configurableListableBeanFactory, + configurableListableBeanFactory.getBean(StepScope.class)); + + this.jsrExpressionParser = new JsrExpressionParser(new StandardBeanExpressionResolver(), beanExpressionContext); } @Autowired diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/JsrExpressionParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/JsrExpressionParser.java new file mode 100644 index 000000000..9aab36f80 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/JsrExpressionParser.java @@ -0,0 +1,99 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.jsr.configuration.support; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.springframework.beans.factory.config.BeanExpressionContext; +import org.springframework.beans.factory.config.BeanExpressionResolver; +import org.springframework.util.StringUtils; + +/** + *

+ * Support class for parsing JSR-352 expressions. The JSR-352 expression syntax, for + * example conditional/elvis statements need to be massaged a bit to work as SPeL expressions. + *

+ * + * @author Chris Schaefer + * @since 3.0 + */ +public class JsrExpressionParser { + public static final String QUOTE = "'"; + private static final String ELVIS_RHS = ":"; + private static final String ELVIS_LHS = "\\?"; + private static final String ELVIS_OPERATOR = "?:"; + private static final String EXPRESSION_SUFFIX = "}"; + private static final String EXPRESSION_PREFIX = "#{"; + private static final String DEFAULT_VALUE_SEPARATOR = ";"; + private static final Pattern CONDITIONAL_EXPRESSION = Pattern.compile("(#\\{\\w[^;]+)"); + + private BeanExpressionContext beanExpressionContext; + private BeanExpressionResolver beanExpressionResolver; + + /** + *

+ * Creates a new instances of this expression parser with the provided expression resolver and context to evaluate + * against. + *

+ * + * @param beanExpressionResolver the expression resolver to use when resolving expressions + * @param beanExpressionContext the expression context to resolve expressions against + */ + public JsrExpressionParser(BeanExpressionResolver beanExpressionResolver, BeanExpressionContext beanExpressionContext) { + this.beanExpressionContext = beanExpressionContext; + this.beanExpressionResolver = beanExpressionResolver; + } + + /** + *

+ * Parses the provided expression, applying any transformations needed to evaluate as a SPeL expression. + *

+ * + * @param expression the expression to parse and transform + * @return a JSR-352 transformed expression that can be evaluated by a SPeL parser + */ + public String parseExpression(String expression) { + if (StringUtils.countOccurrencesOf(expression, ELVIS_OPERATOR) > 0) { + String expressionToParse = expression; + + Matcher conditionalExpressionMatcher = CONDITIONAL_EXPRESSION.matcher(expressionToParse); + + while (conditionalExpressionMatcher.find()) { + String conditionalExpression = conditionalExpressionMatcher.group(1); + + String value = conditionalExpression.split(ELVIS_LHS)[0]; + String defaultValue = conditionalExpression.split(ELVIS_RHS)[1]; + + StringBuilder parsedExpression = new StringBuilder() + .append(EXPRESSION_PREFIX) + .append((String) beanExpressionResolver.evaluate(value, beanExpressionContext)) + .append(ELVIS_OPERATOR) + .append(QUOTE) + .append((String) beanExpressionResolver.evaluate(defaultValue, beanExpressionContext)) + .append(QUOTE) + .append(EXPRESSION_SUFFIX); + + expressionToParse = expressionToParse.replace(conditionalExpression, parsedExpression); + } + + expressionToParse = expressionToParse.replace(DEFAULT_VALUE_SEPARATOR, ""); + + return (String) beanExpressionResolver.evaluate(expressionToParse, beanExpressionContext); + } + + return (String) beanExpressionResolver.evaluate(expression, beanExpressionContext); + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JobPropertySubstitutionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JobPropertySubstitutionTests.java index 309f15cc3..31eef49af 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JobPropertySubstitutionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JobPropertySubstitutionTests.java @@ -56,7 +56,10 @@ public class JobPropertySubstitutionTests { @Test public void testPropertySubstitutionSimple() throws Exception { JobExecution jobExecution = jobLauncher.run(job, - new JobParametersBuilder().addString("testParam", "testParamValue").toJobParameters()); + new JobParametersBuilder() + .addString("testParam", "testParamValue") + .addString("file.name.junit", "myfile2") + .toJobParameters()); assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); } @@ -122,9 +125,19 @@ public class JobPropertySubstitutionTests { @BatchProperty String processorProperty1; + @Inject + @BatchProperty + String processorProperty2; + + @Inject + @BatchProperty + String processorProperty3; + @Override public Object processItem(Object item) throws Exception { assertEquals("testParamValue", processorProperty1); + assertEquals("myfile1.txt", processorProperty2); + assertEquals("/myfile2.txt", processorProperty3); return item; } diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/jsr/configuration/xml/JobPropertySubstitutionTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/jsr/configuration/xml/JobPropertySubstitutionTests-context.xml index d4958f6e7..518006d28 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/jsr/configuration/xml/JobPropertySubstitutionTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/jsr/configuration/xml/JobPropertySubstitutionTests-context.xml @@ -22,6 +22,8 @@ + +