diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/JobParameterResolvingBeanFactoryPostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/JobParameterResolvingBeanFactoryPostProcessor.java deleted file mode 100644 index a8920458b..000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/JobParameterResolvingBeanFactoryPostProcessor.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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.Properties; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanDefinitionVisitor; -import org.springframework.beans.factory.config.BeanExpressionContext; -import org.springframework.beans.factory.config.BeanFactoryPostProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.context.expression.StandardBeanExpressionResolver; -import org.springframework.core.PriorityOrdered; -import org.springframework.util.StringValueResolver; - -/** - *

- * This post processor will resolve values in bean definitions that represent #{jobParameter['key']} - * expressions prior to the standard SPeL resolution if they correspond with an entry in the user - * provided {@link java.util.Properties} to the start or restart methods of the - * {@link org.springframework.batch.core.jsr.launch.JsrJobOperator}. This allows jobProperty replacements - * to occur for elements that require resolution prior to context initialization and are not step scoped. - *

- * - * @author Chris Schaefer - * @since 3.0 - */ -public class JobParameterResolvingBeanFactoryPostProcessor implements BeanFactoryPostProcessor, PriorityOrdered { - private Properties properties; - private JobParameterResolver jobParameterResolver; - private static final Pattern JOB_PARAMETERS_KEY_PATTERN = Pattern.compile("'([^']*?)'"); - private static final Pattern JOB_PARAMETERS_PATTERN = Pattern.compile("(#\\{jobParameters[^}]+\\})"); - - public JobParameterResolvingBeanFactoryPostProcessor(Properties properties) { - this.properties = properties; - } - - /* (non-Javadoc) - * @see org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory(org.springframework.beans.factory.config.ConfigurableListableBeanFactory) - */ - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { - if (jobParameterResolver == null) { - this.jobParameterResolver = new JobParameterResolver(beanFactory, properties); - } - - String[] beanNames = beanFactory.getBeanDefinitionNames(); - - for (String curName : beanNames) { - jobParameterResolver.visitBeanDefinition(beanFactory.getBeanDefinition(curName)); - } - } - - /** - * Sets this {@link BeanFactoryPostProcessor} to the highest precedence so that - * it is executed as early as possible in the chain of {@link BeanFactoryPostProcessor}s - */ - @Override - public int getOrder() { - return PriorityOrdered.HIGHEST_PRECEDENCE; - } - - protected class JobParameterResolver { - private Properties properties; - private JsrExpressionParser jsrExpressionParser; - private BeanDefinitionVisitor beanDefinitionVisitor; - - public JobParameterResolver(ConfigurableListableBeanFactory beanFactory, Properties properties) { - this.properties = properties; - this.jsrExpressionParser = new JsrExpressionParser(new StandardBeanExpressionResolver(), new BeanExpressionContext(beanFactory, null)); - this.beanDefinitionVisitor = new BeanDefinitionVisitor(new JobParameterStringValueResolver()); - } - - public void visitBeanDefinition(BeanDefinition beanDefinition) { - if (properties != null && ! properties.isEmpty() && ! "step".equals(beanDefinition.getScope())) { - beanDefinitionVisitor.visitBeanDefinition(beanDefinition); - } - } - - protected class JobParameterStringValueResolver implements StringValueResolver { - private static final String NULL = "null"; - - @Override - public String resolveStringValue(String value) { - if (value != null && ! "".equals(value)) { - String resolvedString = resolveJobParameters(value); - - if (!"".equals(resolvedString)) { - return jsrExpressionParser.parseExpression(resolvedString); - } - } - - return value; - } - - private String resolveJobParameters(String value) { - StringBuffer valueBuffer = new StringBuffer(); - Matcher jobParameterMatcher = JOB_PARAMETERS_PATTERN.matcher(value); - - while (jobParameterMatcher.find()) { - Matcher jobParameterKeyMatcher = JOB_PARAMETERS_KEY_PATTERN.matcher(jobParameterMatcher.group(1)); - - if (jobParameterKeyMatcher.find()) { - String extractedProperty = jobParameterKeyMatcher.group(1); - - if (properties.containsKey(extractedProperty)) { - String resolvedProperty = properties.getProperty(extractedProperty); - jobParameterMatcher.appendReplacement(valueBuffer, resolvedProperty); - } else { - jobParameterMatcher.appendReplacement(valueBuffer, NULL); - } - } - } - - jobParameterMatcher.appendTail(valueBuffer); - - return valueBuffer.toString(); - } - } - } -} 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 index 3dd184a13..6bbed0f18 100644 --- 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 @@ -31,7 +31,8 @@ import org.springframework.util.StringUtils; * @since 3.0 */ public class JsrExpressionParser { - public static final String QUOTE = "'"; + private static final String QUOTE = "'"; + private static final String NULL = "null"; private static final String ELVIS_RHS = ":"; private static final String ELVIS_LHS = "\\?"; private static final String ELVIS_OPERATOR = "?:"; @@ -43,6 +44,15 @@ public class JsrExpressionParser { private BeanExpressionContext beanExpressionContext; private BeanExpressionResolver beanExpressionResolver; + /** + *

+ * Creates a new instance of this expression parser without and expression resolver. Creating + * an instance via this constructor will still parse expressions but no resolution of operators + * will occur as its expected the caller will. + *

+ */ + public JsrExpressionParser() { } + /** *

* Creates a new instances of this expression parser with the provided expression resolver and context to evaluate @@ -66,11 +76,13 @@ public class JsrExpressionParser { * @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) { - return parseConditionalExpressions(expression); + String expressionToParse = expression; + + if (StringUtils.countOccurrencesOf(expressionToParse, ELVIS_OPERATOR) > 0) { + expressionToParse = parseConditionalExpressions(expressionToParse); } - return (String) beanExpressionResolver.evaluate(expression, beanExpressionContext); + return evaluateExpression(expressionToParse); } private String parseConditionalExpressions(String expression) { @@ -84,20 +96,35 @@ public class JsrExpressionParser { 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); + StringBuilder parsedExpression = new StringBuilder(); + + if(beanExpressionResolver != null) { + parsedExpression.append(EXPRESSION_PREFIX) + .append(evaluateExpression(value)) + .append(ELVIS_OPERATOR) + .append(QUOTE) + .append(evaluateExpression(defaultValue)) + .append(QUOTE) + .append(EXPRESSION_SUFFIX); + } else { + if(NULL.equals(value)) { + parsedExpression.append(defaultValue); + } else { + parsedExpression.append(value); + } + } expressionToParse = expressionToParse.replace(conditionalExpression, parsedExpression); } - expressionToParse = expressionToParse.replace(DEFAULT_VALUE_SEPARATOR, ""); + return expressionToParse.replace(DEFAULT_VALUE_SEPARATOR, ""); + } - return (String) beanExpressionResolver.evaluate(expressionToParse, beanExpressionContext); + private String evaluateExpression(String expression) { + if(beanExpressionResolver != null) { + return (String) beanExpressionResolver.evaluate(expression, beanExpressionContext); + } + + return expression; } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JsrBeanDefinitionDocumentReader.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JsrBeanDefinitionDocumentReader.java new file mode 100644 index 000000000..25d4baac9 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JsrBeanDefinitionDocumentReader.java @@ -0,0 +1,253 @@ +/* + * 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.xml; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.batch.core.jsr.configuration.support.JsrExpressionParser; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.ls.DOMImplementationLS; +import org.w3c.dom.traversal.DocumentTraversal; +import org.w3c.dom.traversal.NodeFilter; +import org.w3c.dom.traversal.NodeIterator; + +/** + *

+ * {@link DefaultBeanDefinitionDocumentReader} extension to hook into the pre/post processing of the provided + * XML document, ensuring any references to property operators such as jobParameters and jobProperties are + * resolved prior to loading the context. Since we know these initial values upfront, doing this transformation + * allows us to ensure values are retrieved in their resolved form prior to loading the context and property + * operators can be used on any element. + *

+ * + * @author Chris Schaefer + * @since 3.0 + */ +public class JsrBeanDefinitionDocumentReader extends DefaultBeanDefinitionDocumentReader { + private static final String NULL = "null"; + private static final String ROOT_JOB_ELEMENT_NAME = "job"; + private static final String JOB_PROPERTY_ELEMENT_NAME = "property"; + private static final String JOB_PROPERTIES_ELEMENT_NAME = "properties"; + private static final String JOB_PROPERTY_ELEMENT_NAME_ATTRIBUTE = "name"; + private static final String JOB_PROPERTY_ELEMENT_VALUE_ATTRIBUTE = "value"; + private static final String JOB_PROPERTIES_KEY_NAME = "jobProperties"; + private static final String JOB_PARAMETERS_KEY_NAME = "jobParameters"; + private static final String JOB_PARAMETERS_BEAN_DEFINITION_NAME = "jsr_jobParameters"; + private static final Log LOG = LogFactory.getLog(JsrBeanDefinitionDocumentReader.class); + private static final Pattern PROPERTY_KEY_SEPERATOR = Pattern.compile("'([^']*?)'"); + private static final Pattern OPERATOR_PATTERN = Pattern.compile("(#\\{(job(Properties|Parameters))[^}]+\\})"); + + private BeanDefinitionRegistry beanDefinitionRegistry; + private JsrExpressionParser expressionParser = new JsrExpressionParser(); + private Map propertyMap = new HashMap(); + + /** + *

+ * Creates a new {@link JsrBeanDefinitionDocumentReader} instance. + *

+ */ + public JsrBeanDefinitionDocumentReader() { } + + /** + *

+ * Create a new {@link JsrBeanDefinitionDocumentReader} instance with the provided + * {@link BeanDefinitionRegistry}. + *

+ * + * @param beanDefinitionRegistry the {@link BeanDefinitionRegistry} to use + */ + public JsrBeanDefinitionDocumentReader(BeanDefinitionRegistry beanDefinitionRegistry) { + this.beanDefinitionRegistry = beanDefinitionRegistry; + } + + @Override + protected void preProcessXml(Element root) { + if (ROOT_JOB_ELEMENT_NAME.equals(root.getLocalName())) { + initProperties(root); + transformDocument(root); + + if (LOG.isDebugEnabled()) { + LOG.debug("Transformed XML from preProcessXml: " + elementToString(root)); + } + } + } + + protected void initProperties(Element root) { + propertyMap.put(JOB_PARAMETERS_KEY_NAME, initJobParameters()); + propertyMap.put(JOB_PROPERTIES_KEY_NAME, initJobProperties(root)); + + resolvePropertyValues(propertyMap.get(JOB_PARAMETERS_KEY_NAME)); + resolvePropertyValues(propertyMap.get(JOB_PROPERTIES_KEY_NAME)); + } + + private Properties initJobParameters() { + Properties jobParameters = new Properties(); + + if (getBeanDefinitionRegistry().containsBeanDefinition(JOB_PARAMETERS_BEAN_DEFINITION_NAME)) { + BeanDefinition beanDefintion = getBeanDefinitionRegistry().getBeanDefinition(JOB_PARAMETERS_BEAN_DEFINITION_NAME); + + Properties properties = (Properties) beanDefintion.getConstructorArgumentValues() + .getGenericArgumentValue(Properties.class) + .getValue(); + + if (properties == null) { + return new Properties(); + } + + jobParameters.putAll(properties); + } + + return jobParameters; + } + + private Properties initJobProperties(Element root) { + Properties properties = new Properties(); + Node propertiesNode = root.getElementsByTagName(JOB_PROPERTIES_ELEMENT_NAME).item(0); + + if(propertiesNode != null) { + NodeList children = propertiesNode.getChildNodes(); + + for(int i=0; i < children.getLength(); i++) { + Node child = children.item(i); + + if(JOB_PROPERTY_ELEMENT_NAME.equals(child.getLocalName())) { + NamedNodeMap attributes = child.getAttributes(); + Node name = attributes.getNamedItem(JOB_PROPERTY_ELEMENT_NAME_ATTRIBUTE); + Node value = attributes.getNamedItem(JOB_PROPERTY_ELEMENT_VALUE_ATTRIBUTE); + + properties.setProperty(name.getNodeValue(), value.getNodeValue()); + } + } + } + + return properties; + } + + private void resolvePropertyValues(Properties properties) { + for (String propertyKey : properties.stringPropertyNames()) { + String resolvedPropertyValue = resolvePropertyValue(properties.getProperty(propertyKey)); + + if(!properties.getProperty(propertyKey).equals(resolvedPropertyValue)) { + properties.setProperty(propertyKey, resolvedPropertyValue); + } + } + } + + private String resolvePropertyValue(String propertyValue) { + String resolvedValue = resolveValue(propertyValue); + + Matcher jobParameterMatcher = OPERATOR_PATTERN.matcher(resolvedValue); + + while (jobParameterMatcher.find()) { + resolvedValue = resolvePropertyValue(resolvedValue); + } + + return resolvedValue; + } + + private String resolveValue(String value) { + StringBuffer valueBuffer = new StringBuffer(); + Matcher jobParameterMatcher = OPERATOR_PATTERN.matcher(value); + + while (jobParameterMatcher.find()) { + Matcher jobParameterKeyMatcher = PROPERTY_KEY_SEPERATOR.matcher(jobParameterMatcher.group(1)); + + if (jobParameterKeyMatcher.find()) { + String propertyType = jobParameterMatcher.group(2); + String extractedProperty = jobParameterKeyMatcher.group(1); + + Properties properties = propertyMap.get(propertyType); + + if(properties == null) { + throw new IllegalArgumentException("Unknown property type: " + propertyType); + } + + String resolvedProperty = properties.getProperty(extractedProperty, NULL); + + if (NULL.equals(resolvedProperty)) { + LOG.info(propertyType + " with key of: " + extractedProperty + " could not be resolved. Possible configuration error?"); + } + + jobParameterMatcher.appendReplacement(valueBuffer, resolvedProperty); + } + } + + jobParameterMatcher.appendTail(valueBuffer); + String resolvedValue = valueBuffer.toString(); + + if (NULL.equals(resolvedValue)) { + return ""; + } + + return expressionParser.parseExpression(resolvedValue); + } + + private BeanDefinitionRegistry getBeanDefinitionRegistry() { + return beanDefinitionRegistry != null ? beanDefinitionRegistry : getReaderContext().getRegistry(); + } + + private void transformDocument(Element root) { + DocumentTraversal traversal = (DocumentTraversal) root.getOwnerDocument(); + NodeIterator iterator = traversal.createNodeIterator(root, NodeFilter.SHOW_ELEMENT, null, true); + + for (Node n = iterator.nextNode(); n != null; n = iterator.nextNode()) { + NamedNodeMap map = n.getAttributes(); + + if (map.getLength() > 0) { + for (int i = 0; i < map.getLength(); i++) { + Node node = map.item(i); + String nodeValue = node.getNodeValue(); + String resolvedValue = resolveValue(nodeValue); + + if(!nodeValue.equals(resolvedValue)) { + node.setNodeValue(resolvedValue); + } + } + } else { + String nodeValue = n.getTextContent(); + String resolvedValue = resolveValue(nodeValue); + + if(!nodeValue.equals(resolvedValue)) { + n.setTextContent(resolvedValue); + } + } + } + } + + protected Properties getJobParameters() { + return propertyMap.get(JOB_PARAMETERS_KEY_NAME); + } + + protected Properties getJobProperties() { + return propertyMap.get(JOB_PROPERTIES_KEY_NAME); + } + + private String elementToString(Element root) { + DOMImplementationLS domImplLS = (DOMImplementationLS) root.getOwnerDocument().getImplementation(); + return domImplLS.createLSSerializer().writeToString(root); + } +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JsrXmlApplicationContext.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JsrXmlApplicationContext.java new file mode 100644 index 000000000..7de3da48f --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JsrXmlApplicationContext.java @@ -0,0 +1,88 @@ +/* + * 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.xml; + +import java.util.Properties; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.io.Resource; + +/** + *

+ * {@link GenericApplicationContext} implementation providing JSR-352 related context operations. + *

+ * + * @author Chris Schaefer + * @since 3.0 + */ +public class JsrXmlApplicationContext extends GenericApplicationContext { + private static final String JOB_PARAMETERS_BEAN_DEFINITION_NAME = "jsr_jobParameters"; + + private XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); + + /** + *

+ * Create a new context instance with no job parameters. + *

+ */ + public JsrXmlApplicationContext() { + reader.setDocumentReaderClass(JsrBeanDefinitionDocumentReader.class); + reader.setEnvironment(this.getEnvironment()); + } + + /** + *

+ * Create a new context instance using the provided {@link Properties} representing job + * parameters when pre-processing the job definition document. + *

+ * + * @param jobParameters the {@link Properties} representing job parameters + */ + public JsrXmlApplicationContext(Properties jobParameters) { + reader.setDocumentReaderClass(JsrBeanDefinitionDocumentReader.class); + reader.setEnvironment(this.getEnvironment()); + + storeJobParameters(jobParameters); + } + + private void storeJobParameters(Properties properties) { + BeanDefinition jobParameters = BeanDefinitionBuilder.genericBeanDefinition(Properties.class).getBeanDefinition(); + jobParameters.getConstructorArgumentValues().addGenericArgumentValue(properties != null ? properties : new Properties()); + + reader.getRegistry().registerBeanDefinition(JOB_PARAMETERS_BEAN_DEFINITION_NAME, jobParameters); + } + + protected XmlBeanDefinitionReader getReader() { + return reader; + } + + /** + * Set whether to use XML validation. Default is true. + */ + public void setValidating(boolean validating) { + this.reader.setValidating(validating); + } + + /** + * Load bean definitions from the given XML resources. + * @param resources one or more resources to load from + */ + public void load(Resource... resources) { + this.reader.loadBeanDefinitions(resources); + } +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ListenerParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ListenerParser.java index df387469a..f2e10b089 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ListenerParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ListenerParser.java @@ -42,6 +42,8 @@ public class ListenerParser { private static final String REF_ATTRIBUTE = "ref"; private static final String LISTENER_ELEMENT = "listener"; private static final String LISTENERS_ELEMENT = "listeners"; + private static final String SCOPE_STEP = "step"; + @SuppressWarnings("rawtypes") private Class listenerType; private String propertyKey; @@ -103,15 +105,35 @@ public class ListenerParser { } protected void applyListenerScope(String beanName, BeanDefinitionRegistry beanDefinitionRegistry) { - if (listenerType == JobListenerFactoryBean.class) { - if (beanDefinitionRegistry.containsBeanDefinition(beanName)) { - BeanDefinition beanDefinition = beanDefinitionRegistry.getBeanDefinition(beanName); - beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON); - beanDefinition.setLazyInit(true); - } + BeanDefinition beanDefinition = getListenerBeanDefinition(beanName, beanDefinitionRegistry); + beanDefinition.setScope(getListenerScope()); + beanDefinition.setLazyInit(isLazyInit()); + + if (!beanDefinitionRegistry.containsBeanDefinition(beanName)) { + beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition); } } + private BeanDefinition getListenerBeanDefinition(String beanName, BeanDefinitionRegistry beanDefinitionRegistry) { + if (beanDefinitionRegistry.containsBeanDefinition(beanName)) { + return beanDefinitionRegistry.getBeanDefinition(beanName); + } + + return BeanDefinitionBuilder.genericBeanDefinition(beanName).getBeanDefinition(); + } + + private boolean isLazyInit() { + return listenerType == JobListenerFactoryBean.class; + } + + private String getListenerScope() { + if (listenerType == JobListenerFactoryBean.class) { + return BeanDefinition.SCOPE_SINGLETON; + } + + return SCOPE_STEP; + } + private BatchArtifact.BatchArtifactType getBatchArtifactType(String stepName) { return (stepName != null && !"".equals(stepName)) ? BatchArtifact.BatchArtifactType.STEP_ARTIFACT : BatchArtifact.BatchArtifactType.ARTIFACT; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/launch/JsrJobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/launch/JsrJobOperator.java index a735f5620..b3590231a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/launch/JsrJobOperator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/launch/JsrJobOperator.java @@ -55,7 +55,7 @@ import org.springframework.batch.core.converter.JobParametersConverter; import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.jsr.JobContextFactoryBean; import org.springframework.batch.core.jsr.JsrJobParametersConverter; -import org.springframework.batch.core.jsr.configuration.support.JobParameterResolvingBeanFactoryPostProcessor; +import org.springframework.batch.core.jsr.configuration.xml.JsrXmlApplicationContext; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.scope.context.StepSynchronizationManager; import org.springframework.batch.core.step.NoSuchStepException; @@ -73,7 +73,6 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.context.ApplicationContext; import org.springframework.context.access.ContextSingletonBeanFactoryLocator; -import org.springframework.context.support.GenericXmlApplicationContext; import org.springframework.core.convert.converter.Converter; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; @@ -450,7 +449,9 @@ public class JsrJobOperator implements JobOperator, InitializingBean { String jobName = previousJobExecution.getJobInstance().getJobName(); - final GenericXmlApplicationContext batchContext = new GenericXmlApplicationContext(); + Properties jobRestartProperties = getJobRestartProperties(params, previousJobExecution); + + final JsrXmlApplicationContext batchContext = new JsrXmlApplicationContext(jobRestartProperties); batchContext.setValidating(false); Resource batchXml = new ClassPathResource("/META-INF/batch.xml"); @@ -464,8 +465,6 @@ public class JsrJobOperator implements JobOperator, InitializingBean { batchContext.load(jobXml); } - batchContext.addBeanFactoryPostProcessor(new JobParameterResolvingBeanFactoryPostProcessor(params)); - AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.jsr.JobContextFactoryBean").getBeanDefinition(); beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON); batchContext.registerBeanDefinition(JSR_JOB_CONTEXT_BEAN_NAME, beanDefinition); @@ -487,7 +486,6 @@ public class JsrJobOperator implements JobOperator, InitializingBean { final org.springframework.batch.core.JobExecution jobExecution; try { - Properties jobRestartProperties = getJobRestartProperties(params, previousJobExecution); JobParameters jobParameters = jobParametersConverter.getJobParameters(jobRestartProperties); jobExecution = jobRepository.createJobExecution(previousJobExecution.getJobInstance(), jobParameters, previousJobExecution.getJobConfigurationName()); } catch (Exception e) { @@ -579,7 +577,7 @@ public class JsrJobOperator implements JobOperator, InitializingBean { @SuppressWarnings("resource") public long start(String jobName, Properties params) throws JobStartException, JobSecurityException { - final GenericXmlApplicationContext batchContext = new GenericXmlApplicationContext(); + final JsrXmlApplicationContext batchContext = new JsrXmlApplicationContext(params); batchContext.setValidating(false); Resource batchXml = new ClassPathResource("/META-INF/batch.xml"); @@ -594,7 +592,6 @@ public class JsrJobOperator implements JobOperator, InitializingBean { batchContext.load(jobXml); } - batchContext.addBeanFactoryPostProcessor(new JobParameterResolvingBeanFactoryPostProcessor(params)); AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.jsr.JobContextFactoryBean").getBeanDefinition(); beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON); batchContext.registerBeanDefinition(JSR_JOB_CONTEXT_BEAN_NAME, beanDefinition); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JobPropertyTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JobPropertyTests.java index d05767962..8ff2c4d4e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JobPropertyTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JobPropertyTests.java @@ -15,10 +15,9 @@ */ package org.springframework.batch.core.jsr.configuration.xml; -import static org.junit.Assert.assertEquals; - import java.io.Serializable; import java.util.List; +import java.util.Properties; import javax.batch.api.BatchProperty; import javax.batch.api.Batchlet; @@ -28,18 +27,15 @@ import javax.batch.api.chunk.ItemProcessor; import javax.batch.api.chunk.ItemReader; import javax.batch.api.chunk.ItemWriter; import javax.batch.api.listener.StepListener; +import javax.batch.runtime.BatchStatus; +import javax.batch.runtime.JobExecution; +import javax.batch.runtime.context.JobContext; import javax.inject.Inject; import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.batch.core.jsr.JsrTestUtils; + +import static junit.framework.Assert.assertEquals; /** *

@@ -47,20 +43,18 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; *

* * @author Chris Schaefer + * @since 3.0 */ -@ContextConfiguration -@RunWith(SpringJUnit4ClassRunner.class) public class JobPropertyTests { - @Autowired - private Job job; - - @Autowired - private JobLauncher jobLauncher; - @Test public void testJobPropertyConfiguration() throws Exception { - JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); - assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); + Properties jobParameters = new Properties(); + jobParameters.setProperty("allow.start.if.complete", "true"); + jobParameters.setProperty("deciderName", "stepDecider"); + jobParameters.setProperty("deciderNumber", "1"); + + JobExecution jobExecution = JsrTestUtils.runJob("jsrJobPropertyTests", jobParameters, 5000L); + assertEquals(BatchStatus.COMPLETED, jobExecution.getBatchStatus()); } public static final class TestItemReader implements ItemReader { @@ -74,7 +68,7 @@ public class JobPropertyTests { @Inject @BatchProperty(name = "notDefinedAnnotationNamedProperty") String notDefinedAnnotationNamedProperty; @Inject @BatchProperty String jobPropertyName1; @Inject @BatchProperty String jobPropertyName2; - @Inject InjectTestObj injectAnnotatedOnlyField; + @Inject JobContext injectAnnotatedOnlyField; @BatchProperty String batchAnnotatedOnlyField; @Inject javax.batch.runtime.context.StepContext stepContext; @@ -96,7 +90,7 @@ public class JobPropertyTests { org.springframework.util.Assert.isNull(notDefinedAnnotationNamedProperty); org.springframework.util.Assert.isNull(batchAnnotatedOnlyField); org.springframework.util.Assert.notNull(injectAnnotatedOnlyField); - org.springframework.util.Assert.isTrue("Chris".equals(injectAnnotatedOnlyField.getName())); + org.springframework.util.Assert.isTrue("job1".equals(injectAnnotatedOnlyField.getJobName())); org.springframework.util.Assert.isNull(readerPropertyName3); } @@ -272,16 +266,4 @@ public class JobPropertyTests { public void stop() throws Exception { } } - - public static class InjectTestObj { - private String name; - - public InjectTestObj(String name) { - this.name = name; - } - - public String getName() { - return name; - } - } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JsrBeanDefinitionDocumentReaderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JsrBeanDefinitionDocumentReaderTests.java new file mode 100644 index 000000000..239970779 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JsrBeanDefinitionDocumentReaderTests.java @@ -0,0 +1,141 @@ +package org.springframework.batch.core.jsr.configuration.xml; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.Test; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.xml.DefaultDocumentLoader; +import org.springframework.beans.factory.xml.DelegatingEntityResolver; +import org.springframework.beans.factory.xml.DocumentLoader; +import org.springframework.core.io.ClassPathResource; +import org.springframework.util.xml.SimpleSaxErrorHandler; +import org.w3c.dom.Document; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; + +/** + *

+ * Test cases around {@link JsrBeanDefinitionDocumentReader}. + *

+ * + * @author Chris Schaefer + */ +public class JsrBeanDefinitionDocumentReaderTests { + private static final String JOB_PARAMETERS_BEAN_DEFINITION_NAME = "jsr_jobParameters"; + + private Log logger = LogFactory.getLog(getClass()); + private DocumentLoader documentLoader = new DefaultDocumentLoader(); + private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger); + + @Test + public void testGetJobParameters() { + Properties jobParameters = new Properties(); + jobParameters.setProperty("jobParameter1", "jobParameter1Value"); + jobParameters.setProperty("jobParameter2", "jobParameter2Value"); + + JsrXmlApplicationContext applicationContext = new JsrXmlApplicationContext(jobParameters); + applicationContext.setValidating(false); + applicationContext.load(new ClassPathResource("baseContext.xml"), + new ClassPathResource("/META-INF/batch.xml"), + new ClassPathResource("/META-INF/batch-jobs/jsrPropertyPreparseTestJob.xml")); + applicationContext.refresh(); + + BeanDefinition beanDefinition = applicationContext.getBeanDefinition(JOB_PARAMETERS_BEAN_DEFINITION_NAME); + + Properties processedJobParameters = (Properties) beanDefinition.getConstructorArgumentValues().getGenericArgumentValue(Properties.class).getValue(); + assertNotNull(processedJobParameters); + assertTrue("Wrong number of job parameters", processedJobParameters.size() == 2); + assertEquals("jobParameter1Value", processedJobParameters.getProperty("jobParameter1")); + assertEquals("jobParameter2Value", processedJobParameters.getProperty("jobParameter2")); + } + + @Test + public void testGetJobProperties() { + Document document = getDocument("/META-INF/batch-jobs/jsrPropertyPreparseTestJob.xml"); + + JsrXmlApplicationContext applicationContext = new JsrXmlApplicationContext(); + JsrBeanDefinitionDocumentReader documentReader = new JsrBeanDefinitionDocumentReader(applicationContext); + documentReader.initProperties(document.getDocumentElement()); + + Properties documentJobProperties = documentReader.getJobProperties(); + assertNotNull(documentJobProperties); + assertTrue("Wrong number of job properties", documentJobProperties.size() == 3); + assertEquals("jobProperty1Value", documentJobProperties.getProperty("jobProperty1")); + assertEquals("jobProperty1Value", documentJobProperties.getProperty("jobProperty2")); + assertEquals("", documentJobProperties.getProperty("jobProperty3")); + } + + @Test + public void testJobParametersResolution() { + Properties jobParameters = new Properties(); + jobParameters.setProperty("jobParameter1", "myfile.txt"); + jobParameters.setProperty("jobParameter2", "#{jobProperties['jobProperty2']}"); + jobParameters.setProperty("jobParameter3", "#{jobParameters['jobParameter1']}"); + + JsrXmlApplicationContext applicationContext = new JsrXmlApplicationContext(jobParameters); + applicationContext.setValidating(false); + applicationContext.load(new ClassPathResource("baseContext.xml"), + new ClassPathResource("/META-INF/batch.xml"), + new ClassPathResource("/META-INF/batch-jobs/jsrPropertyPreparseTestJob.xml")); + applicationContext.refresh(); + + Document document = getDocument("/META-INF/batch-jobs/jsrPropertyPreparseTestJob.xml"); + + JsrBeanDefinitionDocumentReader documentReader = new JsrBeanDefinitionDocumentReader(applicationContext); + documentReader.initProperties(document.getDocumentElement()); + + Properties resolvedParameters = documentReader.getJobParameters(); + + assertNotNull(resolvedParameters); + assertTrue("Wrong number of job parameters", resolvedParameters.size() == 3); + assertEquals("myfile.txt", resolvedParameters.getProperty("jobParameter1")); + assertEquals("jobProperty1Value", resolvedParameters.getProperty("jobParameter2")); + assertEquals("myfile.txt", resolvedParameters.getProperty("jobParameter3")); + } + + @Test + public void testJobPropertyResolution() { + Properties jobParameters = new Properties(); + jobParameters.setProperty("file.name", "myfile.txt"); + + JsrXmlApplicationContext applicationContext = new JsrXmlApplicationContext(jobParameters); + applicationContext.setValidating(false); + applicationContext.load(new ClassPathResource("baseContext.xml"), + new ClassPathResource("/META-INF/batch.xml"), + new ClassPathResource("/META-INF/batch-jobs/jsrPropertyPreparseTestJob.xml")); + applicationContext.refresh(); + + Document document = getDocument("/META-INF/batch-jobs/jsrPropertyPreparseTestJob.xml"); + + JsrBeanDefinitionDocumentReader documentReader = new JsrBeanDefinitionDocumentReader(applicationContext); + documentReader.initProperties(document.getDocumentElement()); + + Properties resolvedProperties = documentReader.getJobProperties(); + assertNotNull(resolvedProperties); + assertTrue("Wrong number of job properties", resolvedProperties.size() == 3); + assertEquals("jobProperty1Value", resolvedProperties.getProperty("jobProperty1")); + assertEquals("jobProperty1Value", resolvedProperties.getProperty("jobProperty2")); + assertEquals("myfile.txt", resolvedProperties.getProperty("jobProperty3")); + } + + private Document getDocument(String location) { + InputStream inputStream = ClassLoader.class.getResourceAsStream(location); + + try { + return documentLoader.loadDocument(new InputSource(inputStream), + new DelegatingEntityResolver(getClass().getClassLoader()), errorHandler, 0, true); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + try { + inputStream.close(); + } catch (IOException e) { } + } + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JsrXmlApplicationContextTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JsrXmlApplicationContextTests.java new file mode 100644 index 000000000..ded10112c --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JsrXmlApplicationContextTests.java @@ -0,0 +1,46 @@ +package org.springframework.batch.core.jsr.configuration.xml; + +import java.util.Properties; +import org.junit.Test; +import org.springframework.beans.factory.config.BeanDefinition; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; + +/** + *

+ * Test cases around {@link JsrXmlApplicationContext}. + *

+ * + * @author Chris Schaefer + */ +public class JsrXmlApplicationContextTests { + private static final String JOB_PARAMETERS_BEAN_DEFINITION_NAME = "jsr_jobParameters"; + + @Test + public void testNullProperties() { + JsrXmlApplicationContext applicationContext = new JsrXmlApplicationContext(null); + + BeanDefinition beanDefinition = applicationContext.getBeanDefinition(JOB_PARAMETERS_BEAN_DEFINITION_NAME); + Properties properties = (Properties) beanDefinition.getConstructorArgumentValues().getGenericArgumentValue(Properties.class).getValue(); + + assertNotNull("Properties should not be null", properties); + assertTrue("Properties should be empty", properties.isEmpty()); + } + + @Test + public void testWithProperties() { + Properties properties = new Properties(); + properties.put("prop1key", "prop1val"); + + JsrXmlApplicationContext applicationContext = new JsrXmlApplicationContext(properties); + + BeanDefinition beanDefinition = applicationContext.getBeanDefinition(JOB_PARAMETERS_BEAN_DEFINITION_NAME); + Properties storedProperties = (Properties) beanDefinition.getConstructorArgumentValues().getGenericArgumentValue(Properties.class).getValue(); + + assertNotNull("Properties should not be null", storedProperties); + assertFalse("Properties not be empty", storedProperties.isEmpty()); + assertEquals("prop1val", storedProperties.getProperty("prop1key")); + } +} diff --git a/spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrJobPropertyTests.xml b/spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrJobPropertyTests.xml new file mode 100644 index 000000000..5791bf1b3 --- /dev/null +++ b/spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrJobPropertyTests.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrPropertyPreparseTestJob.xml b/spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrPropertyPreparseTestJob.xml new file mode 100644 index 000000000..caa838a55 --- /dev/null +++ b/spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrPropertyPreparseTestJob.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/jsr/configuration/xml/JobPropertyTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/jsr/configuration/xml/JobPropertyTests-context.xml deleted file mode 100644 index 277ffa6f0..000000000 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/jsr/configuration/xml/JobPropertyTests-context.xml +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - stepDecider - 1 - - - -