From 34f9fa73416759dd4e7d03e3b3010109322b8a95 Mon Sep 17 00:00:00 2001 From: Chris Schaefer Date: Thu, 19 Sep 2013 11:18:23 -0400 Subject: [PATCH] General cleanup etc around properties and substitution * Move pre-context init of JobParameters support out of ThreadLocalClassloaderBeanPostProcessor * General cleanup in BatchPropertyBeanPostProcessor, move artifact type checking into classifer class, minor naming updates * Refactor BatchPropertyContext to use different data structures for different types of properties and provide named accessors * Remove path based artifact scoping support * Make sure expressions are parsed when resolving properties directly from XML * Update property tests * Move registration of jobProperties bean into the JsrNamespaceUtils --- .../batch/core/jsr/JobContext.java | 13 +- .../core/jsr/StepContextFactoryBean.java | 4 +- .../configuration/support/BatchArtifact.java | 116 ++++++ .../BatchPropertyBeanPostProcessor.java | 142 +++----- .../support/BatchPropertyContext.java | 336 ++++++++++++------ ...eterResolvingBeanFactoryPostProcessor.java | 136 +++++++ .../support/JsrExpressionParser.java | 58 +-- ...readLocalClassloaderBeanPostProcessor.java | 76 ---- .../jsr/configuration/xml/BatchletParser.java | 5 +- .../jsr/configuration/xml/ChunkParser.java | 19 +- .../jsr/configuration/xml/DecisionParser.java | 11 +- .../core/jsr/configuration/xml/JobParser.java | 14 +- .../configuration/xml/JsrNamespaceUtils.java | 23 +- .../jsr/configuration/xml/ListnerParser.java | 16 +- .../jsr/configuration/xml/PropertyParser.java | 87 ++--- .../jsr/configuration/xml/StepParser.java | 13 +- .../batch/core/jsr/launch/JsrJobOperator.java | 10 +- .../support/BatchPropertyContextTests.java | 199 +++++------ .../xml/JobPropertyTests-context.xml | 4 +- 19 files changed, 749 insertions(+), 533 deletions(-) create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchArtifact.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/JobParameterResolvingBeanFactoryPostProcessor.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JobContext.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JobContext.java index e1e016a60..dcd63eb67 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JobContext.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/JobContext.java @@ -33,19 +33,18 @@ import org.springframework.util.Assert; * @since 3.0 */ public class JobContext implements javax.batch.runtime.context.JobContext { - private Object transientUserData; - private Properties properties; - private JobExecution jobExecution; + private Properties properties; + private JobExecution jobExecution; - /** + /** * @param jobExecution for the related job */ public JobContext(JobExecution jobExecution, Properties properties) { Assert.notNull(jobExecution, "A JobExecution is required"); - this.properties = properties; - this.jobExecution = jobExecution; + this.jobExecution = jobExecution; + this.properties = properties != null ? properties : new Properties(); } /* (non-Javadoc) @@ -93,7 +92,7 @@ public class JobContext implements javax.batch.runtime.context.JobContext { */ @Override public Properties getProperties() { - return properties != null ? properties : new Properties(); + return properties; } /* (non-Javadoc) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepContextFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepContextFactoryBean.java index a91ba7e0f..4594fe496 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepContextFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/StepContextFactoryBean.java @@ -41,9 +41,7 @@ public class StepContextFactoryBean implements FactoryBean { public StepContext getObject() throws Exception { org.springframework.batch.core.scope.context.StepContext stepContext = StepSynchronizationManager.getContext(); - String stepName = stepContext.getStepName(); - String jobName = stepContext.getStepExecution().getJobExecution().getJobInstance().getJobName(); - Properties properties = batchPropertyContext.getStepLevelProperties(jobName + "." + stepName); + Properties properties = batchPropertyContext.getStepProperties(stepContext.getStepName()); return new StepContext(stepContext.getStepExecution(), properties); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchArtifact.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchArtifact.java new file mode 100644 index 000000000..18a26f954 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchArtifact.java @@ -0,0 +1,116 @@ +/* + * 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 javax.batch.api.Batchlet; +import javax.batch.api.Decider; +import javax.batch.api.chunk.CheckpointAlgorithm; +import javax.batch.api.chunk.ItemProcessor; +import javax.batch.api.chunk.ItemReader; +import javax.batch.api.chunk.ItemWriter; +import javax.batch.api.chunk.listener.ChunkListener; +import javax.batch.api.chunk.listener.ItemProcessListener; +import javax.batch.api.chunk.listener.ItemReadListener; +import javax.batch.api.chunk.listener.ItemWriteListener; +import javax.batch.api.chunk.listener.RetryProcessListener; +import javax.batch.api.chunk.listener.RetryReadListener; +import javax.batch.api.chunk.listener.RetryWriteListener; +import javax.batch.api.chunk.listener.SkipProcessListener; +import javax.batch.api.chunk.listener.SkipReadListener; +import javax.batch.api.chunk.listener.SkipWriteListener; +import javax.batch.api.listener.JobListener; +import javax.batch.api.listener.StepListener; +import javax.batch.api.partition.PartitionAnalyzer; +import javax.batch.api.partition.PartitionCollector; +import javax.batch.api.partition.PartitionMapper; +import javax.batch.api.partition.PartitionPlan; +import javax.batch.api.partition.PartitionReducer; + +/** + *

+ * Simple enum representing metadata about batch artifacts and types. + *

+ * + * @author Chris Schaefer + */ +public enum BatchArtifact { + ITEM_READER(ItemReader.class), + ITEM_WRITER(ItemWriter.class), + ITEM_PROCESSOR(ItemProcessor.class), + CHECKPOINT_ALGORITHM(CheckpointAlgorithm.class), + BATCHLET(Batchlet.class), + ITEM_READ_LISTENER(ItemReadListener.class), + ITEM_PROCESS_LISTENER(ItemProcessListener.class), + ITEM_WRITE_LISTENER(ItemWriteListener.class), + JOB_LISTENER(JobListener.class), + STEP_LISTENER(StepListener.class), + CHUNK_LISTENER(ChunkListener.class), + SKIP_READ_LISTENER(SkipReadListener.class), + SKIP_PROCESS_LISTENER(SkipProcessListener.class), + SKIP_WRITER_LISTENER(SkipWriteListener.class), + RETRY_READ_LISTENER(RetryReadListener.class), + RETRY_PROCESS_LISTENER(RetryProcessListener.class), + RETRY_WRITE_LISTENER(RetryWriteListener.class), + PARTITION_MAPPER(PartitionMapper.class), + PARTITION_REDUCER(PartitionReducer.class), + PARTITION_COLLECTOR(PartitionCollector.class), + PARTITION_ANALYZER(PartitionAnalyzer.class), + PARTITION_PLAN(PartitionPlan.class), + DECIDER(Decider.class); + + private Class clazz; + + private BatchArtifact(Class clazz) { + this.clazz = clazz; + } + + private Class getBatchArtifactClass() { + return clazz; + } + + /** + *

+ * Determines if the provided artifact is a JSR-352 batch artifact. + *

+ * + * @param artifact the artifact to check + * @return boolean answer based on check + */ + public static boolean isBatchArtifact(Object artifact) { + for (BatchArtifact batchArtifactType : BatchArtifact.values()) { + if (batchArtifactType.getBatchArtifactClass().isInstance(artifact)) { + return true; + } + } + + return false; + } + + /** + *

+ * Enum to identify batch artifact types. + *

+ * + * @author Chris Schaefer + * @since 3.0 + */ + public enum BatchArtifactType { + STEP, + STEP_ARTIFACT, + ARTIFACT, + JOB + } +} 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 d986d61c1..1eda5a89f 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 @@ -23,29 +23,6 @@ import java.util.Properties; import java.util.Set; import javax.batch.api.BatchProperty; -import javax.batch.api.Batchlet; -import javax.batch.api.Decider; -import javax.batch.api.chunk.CheckpointAlgorithm; -import javax.batch.api.chunk.ItemProcessor; -import javax.batch.api.chunk.ItemReader; -import javax.batch.api.chunk.ItemWriter; -import javax.batch.api.chunk.listener.ChunkListener; -import javax.batch.api.chunk.listener.ItemProcessListener; -import javax.batch.api.chunk.listener.ItemReadListener; -import javax.batch.api.chunk.listener.ItemWriteListener; -import javax.batch.api.chunk.listener.RetryProcessListener; -import javax.batch.api.chunk.listener.RetryReadListener; -import javax.batch.api.chunk.listener.RetryWriteListener; -import javax.batch.api.chunk.listener.SkipProcessListener; -import javax.batch.api.chunk.listener.SkipReadListener; -import javax.batch.api.chunk.listener.SkipWriteListener; -import javax.batch.api.listener.JobListener; -import javax.batch.api.listener.StepListener; -import javax.batch.api.partition.PartitionAnalyzer; -import javax.batch.api.partition.PartitionCollector; -import javax.batch.api.partition.PartitionMapper; -import javax.batch.api.partition.PartitionPlan; -import javax.batch.api.partition.PartitionReducer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -73,87 +50,61 @@ import org.springframework.util.ReflectionUtils; * @since 3.0 */ public class BatchPropertyBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { + private static final String SCOPED_TARGET_BEAN_PREFIX = "scopedTarget."; + private static final Log LOGGER = LogFactory.getLog(BatchPropertyBeanPostProcessor.class); + private static final Set> REQUIRED_ANNOTATIONS = new HashSet>(); + private JsrExpressionParser jsrExpressionParser; private BatchPropertyContext batchPropertyContext; - private Log logger = LogFactory.getLog(getClass()); - private Set> requiredAnnotations = new HashSet>(); - public BatchPropertyBeanPostProcessor() { - setRequiredAnnotations(); - } - - @Override - public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException { - if (!isBatchArtifact(bean)) { - return bean; - } - - String beanPropertyName = getBeanPropertyName(beanName); - - final Properties artifactProperties = batchPropertyContext.getBatchProperties(beanPropertyName); - - if (artifactProperties.isEmpty()) { - return bean; - } - - injectBatchProperties(bean, artifactProperties); - - return bean; - } - - private String getBeanPropertyName(String beanName) { - StepContext stepContext = StepSynchronizationManager.getContext(); - - if(stepContext != null) { - String stepName = stepContext.getStepName(); - String jobName = stepContext.getStepExecution().getJobExecution().getJobInstance().getJobName(); - return jobName + "." + stepName + "." + beanName.substring("scopedTarget.".length()); - } - - return beanName; - } - - @SuppressWarnings("unchecked") - private void setRequiredAnnotations() { + static { ClassLoader cl = BatchPropertyBeanPostProcessor.class.getClassLoader(); try { - requiredAnnotations.add((Class) cl.loadClass("javax.inject.Inject")); + REQUIRED_ANNOTATIONS.add((Class) cl.loadClass("javax.inject.Inject")); } catch (ClassNotFoundException ex) { - logger.warn("javax.inject.Inject not found - @BatchProperty marked fields will not be processed."); + LOGGER.warn("javax.inject.Inject not found - @BatchProperty marked fields will not be processed."); } - requiredAnnotations.add(BatchProperty.class); + REQUIRED_ANNOTATIONS.add(BatchProperty.class); } - private boolean isBatchArtifact(Object bean) { - return (bean instanceof ItemReader) || - (bean instanceof ItemProcessor) || - (bean instanceof ItemWriter) || - (bean instanceof CheckpointAlgorithm) || - (bean instanceof Batchlet) || - (bean instanceof ItemReadListener) || - (bean instanceof ItemProcessListener) || - (bean instanceof ItemWriteListener) || - (bean instanceof JobListener) || - (bean instanceof StepListener) || - (bean instanceof ChunkListener) || - (bean instanceof SkipReadListener) || - (bean instanceof SkipProcessListener) || - (bean instanceof SkipWriteListener) || - (bean instanceof RetryReadListener) || - (bean instanceof RetryProcessListener) || - (bean instanceof RetryWriteListener) || - (bean instanceof PartitionMapper) || - (bean instanceof PartitionReducer) || - (bean instanceof PartitionCollector) || - (bean instanceof PartitionAnalyzer) || - (bean instanceof PartitionPlan) || - (bean instanceof Decider); + @Override + public Object postProcessBeforeInitialization(final Object artifact, String artifactName) throws BeansException { + if (! BatchArtifact.isBatchArtifact(artifact)) { + return artifact; + } + + Properties artifactProperties = getArtifactProperties(artifactName); + + if (artifactProperties.isEmpty()) { + return artifact; + } + + injectBatchProperties(artifact, artifactProperties); + + return artifact; } - private void injectBatchProperties(final Object bean, final Properties artifactProperties) { - ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() { + @Override + public Object postProcessAfterInitialization(Object artifact, String artifactName) throws BeansException { + return artifact; + } + + private Properties getArtifactProperties(String artifactName) { + StepContext stepContext = StepSynchronizationManager.getContext(); + + if (stepContext != null) { + String originalArtifactName = artifactName.substring(SCOPED_TARGET_BEAN_PREFIX.length()); + + return batchPropertyContext.getStepArtifactProperties(stepContext.getStepName(), originalArtifactName); + } + + return batchPropertyContext.getArtifactProperties(artifactName); + } + + private void injectBatchProperties(final Object artifact, final Properties artifactProperties) { + ReflectionUtils.doWithFields(artifact.getClass(), new ReflectionUtils.FieldCallback() { @Override public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { if (isValidFieldModifier(field) && isAnnotated(field)) { @@ -163,7 +114,7 @@ public class BatchPropertyBeanPostProcessor implements BeanPostProcessor, BeanFa String batchProperty = getBatchPropertyFieldValue(field, artifactProperties); if (batchProperty != null) { - field.set(bean, batchProperty); + field.set(artifact, batchProperty); } field.setAccessible(isAccessible); @@ -193,7 +144,7 @@ public class BatchPropertyBeanPostProcessor implements BeanPostProcessor, BeanFa } private boolean isAnnotated(Field field) { - for (Class annotation : requiredAnnotations) { + for (Class annotation : REQUIRED_ANNOTATIONS) { if (!field.isAnnotationPresent(annotation)) { return false; } @@ -206,11 +157,6 @@ public class BatchPropertyBeanPostProcessor implements BeanPostProcessor, BeanFa return !Modifier.isStatic(field.getModifiers()) && !Modifier.isFinal(field.getModifiers()); } - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - return bean; - } - @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { if (!(beanFactory instanceof ConfigurableListableBeanFactory)) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyContext.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyContext.java index 5c35a6e5d..24d50d357 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyContext.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyContext.java @@ -15,16 +15,17 @@ */ package org.springframework.batch.core.jsr.configuration.support; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Properties; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Pattern; +import org.springframework.util.Assert; /** *

- * Simple context object to hold parsed JSR-352 batch properties, mapping properties - * to beans / "batch artifacts". Used internally when parsing property tags from a batch - * configuration file and to obtain corresponding values when injecting into batch artifacts. + * Context object to hold parsed JSR-352 batch properties, mapping properties to beans / + * "batch artifacts". Used internally when parsing property tags from a batch configuration + * file and to obtain corresponding values when injecting into batch artifacts. *

* * @author Chris Schaefer @@ -32,139 +33,211 @@ import java.util.regex.Pattern; * @since 3.0 */ public class BatchPropertyContext { - private static final String JOB_ARTIFACT_PROPERTY_PREFIX = "job-"; - private static final Pattern JOB_PATH_DELIMITER_PATTERN = Pattern.compile("\\."); - - private ConcurrentHashMap batchProperties = new ConcurrentHashMap(); + private Properties jobProperties = new Properties(); + private Map stepProperties = new HashMap(); + private Map artifactProperties = new HashMap(); + private Map> stepArtifactProperties = new HashMap>(); /** *

- * Adds each of the provided {@link BatchPropertyContext} objects to the existing property - * context. + * Obtains the Job level properties. + *

+ * + * @return the Job level properties + */ + public Properties getJobProperties() { + return jobProperties; + } + + /** + *

+ * Obtains the Step level properties for the provided Step name. + *

+ * + * @param stepName the Step name to obtain properties for + * @return the {@link Properties} for the Step + */ + public Properties getStepProperties(String stepName) { + Properties properties = stepProperties.get(stepName); + + return properties != null ? properties : new Properties(); + } + + /** + *

+ * Obtains the batch {@link Properties} for the provided artifact name. The returned {@link Properties} + * will also contain any job level properties that have been set. Job level properties will not override + * existing lower level artifact properties. + *

+ * + * @param artifactName the batch artifact to obtain properties for + * @return the {@link Properties} for the provided batch artifact + */ + public Properties getArtifactProperties(String artifactName) { + Properties properties = new Properties(); + properties.putAll(getJobProperties()); + + if (artifactProperties.containsKey(artifactName)) { + properties.putAll(artifactProperties.get(artifactName)); + } + + return properties; + } + + /** + *

+ * Obtains the batch {@link Properties} for the provided Step and artifact name. + *

+ * + * @param stepName the Step name the artifact is associated with + * @param artifactName the artifact name to obtain {@link Properties} for + * @return the {@link Properties} for the provided Step artifact + */ + public Properties getStepArtifactProperties(String stepName, String artifactName) { + Properties properties = new Properties(); + properties.putAll(getStepProperties(stepName)); + + Map artifactProperties = stepArtifactProperties.get(stepName); + + if (artifactProperties != null && artifactProperties.containsKey(artifactName)) { + properties.putAll(artifactProperties.get(artifactName)); + } + + return properties; + } + + /** + *

+ * Adds Job level properties to the context. *

* * @param batchPropertyContextEntries the {@link BatchPropertyContextEntry} objects to add */ - public void setBatchContextEntries(List batchPropertyContextEntries) { - for (BatchPropertyContextEntry batchPropertyContextEntry : batchPropertyContextEntries) { - setBatchContextEntry(batchPropertyContextEntry); - } - } + public void setJobPropertiesContextEntry(List batchPropertyContextEntries) { + for(BatchPropertyContextEntry batchPropertyContextEntry : batchPropertyContextEntries) { + Properties jobProperties = batchPropertyContextEntry.getProperties(); - private void setBatchContextEntry(BatchPropertyContextEntry batchPropertyContextEntry) { - String beanName = batchPropertyContextEntry.getBeanName(); - Properties properties = batchPropertyContextEntry.getProperties(); - - if (batchProperties.containsKey(beanName)) { - Properties existingProperties = batchProperties.get(beanName); - existingProperties.putAll(properties); - - batchProperties.put(beanName, existingProperties); - } else { - batchProperties.put(beanName, properties); - } - } - - /** - *

- * Obtains all properties for the specific batch artifact / bean name without any job level - * properties. - *

- * - * @param beanName the batch artifact / bean name to obtain {@link Properties} for - * @return the step level {@link Properties} - */ - public Properties getStepLevelProperties(String beanName) { - Properties properties = new Properties(); - - if (batchProperties.containsKey(beanName)) { - properties.putAll(batchProperties.get(beanName)); - } - - return properties; - } - - /** - *

- * Obtains the batch {@link Properties} for the provided bean name / batch artifact. The returned - * {@link Properties} will also contain any job level properties that have been set. Job level - * properties will not override existing lower level artifact properties. - *

- * - * @param beanName the bean name representing the batch artifact to obtain properties for - * @return the {@link Properties} for the provided batch artifact - */ - public Properties getBatchProperties(String beanName) { - Properties properties = new Properties(); - - if (batchProperties.containsKey(beanName)) { - properties.putAll(batchProperties.get(beanName)); - } - - Properties jobLevelProperties = getJobProperties(); - - for (String jobLevelProperty : jobLevelProperties.stringPropertyNames()) { - if (!properties.containsKey(jobLevelProperty)) { - properties.put(jobLevelProperty, jobLevelProperties.getProperty(jobLevelProperty)); + if (jobProperties != null && !jobProperties.isEmpty()) { + this.jobProperties.putAll(jobProperties); } } - - return properties; } /** *

- * Obtains all {@link Properties} that reside at the Job level configuration. + * Adds Step level properties to the context. *

* - * @return the job level {@link Properties} + * @param batchPropertyContextEntries the {@link BatchPropertyContextEntry} objects to add */ - public Properties getJobProperties() { - Properties jobProperties = new Properties(); + public void setStepPropertiesContextEntry(List batchPropertyContextEntries) { + for (BatchPropertyContextEntry batchPropertyContextEntry : batchPropertyContextEntries) { + Assert.hasText(batchPropertyContextEntry.getArtifactName(), "Step name must be defined as the artifact name."); - for (String jobLevelProperty : batchProperties.keySet()) { - if(isJobLevelComponentPath(jobLevelProperty)) { - if (batchProperties.containsKey(jobLevelProperty)) { - jobProperties.putAll(batchProperties.get(jobLevelProperty)); - break; + String stepName = batchPropertyContextEntry.getArtifactName(); + Properties stepProperties = batchPropertyContextEntry.getProperties(); + + if (stepProperties != null && ! stepProperties.isEmpty()) { + if (this.stepProperties.containsKey(stepName)) { + Properties existingStepProperties = this.stepProperties.get(stepName); + existingStepProperties.putAll(stepProperties); + + this.stepProperties.put(stepName, existingStepProperties); + } else { + this.stepProperties.put(stepName, stepProperties); } } } - - return jobProperties; - } - - // for now we assume properties are using a path format, currently: jobName.componentName.artifactName - // componentName can be the step name, job name prefixed by job- etc - protected boolean isJobLevelComponentPath(String jobLevelProperty) { - if(jobLevelProperty == null || "".equals(jobLevelProperty)) { - return false; - } - - String[] path = JOB_PATH_DELIMITER_PATTERN.split(jobLevelProperty); - - if (path.length >= 2) { - String componentPath = path[1]; - - if (componentPath != null && componentPath.startsWith(JOB_ARTIFACT_PROPERTY_PREFIX)) { - return true; - } - } - - return false; } /** *

- * Simple object to encapsulate batch properties of a given bean / batch artifact. + * Adds non-Step scoped artifact properties to the context. + *

+ * + * @param batchPropertyContextEntries the {@link BatchPropertyContextEntry} objects to add + */ + public void setArtifactPropertiesContextEntry(List batchPropertyContextEntries) { + for (BatchPropertyContextEntry batchPropertyContextEntry : batchPropertyContextEntries) { + Assert.hasText(batchPropertyContextEntry.getArtifactName(), "Artifact name must be defined"); + + Properties properties = batchPropertyContextEntry.getProperties(); + String artifactName = batchPropertyContextEntry.getArtifactName(); + + if (properties != null && !properties.isEmpty()) { + artifactProperties.put(artifactName, properties); + } + } + } + + /** + *

+ * Adds Step scoped artifact properties to the context. + *

+ * + * @param batchPropertyContextEntries the {@link BatchPropertyContextEntry} objects to add + */ + public void setStepArtifactPropertiesContextEntry(List batchPropertyContextEntries) { + for (BatchPropertyContextEntry batchPropertyContextEntry : batchPropertyContextEntries) { + Assert.hasText(batchPropertyContextEntry.getStepName(), "Step name must be defined"); + Assert.hasText(batchPropertyContextEntry.getArtifactName(), "Artifact name must be defined"); + + String stepName = batchPropertyContextEntry.getStepName(); + final String artifactName = batchPropertyContextEntry.getArtifactName(); + + final Properties properties = batchPropertyContextEntry.getProperties(); + + if (!properties.isEmpty()) { + Map artifactProperties = stepArtifactProperties.get(stepName); + + if (artifactProperties == null) { + stepArtifactProperties.put(stepName, new HashMap() {{ + put(artifactName, properties); + }}); + } else { + artifactProperties.put(artifactName, properties); + } + } + } + } + + /** + *

+ * Obtains the property to be used when setting data for the provided {@link BatchArtifact.BatchArtifactType}. + *

+ * + * @param batchArtifactType the {@link BatchArtifact.BatchArtifactType} to lookup the property name for + * @return the property name for + * @throws IllegalStateException if an unhandled {@link BatchArtifact.BatchArtifactType} is encountered + */ + public String getPropertyName(BatchArtifact.BatchArtifactType batchArtifactType) { + switch (batchArtifactType) { + case STEP: + return "stepPropertiesContextEntry"; + case STEP_ARTIFACT: + return "stepArtifactPropertiesContextEntry"; + case ARTIFACT: + return "artifactPropertiesContextEntry"; + case JOB: + return "jobPropertiesContextEntry"; + default: + throw new IllegalStateException("Unhandled BatchArtifactType of: " + batchArtifactType); + } + } + + /** + *

+ * Simple object to encapsulate batch properties of a given batch artifact. *

* * @author Chris Schaefer * @since 3.0 */ public class BatchPropertyContextEntry { - private String beanName; + private String stepName; + private String artifactName; private Properties properties; + private BatchArtifact.BatchArtifactType batchArtifactType; /** *

@@ -172,23 +245,25 @@ public class BatchPropertyContext { * and its associated {@link Properties}. *

* - * @param beanName the bean name representing the batch artifact + * @param artifactName the name representing the batch artifact * @param properties the associated {@link Properties} + * @param batchArtifactType the associated {@link BatchArtifact.BatchArtifactType} */ - public BatchPropertyContextEntry(String beanName, Properties properties) { - this.beanName = beanName; - this.properties = properties; + public BatchPropertyContextEntry(String artifactName, Properties properties, BatchArtifact.BatchArtifactType batchArtifactType) { + this.artifactName = artifactName; + this.batchArtifactType = batchArtifactType; + this.properties = properties != null ? properties : new Properties(); } /** *

- * Obtains the bean name of the batch artifact this entry is associated with. + * Obtains the name of the batch artifact this entry is associated with. *

* - * @return the bean name of the batch artifact + * @return the name of the batch artifact */ - public String getBeanName() { - return beanName; + public String getArtifactName() { + return artifactName; } /** @@ -201,5 +276,38 @@ public class BatchPropertyContext { public Properties getProperties() { return properties; } + + /** + *

+ * Obtains the {@link BatchArtifact.BatchArtifactType} represented by this context entry. + *

+ * + * @return the {@link BatchArtifact.BatchArtifactType} + */ + public BatchArtifact.BatchArtifactType getBatchArtifactType() { + return batchArtifactType; + } + + /** + *

+ * Sets the Step name associated with this entry. + *

+ * + * @param stepName the Step name associated with this entry + */ + public void setStepName(String stepName) { + this.stepName = stepName; + } + + /** + *

+ * Obtains the Step name associated with this entry. + *

+ * + * @return the step name associated with this entry + */ + public String getStepName() { + return stepName; + } } } 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 new file mode 100644 index 000000000..ab6a24640 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/JobParameterResolvingBeanFactoryPostProcessor.java @@ -0,0 +1,136 @@ +/* + * 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 lowest precedence so that + * it is executed as late as possible in the chain of {@link BeanFactoryPostProcessor}s + */ + @Override + public int getOrder() { + return PriorityOrdered.LOWEST_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 = resolveJobProperties(value); + + if (!"".equals(resolvedString)) { + return jsrExpressionParser.parseExpression(resolvedString); + } + } + + return value; + } + + private String resolveJobProperties(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 9aab36f80..3dd184a13 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 @@ -24,7 +24,7 @@ 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. + * example conditional/elvis statements need to be transformed a bit to be valid SPeL expressions. *

* * @author Chris Schaefer @@ -38,7 +38,7 @@ public class JsrExpressionParser { 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 static final Pattern CONDITIONAL_EXPRESSION = Pattern.compile("(((\\bnull\\b)|(#\\{\\w))[^;]+)"); private BeanExpressionContext beanExpressionContext; private BeanExpressionResolver beanExpressionResolver; @@ -67,33 +67,37 @@ public class JsrExpressionParser { */ 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 parseConditionalExpressions(expression); } return (String) beanExpressionResolver.evaluate(expression, beanExpressionContext); } + + private String parseConditionalExpressions(String expression) { + 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); + } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/ThreadLocalClassloaderBeanPostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/ThreadLocalClassloaderBeanPostProcessor.java index ea399b0fc..cad2fe9a8 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/ThreadLocalClassloaderBeanPostProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/ThreadLocalClassloaderBeanPostProcessor.java @@ -15,14 +15,10 @@ */ 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.PropertyValue; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanDefinitionVisitor; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.RuntimeBeanReference; @@ -30,7 +26,6 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.core.PriorityOrdered; -import org.springframework.util.StringValueResolver; /** * After the {@link BeanFactory} is created, this post processor will evaluate to see @@ -38,26 +33,10 @@ import org.springframework.util.StringValueResolver; * to class names instead of bean names. If this is the case, a new {@link BeanDefinition} * is added with the name of the class as the bean name. * - * This post processor will also 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 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 Michael Minella - * @author Chris Schaefer * @since 3.0 */ public class ThreadLocalClassloaderBeanPostProcessor implements BeanFactoryPostProcessor, PriorityOrdered { - private JobParameterResolver jobParameterResolver; - private static final Pattern JOB_PARAMETERS_KEY_PATTERN = Pattern.compile("'([^']*?)'"); - private static final Pattern JOB_PARAMETERS_PATTERN = Pattern.compile("(#\\{jobParameters[^}]+\\})"); - - public ThreadLocalClassloaderBeanPostProcessor(Properties properties) { - this.jobParameterResolver = new JobParameterResolver(properties); - } - /* (non-Javadoc) * @see org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory(org.springframework.beans.factory.config.ConfigurableListableBeanFactory) */ @@ -81,8 +60,6 @@ public class ThreadLocalClassloaderBeanPostProcessor implements BeanFactoryPostP } } } - - jobParameterResolver.visitBeanDefinition(beanDefinition); } } @@ -94,57 +71,4 @@ public class ThreadLocalClassloaderBeanPostProcessor implements BeanFactoryPostP public int getOrder() { return PriorityOrdered.LOWEST_PRECEDENCE; } - - protected class JobParameterResolver { - private Properties properties; - private BeanDefinitionVisitor beanDefinitionVisitor; - - public JobParameterResolver(Properties properties) { - this.properties = properties; - 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 { - @Override - public String resolveStringValue(String value) { - if (value != null && ! "".equals(value)) { - String resolvedString = resolveJobProperties(value); - - if (!"".equals(resolvedString)) { - return resolvedString; - } - } - - return value; - } - - private String resolveJobProperties(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); - } - } - } - - jobParameterMatcher.appendTail(valueBuffer); - - return valueBuffer.toString(); - } - } - } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/BatchletParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/BatchletParser.java index ff32cb0dd..962c73b6a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/BatchletParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/BatchletParser.java @@ -15,6 +15,7 @@ */ package org.springframework.batch.core.jsr.configuration.xml; +import org.springframework.batch.core.jsr.configuration.support.BatchArtifact; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; @@ -36,7 +37,7 @@ import org.w3c.dom.Element; public class BatchletParser extends AbstractSingleBeanDefinitionParser { private static final String REF = "ref"; - public void parseBatchlet(Element batchletElement, AbstractBeanDefinition bd, ParserContext parserContext) { + public void parseBatchlet(Element batchletElement, AbstractBeanDefinition bd, ParserContext parserContext, String stepName) { bd.setBeanClass(StepFactoryBean.class); bd.setAttribute("isNamespaceStep", false); @@ -49,6 +50,6 @@ public class BatchletParser extends AbstractSingleBeanDefinitionParser { bd.setRole(BeanDefinition.ROLE_SUPPORT); bd.setSource(parserContext.extractSource(batchletElement)); - new PropertyParser(taskletRef, parserContext).parseProperties(batchletElement); + new PropertyParser(taskletRef, parserContext, BatchArtifact.BatchArtifactType.STEP_ARTIFACT, stepName).parseProperties(batchletElement); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ChunkParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ChunkParser.java index 4a4989ae2..4a2a2d797 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ChunkParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ChunkParser.java @@ -18,6 +18,7 @@ package org.springframework.batch.core.jsr.configuration.xml; import java.util.List; import org.springframework.batch.core.configuration.xml.ExceptionElementParser; +import org.springframework.batch.core.jsr.configuration.support.BatchArtifact; import org.springframework.batch.core.step.item.ChunkOrientedTasklet; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemReader; @@ -64,7 +65,7 @@ public class ChunkParser { private static final String ITEM_CHECKPOINT_POLICY = "item"; private static final String CHECKPOINT_POLICY_ATTRIBUTE = "checkpoint-policy"; - public void parse(Element element, AbstractBeanDefinition bd, ParserContext parserContext) { + public void parse(Element element, AbstractBeanDefinition bd, ParserContext parserContext, String stepName) { MutablePropertyValues propertyValues = bd.getPropertyValues(); bd.setBeanClass(StepFactoryBean.class); bd.setAttribute("isNamespaceStep", false); @@ -83,7 +84,7 @@ public class ChunkParser { parseSimpleAttribute(element, propertyValues, TIME_LIMIT_ATTRIBUTE, "timeout"); } else if(checkpointPolicy.equals(CUSTOM_CHECKPOINT_POLICY)) { - parseCustomCheckpointAlgorithm(element, parserContext, propertyValues); + parseCustomCheckpointAlgorithm(element, parserContext, propertyValues, stepName); } } else { String itemCount = element.getAttribute(ITEM_COUNT_ATTRIBUTE); @@ -103,7 +104,7 @@ public class ChunkParser { for (int i = 0; i < children.getLength(); i++) { Node nd = children.item(i); - parseChildElement(element, parserContext, propertyValues, nd); + parseChildElement(element, parserContext, propertyValues, nd, stepName); } } @@ -117,7 +118,7 @@ public class ChunkParser { @SuppressWarnings({"unchecked", "rawtypes"}) private void parseChildElement(Element element, ParserContext parserContext, - MutablePropertyValues propertyValues, Node nd) { + MutablePropertyValues propertyValues, Node nd, String stepName) { if (nd instanceof Element) { Element nestedElement = (Element) nd; String name = nestedElement.getLocalName(); @@ -128,19 +129,19 @@ public class ChunkParser { propertyValues.addPropertyValue("itemReader", new RuntimeBeanReference(artifactName)); } - new PropertyParser(artifactName, parserContext).parseProperties(nestedElement); + new PropertyParser(artifactName, parserContext, BatchArtifact.BatchArtifactType.STEP_ARTIFACT, stepName).parseProperties(nestedElement); } else if(name.equals(PROCESSOR_ELEMENT)) { if (StringUtils.hasText(artifactName)) { propertyValues.addPropertyValue("itemProcessor", new RuntimeBeanReference(artifactName)); } - new PropertyParser(artifactName, parserContext).parseProperties(nestedElement); + new PropertyParser(artifactName, parserContext, BatchArtifact.BatchArtifactType.STEP_ARTIFACT, stepName).parseProperties(nestedElement); } else if(name.equals(WRITER_ELEMENT)) { if (StringUtils.hasText(artifactName)) { propertyValues.addPropertyValue("itemWriter", new RuntimeBeanReference(artifactName)); } - new PropertyParser(artifactName, parserContext).parseProperties(nestedElement); + new PropertyParser(artifactName, parserContext, BatchArtifact.BatchArtifactType.STEP_ARTIFACT, stepName).parseProperties(nestedElement); } else if(name.equals(SKIPPABLE_EXCEPTION_CLASSES_ELEMENT)) { ManagedMap exceptionClasses = new ExceptionElementParser().parse(element, parserContext, SKIPPABLE_EXCEPTION_CLASSES_ELEMENT); if(exceptionClasses != null) { @@ -165,7 +166,7 @@ public class ChunkParser { } } - private void parseCustomCheckpointAlgorithm(Element element, ParserContext parserContext, MutablePropertyValues propertyValues) { + private void parseCustomCheckpointAlgorithm(Element element, ParserContext parserContext, MutablePropertyValues propertyValues, String stepName) { List elements = DomUtils.getChildElementsByTagName(element, CHECKPOINT_ALGORITHM_ELEMENT); if(elements.size() == 1) { @@ -176,7 +177,7 @@ public class ChunkParser { propertyValues.addPropertyValue("chunkCompletionPolicy", new RuntimeBeanReference(name)); } - new PropertyParser(name, parserContext).parseProperties(checkpointAlgorithmElement); + new PropertyParser(name, parserContext, BatchArtifact.BatchArtifactType.STEP_ARTIFACT, stepName).parseProperties(checkpointAlgorithmElement); } else if(elements.size() > 1){ parserContext.getReaderContext().error( "The element may not appear more than once in a single <" diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/DecisionParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/DecisionParser.java index 726a73ba8..9249e2c34 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/DecisionParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/DecisionParser.java @@ -19,6 +19,7 @@ import java.util.Collection; import org.springframework.batch.core.job.flow.JobExecutionDecider; import org.springframework.batch.core.job.flow.support.state.StepState; +import org.springframework.batch.core.jsr.configuration.support.BatchArtifact; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.parsing.BeanComponentDefinition; @@ -50,8 +51,6 @@ public class DecisionParser { String idAttribute = element.getAttribute(ID_ATTRIBUTE); - PropertyParser.pushPath(idAttribute); - parserContext.registerBeanComponent(new BeanComponentDefinition(factoryDefinition, idAttribute)); stateBuilder.addConstructorArgReference(idAttribute); @@ -63,12 +62,8 @@ public class DecisionParser { factoryDefinition.setAttribute("jobParserJobFactoryBeanRef", jobFactoryRef); } - new PropertyParser(refAttribute, parserContext).parseProperties(element); + new PropertyParser(refAttribute, parserContext, BatchArtifact.BatchArtifactType.STEP_ARTIFACT, idAttribute).parseProperties(element); - Collection nextElements = FlowParser.getNextElements(parserContext, stateBuilder.getBeanDefinition(), element); - - PropertyParser.popPath(); - - return nextElements; + return FlowParser.getNextElements(parserContext, stateBuilder.getBeanDefinition(), element); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JobParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JobParser.java index 8e76871f3..34e275ef6 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JobParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JobParser.java @@ -17,6 +17,7 @@ package org.springframework.batch.core.jsr.configuration.xml; import org.springframework.batch.core.configuration.xml.CoreNamespaceUtils; import org.springframework.batch.core.jsr.StepContextFactoryBean; +import org.springframework.batch.core.jsr.configuration.support.BatchArtifact; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; @@ -49,13 +50,6 @@ public class JobParser extends AbstractSingleBeanDefinitionParser { String jobName = element.getAttribute(ID_ATTRIBUTE); - if(PropertyParser.hasPath()) { - PropertyParser.clearPath(); - throw new IllegalArgumentException("Job parsing started for job name: " + jobName + " and property parser path already populated."); - } - - PropertyParser.pushPath(jobName); - builder.addConstructorArgValue(jobName); String restartableAttribute = element.getAttribute(RESTARTABLE_ATTRIBUTE); @@ -63,6 +57,8 @@ public class JobParser extends AbstractSingleBeanDefinitionParser { builder.addPropertyValue("restartable", restartableAttribute); } + new PropertyParser(jobName, parserContext, BatchArtifact.BatchArtifactType.JOB).parseProperties(element); + BeanDefinition flowDef = new FlowParser(jobName, jobName).parse(element, parserContext); builder.addPropertyValue("flow", flowDef); @@ -74,9 +70,5 @@ public class JobParser extends AbstractSingleBeanDefinitionParser { parserContext.getRegistry().registerBeanDefinition("stepContextFactory", stepContextBeanDefinition); new ListnerParser(JobListenerFactoryBean.class, "jobExecutionListeners").parseListeners(element, parserContext, builder); - - new PropertyParser(PropertyParser.JOB_ARTIFACT_PROPERTY_PREFIX + jobName, parserContext).parseProperties(element); - - PropertyParser.popPath(); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JsrNamespaceUtils.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JsrNamespaceUtils.java index c2eb546ce..7bcbc447f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JsrNamespaceUtils.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JsrNamespaceUtils.java @@ -15,8 +15,10 @@ */ package org.springframework.batch.core.jsr.configuration.xml; +import java.util.HashMap; import org.springframework.batch.core.jsr.configuration.support.BatchPropertyBeanPostProcessor; import org.springframework.batch.core.jsr.configuration.support.JsrAutowiredAnnotationBeanPostProcessor; +import org.springframework.batch.core.jsr.configuration.support.ThreadLocalClassloaderBeanPostProcessor; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; @@ -24,18 +26,22 @@ import org.springframework.beans.factory.xml.ParserContext; import org.springframework.context.annotation.AnnotationConfigUtils; /** - * Utility methods used in parsing of the JSR-352 batch namespace + * Utility methods used in parsing of the JSR-352 batch namespace and related helpers. * * @author Michael Minella * @author Chris Schaefer * @since 3.0 */ class JsrNamespaceUtils { + private static final String JOB_PROPERTIES_BEAN_NAME = "jobProperties"; private static final String BATCH_PROPERTY_POST_PROCESSOR_BEAN_NAME = "batchPropertyPostProcessor"; + private static final String THREAD_LOCAL_CLASS_LOADER_BEAN_POST_PROCESSOR_BEAN_NAME = "threadLocalClassloaderBeanPostProcessor"; static void autoregisterJsrBeansForNamespace(ParserContext parserContext) { + autoRegisterJobProperties(parserContext); autoRegisterBatchPostProcessor(parserContext); autoRegisterJsrAutowiredAnnotationBeanPostProcessor(parserContext); + autoRegisterThreadLocalClassloaderBeanPostProcessor(parserContext); } private static void autoRegisterBatchPostProcessor(ParserContext parserContext) { @@ -46,6 +52,10 @@ class JsrNamespaceUtils { registerPostProcessor(parserContext, JsrAutowiredAnnotationBeanPostProcessor.class, BeanDefinition.ROLE_INFRASTRUCTURE, AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME); } + private static void autoRegisterThreadLocalClassloaderBeanPostProcessor(ParserContext parserContext) { + registerPostProcessor(parserContext, ThreadLocalClassloaderBeanPostProcessor.class, BeanDefinition.ROLE_INFRASTRUCTURE, THREAD_LOCAL_CLASS_LOADER_BEAN_POST_PROCESSOR_BEAN_NAME); + } + private static void registerPostProcessor(ParserContext parserContext, Class clazz, int role, String beanName) { BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz); @@ -54,4 +64,15 @@ class JsrNamespaceUtils { parserContext.getRegistry().registerBeanDefinition(beanName, beanDefinition); } + + // Registers a bean by the name of {@link #JOB_PROPERTIES_BEAN_NAME} so job level properties can be obtained through + // for example a SPeL expression referencing #{jobProperties['key']} similar to systemProperties resolution. + private static void autoRegisterJobProperties(ParserContext parserContext) { + if (!parserContext.getRegistry().containsBeanDefinition(JOB_PROPERTIES_BEAN_NAME)) { + AbstractBeanDefinition jobPropertiesBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(HashMap.class).getBeanDefinition(); + jobPropertiesBeanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + + parserContext.getRegistry().registerBeanDefinition(JOB_PROPERTIES_BEAN_NAME, jobPropertiesBeanDefinition); + } + } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ListnerParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ListnerParser.java index c6b883389..c9bfc2527 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ListnerParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ListnerParser.java @@ -17,6 +17,7 @@ package org.springframework.batch.core.jsr.configuration.xml; import java.util.List; +import org.springframework.batch.core.jsr.configuration.support.BatchArtifact; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.parsing.CompositeComponentDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; @@ -48,8 +49,8 @@ public class ListnerParser { this.listenerType = listenerType; } - public void parseListeners(Element element, ParserContext parserContext, AbstractBeanDefinition bd) { - ManagedList listeners = parseListeners(element, parserContext); + public void parseListeners(Element element, ParserContext parserContext, AbstractBeanDefinition bd, String stepName) { + ManagedList listeners = parseListeners(element, parserContext, stepName); if(listeners.size() > 0) { bd.getPropertyValues().add(propertyKey, listeners); @@ -57,14 +58,14 @@ public class ListnerParser { } public void parseListeners(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { - ManagedList listeners = parseListeners(element, parserContext); + ManagedList listeners = parseListeners(element, parserContext, ""); if(listeners.size() > 0) { builder.addPropertyValue(propertyKey, listeners); } } - private ManagedList parseListeners(Element element, ParserContext parserContext) { + private ManagedList parseListeners(Element element, ParserContext parserContext, String stepName) { List listenersElements = DomUtils.getChildElementsByTagName(element, LISTENERS_ELEMENT); ManagedList listeners = new ManagedList(); @@ -84,7 +85,7 @@ public class ListnerParser { listeners.add(bd.getBeanDefinition()); - new PropertyParser(beanName, parserContext).parseProperties(listenerElement); + new PropertyParser(beanName, parserContext, getBatchArtifactType(stepName), stepName).parseProperties(listenerElement); } parserContext.popAndRegisterContainingComponent(); } @@ -95,4 +96,9 @@ public class ListnerParser { return listeners; } + + 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/configuration/xml/PropertyParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/PropertyParser.java index ddf503461..3fd51a818 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/PropertyParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/PropertyParser.java @@ -15,73 +15,54 @@ */ package org.springframework.batch.core.jsr.configuration.xml; -import java.util.Deque; import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; +import org.springframework.batch.core.jsr.configuration.support.BatchArtifact; import org.springframework.batch.core.jsr.configuration.support.BatchPropertyContext; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; /** *

- * Parser for the <properties /> element defined by JSR-352. Parsed job level properties are - * also registered as a Map similar to systemProperties. Job level properties can be obtained for - * example through SPeL by referencing #{jobProperties['key']}. + * Parser for the <properties /> element defined by JSR-352. *

* * @author Chris Schaefer * @since 3.0 */ public class PropertyParser { - static final String JOB_ARTIFACT_PROPERTY_PREFIX = "job-"; private static final String PROPERTY_ELEMENT = "property"; private static final String PROPERTIES_ELEMENT = "properties"; private static final String PROPERTY_NAME_ATTRIBUTE = "name"; private static final String PROPERTY_VALUE_ATTRIBUTE = "value"; private static final String JOB_PROPERTIES_BEAN_NAME = "jobProperties"; - private static final String BATCH_CONTEXT_ENTRIES_PROPERTY_NAME = "batchContextEntries"; private static final String BATCH_PROPERTY_CONTEXT_BEAN_CLASS_NAME = "org.springframework.batch.core.jsr.configuration.support.BatchPropertyContext"; private static final String BATCH_PROPERTY_CONTEXT_BEAN_NAME = "batchPropertyContext"; - private static final Deque PATH = new LinkedList(); private String beanName; + private String stepName; private ParserContext parserContext; + private BatchArtifact.BatchArtifactType batchArtifactType; - public PropertyParser(String beanName, ParserContext parserContext) { + public PropertyParser(String beanName, ParserContext parserContext, BatchArtifact.BatchArtifactType batchArtifactType) { this.beanName = beanName; this.parserContext = parserContext; + this.batchArtifactType = batchArtifactType; registerBatchPropertyContext(); - registerJobProperties(); } - public PropertyParser(ParserContext parserContext) { - this("", parserContext); - } - - public static void pushPath(String pathElement) { - PATH.push(pathElement); - } - - public static void popPath() { - PATH.pop(); - } - - public static boolean hasPath() { - return !PATH.isEmpty(); - } - - public static void clearPath() { - PATH.clear(); + public PropertyParser(String beanName, ParserContext parserContext, BatchArtifact.BatchArtifactType batchArtifactType, String stepName) { + this(beanName, parserContext, batchArtifactType); + this.stepName = stepName; } /** @@ -118,13 +99,17 @@ public class PropertyParser { BatchPropertyContext batchPropertyContext = new BatchPropertyContext(); BatchPropertyContext.BatchPropertyContextEntry batchPropertyContextEntry = - batchPropertyContext.new BatchPropertyContextEntry(getContextEntryKey(), properties); + batchPropertyContext.new BatchPropertyContextEntry(beanName, properties, batchArtifactType); + + if (StringUtils.hasText(stepName)) { + batchPropertyContextEntry.setStepName(stepName); + } ManagedList managedList = new ManagedList(); managedList.setMergeEnabled(true); managedList.add(batchPropertyContextEntry); - beanDefinition.getPropertyValues().addPropertyValue(BATCH_CONTEXT_ENTRIES_PROPERTY_NAME, managedList); + beanDefinition.getPropertyValues().addPropertyValue(batchPropertyContext.getPropertyName(batchArtifactType), managedList); } private void registerBatchPropertyContext() { @@ -139,20 +124,25 @@ public class PropertyParser { } } - private void registerJobProperties() { - if (!parserContext.getRegistry().containsBeanDefinition(JOB_PROPERTIES_BEAN_NAME)) { - AbstractBeanDefinition jobPropertiesBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(HashMap.class).getBeanDefinition(); - jobPropertiesBeanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + private void setJobProperties(Properties properties) { + if (batchArtifactType.equals(BatchArtifact.BatchArtifactType.JOB)) { + BeanDefinition beanDefinition = parserContext.getRegistry().getBeanDefinition(BATCH_PROPERTY_CONTEXT_BEAN_NAME); - parserContext.getRegistry().registerBeanDefinition(JOB_PROPERTIES_BEAN_NAME, jobPropertiesBeanDefinition); + BatchPropertyContext batchPropertyContext = new BatchPropertyContext(); + BatchPropertyContext.BatchPropertyContextEntry batchPropertyContextEntry = + batchPropertyContext.new BatchPropertyContextEntry(beanName, properties, batchArtifactType); + + beanDefinition.getPropertyValues().addPropertyValue(batchPropertyContext.getPropertyName(batchArtifactType), batchPropertyContextEntry); + + registerJobProperties(properties); } } - private void setJobProperties(Properties properties) { - if (beanName.startsWith(JOB_ARTIFACT_PROPERTY_PREFIX)) { + private void registerJobProperties(Properties properties) { + if (batchArtifactType.equals(BatchArtifact.BatchArtifactType.JOB)) { Map jobProperties = new HashMap(); - if (properties != null) { + if (properties != null && ! properties.isEmpty()) { for (String param : properties.stringPropertyNames()) { jobProperties.put(param, properties.getProperty(param)); } @@ -162,23 +152,4 @@ public class PropertyParser { jobPropertiesBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(jobProperties); } } - - private String getPath() { - StringBuilder pathBuilder = new StringBuilder(); - Iterator pathIterator = PATH.descendingIterator(); - - if (pathIterator.hasNext()) { - pathBuilder.append(pathIterator.next()); - - while (pathIterator.hasNext()) { - pathBuilder.append(".").append(pathIterator.next()); - } - } - - return pathBuilder.toString(); - } - - private String getContextEntryKey() { - return "".equals(beanName) ? getPath() : getPath() + "." + beanName; - } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/StepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/StepParser.java index 80ed668d8..f578ce82f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/StepParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/StepParser.java @@ -18,6 +18,7 @@ package org.springframework.batch.core.jsr.configuration.xml; import java.util.Collection; import org.springframework.batch.core.job.flow.support.state.StepState; +import org.springframework.batch.core.jsr.configuration.support.BatchArtifact; import org.springframework.batch.core.listener.StepListenerFactoryBean; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition; @@ -55,8 +56,6 @@ public class StepParser extends AbstractSingleBeanDefinitionParser { String stepName = element.getAttribute(SPLIT_ID_ATTRIBUTE); builder.addPropertyValue("name", stepName); - PropertyParser.pushPath(stepName); - parserContext.registerBeanComponent(new BeanComponentDefinition(bd, stepName)); stateBuilder.addConstructorArgReference(stepName); @@ -71,8 +70,8 @@ public class StepParser extends AbstractSingleBeanDefinitionParser { allowStartIfComplete); } - new ListnerParser(StepListenerFactoryBean.class, "listeners").parseListeners(element, parserContext, bd); - new PropertyParser(parserContext).parseProperties(element); + new ListnerParser(StepListenerFactoryBean.class, "listeners").parseListeners(element, parserContext, bd, stepName); + new PropertyParser(stepName, parserContext, BatchArtifact.BatchArtifactType.STEP).parseProperties(element); // look at all nested elements NodeList children = element.getChildNodes(); @@ -85,17 +84,15 @@ public class StepParser extends AbstractSingleBeanDefinitionParser { String name = nestedElement.getLocalName(); if(name.equalsIgnoreCase(BATCHLET_ELEMENT)) { - new BatchletParser().parseBatchlet(nestedElement, bd, parserContext); + new BatchletParser().parseBatchlet(nestedElement, bd, parserContext, stepName); } else if(name.equals(CHUNK_ELEMENT)) { - new ChunkParser().parse(nestedElement, bd, parserContext); + new ChunkParser().parse(nestedElement, bd, parserContext, stepName); } } } Collection nextElements = FlowParser.getNextElements(parserContext, stepName, stateBuilder.getBeanDefinition(), element); - PropertyParser.popPath(); - return nextElements; } } 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 35d63671e..b549088f1 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 @@ -47,7 +47,7 @@ import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.jsr.JobContext; import org.springframework.batch.core.jsr.JsrJobParametersConverter; import org.springframework.batch.core.jsr.configuration.support.BatchPropertyContext; -import org.springframework.batch.core.jsr.configuration.support.ThreadLocalClassloaderBeanPostProcessor; +import org.springframework.batch.core.jsr.configuration.support.JobParameterResolvingBeanFactoryPostProcessor; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.access.BeanFactoryLocator; @@ -452,7 +452,7 @@ public class JsrJobOperator implements JobOperator, InitializingBean { batchContext.load(jobXml); } - batchContext.addBeanFactoryPostProcessor(new ThreadLocalClassloaderBeanPostProcessor(params)); + batchContext.addBeanFactoryPostProcessor(new JobParameterResolvingBeanFactoryPostProcessor(params)); batchContext.setParent(baseContext); batchContext.refresh(); @@ -476,7 +476,7 @@ public class JsrJobOperator implements JobOperator, InitializingBean { ConfigurableListableBeanFactory factory = batchContext.getBeanFactory(); BatchPropertyContext batchPropertyContext = factory.getBean(BATCH_PROPERTY_CONTEXT_BEAN_NAME, BatchPropertyContext.class); - Properties properties = batchPropertyContext.getBatchProperties("job-" + job.getName()); + Properties properties = batchPropertyContext.getJobProperties(); factory.registerSingleton(job.getName() + "_" + jobExecution.getId() + "_jobContext", new JobContext(jobExecution, properties)); @@ -553,7 +553,7 @@ public class JsrJobOperator implements JobOperator, InitializingBean { batchContext.load(jobXml); } - batchContext.addBeanFactoryPostProcessor(new ThreadLocalClassloaderBeanPostProcessor(params)); + batchContext.addBeanFactoryPostProcessor(new JobParameterResolvingBeanFactoryPostProcessor(params)); batchContext.setParent(baseContext); batchContext.refresh(); @@ -575,7 +575,7 @@ public class JsrJobOperator implements JobOperator, InitializingBean { ConfigurableListableBeanFactory factory = batchContext.getBeanFactory(); BatchPropertyContext batchPropertyContext = factory.getBean(BATCH_PROPERTY_CONTEXT_BEAN_NAME, BatchPropertyContext.class); - Properties properties = batchPropertyContext.getBatchProperties("job-" + job.getName()); + Properties properties = batchPropertyContext.getJobProperties(); factory.registerSingleton(job.getName() + "_" + jobExecution.getId() + "_jobContext", new JobContext(jobExecution, properties)); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyContextTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyContextTests.java index 85479e66e..741a68228 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyContextTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyContextTests.java @@ -16,13 +16,12 @@ package org.springframework.batch.core.jsr.configuration.support; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Properties; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; /** *

@@ -32,7 +31,10 @@ import static org.junit.Assert.assertTrue; * @author Chris Schaefer */ public class BatchPropertyContextTests { - private List entries = new ArrayList(); + private List jobProperties = new ArrayList(); + private List stepProperties = new ArrayList(); + private List artifactProperties = new ArrayList(); + private List stepArtifactProperties = new ArrayList(); @Before public void setUp() { @@ -41,83 +43,55 @@ public class BatchPropertyContextTests { Properties step1Properties = new Properties(); step1Properties.setProperty("step1PropertyName1", "step1PropertyValue1"); step1Properties.setProperty("step1PropertyName2", "step1PropertyValue2"); - entries.add(batchPropertyContext.new BatchPropertyContextEntry("job1.step1", step1Properties)); + stepProperties.add(batchPropertyContext.new BatchPropertyContextEntry("step1", step1Properties, BatchArtifact.BatchArtifactType.STEP)); Properties step2Properties = new Properties(); step2Properties.setProperty("step2PropertyName1", "step2PropertyValue1"); step2Properties.setProperty("step2PropertyName2", "step2PropertyValue2"); - entries.add(batchPropertyContext.new BatchPropertyContextEntry("job1.step2", step2Properties)); + stepProperties.add(batchPropertyContext.new BatchPropertyContextEntry("step2", step2Properties, BatchArtifact.BatchArtifactType.STEP)); Properties jobProperties = new Properties(); jobProperties.setProperty("jobProperty1", "jobProperty1value"); jobProperties.setProperty("jobProperty2", "jobProperty2value"); - entries.add(batchPropertyContext.new BatchPropertyContextEntry("job1.job-job1", jobProperties)); + this.jobProperties.add(batchPropertyContext.new BatchPropertyContextEntry("job1", jobProperties, BatchArtifact.BatchArtifactType.JOB)); + + Properties artifactProperties = new Properties(); + artifactProperties.setProperty("deciderProperty1", "deciderProperty1value"); + artifactProperties.setProperty("deciderProperty2", "deciderProperty2value"); + this.artifactProperties.add(batchPropertyContext.new BatchPropertyContextEntry("decider1", artifactProperties, BatchArtifact.BatchArtifactType.ARTIFACT)); + + Properties stepArtifactProperties = new Properties(); + stepArtifactProperties.setProperty("readerProperty1", "readerProperty1value"); + stepArtifactProperties.setProperty("readerProperty2", "readerProperty2value"); + + BatchPropertyContext.BatchPropertyContextEntry batchPropertyContextEntry = + batchPropertyContext.new BatchPropertyContextEntry("reader", stepArtifactProperties, BatchArtifact.BatchArtifactType.STEP_ARTIFACT); + batchPropertyContextEntry.setStepName("step1"); + + this.stepArtifactProperties.add(batchPropertyContextEntry); } @Test - public void testAddBatchContextEntries() { + public void testStepLevelProperties() { BatchPropertyContext batchPropertyContext = new BatchPropertyContext(); - batchPropertyContext.setBatchContextEntries(entries); + batchPropertyContext.setJobPropertiesContextEntry(jobProperties); + batchPropertyContext.setStepPropertiesContextEntry(stepProperties); - Properties step1BatchProperties = batchPropertyContext.getBatchProperties("job1.step1"); - assertEquals(4, step1BatchProperties.size()); - assertEquals("step1PropertyValue1", step1BatchProperties.getProperty("step1PropertyName1")); - assertEquals("step1PropertyValue2", step1BatchProperties.getProperty("step1PropertyName2")); - assertEquals("jobProperty1value", step1BatchProperties.getProperty("jobProperty1")); - assertEquals("jobProperty2value", step1BatchProperties.getProperty("jobProperty2")); + Properties step1Properties = batchPropertyContext.getStepProperties("step1"); + assertEquals(2, step1Properties.size()); + assertEquals("step1PropertyValue1", step1Properties.getProperty("step1PropertyName1")); + assertEquals("step1PropertyValue2", step1Properties.getProperty("step1PropertyName2")); - Properties step2BatchProperties = batchPropertyContext.getBatchProperties("job1.step2"); - assertEquals(4, step2BatchProperties.size()); - assertEquals("step2PropertyValue1", step2BatchProperties.getProperty("step2PropertyName1")); - assertEquals("step2PropertyValue2", step2BatchProperties.getProperty("step2PropertyName2")); - assertEquals("jobProperty1value", step2BatchProperties.getProperty("jobProperty1")); - assertEquals("jobProperty2value", step2BatchProperties.getProperty("jobProperty2")); - - Properties jobProperties = batchPropertyContext.getBatchProperties("job1.job-job1"); - assertEquals(2, jobProperties.size()); - assertEquals("jobProperty1value", jobProperties.getProperty("jobProperty1")); - assertEquals("jobProperty2value", jobProperties.getProperty("jobProperty2")); + Properties step2Properties = batchPropertyContext.getStepProperties("step2"); + assertEquals(2, step2Properties.size()); + assertEquals("step2PropertyValue1", step2Properties.getProperty("step2PropertyName1")); + assertEquals("step2PropertyValue2", step2Properties.getProperty("step2PropertyName2")); } @Test - public void testAddBatchContextEntriesToExistingArtifact() { + public void testJobLevelProperties() { BatchPropertyContext batchPropertyContext = new BatchPropertyContext(); - - Properties step1properties = new Properties(); - step1properties.setProperty("newStep1PropertyName", "newStep1PropertyValue"); - entries.add(batchPropertyContext.new BatchPropertyContextEntry("job1.step1", step1properties)); - - batchPropertyContext.setBatchContextEntries(entries); - - Properties bean2 = batchPropertyContext.getBatchProperties("job1.step1"); - assertEquals(5, bean2.size()); - assertEquals("step1PropertyValue1", bean2.getProperty("step1PropertyName1")); - assertEquals("step1PropertyValue2", bean2.getProperty("step1PropertyName2")); - assertEquals("newStep1PropertyValue", bean2.getProperty("newStep1PropertyName")); - assertEquals("jobProperty1value", bean2.getProperty("jobProperty1")); - assertEquals("jobProperty2value", bean2.getProperty("jobProperty2")); - } - - @Test - public void testGetStepLevelProperties() { - BatchPropertyContext batchPropertyContext = new BatchPropertyContext(); - batchPropertyContext.setBatchContextEntries(entries); - - Properties bean1 = batchPropertyContext.getStepLevelProperties("job1.step1"); - assertEquals(2, bean1.size()); - assertEquals("step1PropertyValue1", bean1.getProperty("step1PropertyName1")); - assertEquals("step1PropertyValue2", bean1.getProperty("step1PropertyName2")); - - Properties bean2 = batchPropertyContext.getStepLevelProperties("job1.step2"); - assertEquals(2, bean2.size()); - assertEquals("step2PropertyValue1", bean2.getProperty("step2PropertyName1")); - assertEquals("step2PropertyValue2", bean2.getProperty("step2PropertyName2")); - } - - @Test - public void testJobProperties() { - BatchPropertyContext batchPropertyContext = new BatchPropertyContext(); - batchPropertyContext.setBatchContextEntries(entries); + batchPropertyContext.setJobPropertiesContextEntry(jobProperties); Properties jobProperties = batchPropertyContext.getJobProperties(); assertEquals(2, jobProperties.size()); @@ -126,56 +100,83 @@ public class BatchPropertyContextTests { } @Test - public void testJobNonOverridingJobProperties() { + public void testAddPropertiesToExistingStep() { BatchPropertyContext batchPropertyContext = new BatchPropertyContext(); + batchPropertyContext.setJobPropertiesContextEntry(jobProperties); + batchPropertyContext.setStepPropertiesContextEntry(stepProperties); - Properties jobProperties = new Properties(); - jobProperties.setProperty("step1PropertyName1", "step1PropertyOverride"); - entries.add(batchPropertyContext.new BatchPropertyContextEntry("job1.job-job1", jobProperties)); + Properties step1 = batchPropertyContext.getStepProperties("step1"); + assertEquals(2, step1.size()); + assertEquals("step1PropertyValue1", step1.getProperty("step1PropertyName1")); + assertEquals("step1PropertyValue2", step1.getProperty("step1PropertyName2")); - batchPropertyContext.setBatchContextEntries(entries); + Properties step1properties = new Properties(); + step1properties.setProperty("newStep1PropertyName", "newStep1PropertyValue"); - Properties bean1 = batchPropertyContext.getBatchProperties("job1.step1"); - assertEquals(4, bean1.size()); - assertEquals("step1PropertyValue1", bean1.getProperty("step1PropertyName1")); - assertEquals("step1PropertyValue2", bean1.getProperty("step1PropertyName2")); - assertEquals("jobProperty1value", bean1.getProperty("jobProperty1")); - assertEquals("jobProperty2value", bean1.getProperty("jobProperty2")); + batchPropertyContext.setStepPropertiesContextEntry( + Collections.singletonList(batchPropertyContext.new BatchPropertyContextEntry("step1", step1properties, BatchArtifact.BatchArtifactType.STEP))); - Properties testJobBean = batchPropertyContext.getBatchProperties("job1.job-job1"); - assertEquals(3, testJobBean.size()); - assertEquals("step1PropertyOverride", testJobBean.getProperty("step1PropertyName1")); - assertEquals("jobProperty1value", testJobBean.getProperty("jobProperty1")); - assertEquals("jobProperty2value", testJobBean.getProperty("jobProperty2")); + Properties step1updated = batchPropertyContext.getStepProperties("step1"); + assertEquals(3, step1updated.size()); + assertEquals("step1PropertyValue1", step1updated.getProperty("step1PropertyName1")); + assertEquals("step1PropertyValue2", step1updated.getProperty("step1PropertyName2")); + assertEquals("newStep1PropertyValue", step1updated.getProperty("newStep1PropertyName")); } @Test - public void testJobLevelPropertiesWithPath() { - List entries = new ArrayList(); - + public void testNonStepLevelArtifactProperties() { BatchPropertyContext batchPropertyContext = new BatchPropertyContext(); + batchPropertyContext.setJobPropertiesContextEntry(jobProperties); + batchPropertyContext.setArtifactPropertiesContextEntry(artifactProperties); + batchPropertyContext.setStepPropertiesContextEntry(stepProperties); - Properties jobProperties = new Properties(); - jobProperties.setProperty("readerName", "testJobreaderName"); - - entries.add(batchPropertyContext.new BatchPropertyContextEntry("job1.job-job1.itemReader", jobProperties)); - - batchPropertyContext.setBatchContextEntries(entries); - - Properties props = batchPropertyContext.getJobProperties(); - assertEquals(1, props.size()); - assertEquals("testJobreaderName", props.getProperty("readerName")); + Properties artifactProperties = batchPropertyContext.getArtifactProperties("decider1"); + assertEquals(4, artifactProperties.size()); + assertEquals("deciderProperty1value", artifactProperties.getProperty("deciderProperty1")); + assertEquals("deciderProperty2value", artifactProperties.getProperty("deciderProperty2")); + assertEquals("jobProperty1value", artifactProperties.getProperty("jobProperty1")); + assertEquals("jobProperty2value", artifactProperties.getProperty("jobProperty2")); } @Test - public void testJobLevelComponentPath() { + public void testStepLevelArtifactProperties() { BatchPropertyContext batchPropertyContext = new BatchPropertyContext(); - assertTrue(batchPropertyContext.isJobLevelComponentPath("myJob.job-myJob")); - assertTrue(batchPropertyContext.isJobLevelComponentPath("myJob.job-myJob.myReader")); - assertTrue(batchPropertyContext.isJobLevelComponentPath("myJob.job-myJob.myReader.something")); - assertFalse(batchPropertyContext.isJobLevelComponentPath("myJob")); - assertFalse(batchPropertyContext.isJobLevelComponentPath("job-myJob")); - assertFalse(batchPropertyContext.isJobLevelComponentPath(null)); - assertFalse(batchPropertyContext.isJobLevelComponentPath("myJob.")); + batchPropertyContext.setJobPropertiesContextEntry(jobProperties); + batchPropertyContext.setArtifactPropertiesContextEntry(artifactProperties); + batchPropertyContext.setStepPropertiesContextEntry(stepProperties); + batchPropertyContext.setStepArtifactPropertiesContextEntry(stepArtifactProperties); + + Properties artifactProperties = batchPropertyContext.getStepArtifactProperties("step1", "reader"); + assertEquals(4, artifactProperties.size()); + assertEquals("readerProperty1value", artifactProperties.getProperty("readerProperty1")); + assertEquals("readerProperty2value", artifactProperties.getProperty("readerProperty2")); + assertEquals("step1PropertyValue1", artifactProperties.getProperty("step1PropertyName1")); + assertEquals("step1PropertyValue2", artifactProperties.getProperty("step1PropertyName2")); + } + + @Test + public void testArtifactNonOverridingJobProperties() { + BatchPropertyContext batchPropertyContext = new BatchPropertyContext(); + batchPropertyContext.setJobPropertiesContextEntry(jobProperties); + batchPropertyContext.setArtifactPropertiesContextEntry(artifactProperties); + + Properties jobProperties = new Properties(); + jobProperties.setProperty("deciderProperty1", "decider1PropertyOverride"); + + batchPropertyContext.setJobPropertiesContextEntry( + Collections.singletonList(batchPropertyContext.new BatchPropertyContextEntry("job1", jobProperties, BatchArtifact.BatchArtifactType.JOB))); + + Properties step1 = batchPropertyContext.getArtifactProperties("decider1"); + assertEquals(4, step1.size()); + assertEquals("deciderProperty1value", step1.getProperty("deciderProperty1")); + assertEquals("deciderProperty2value", step1.getProperty("deciderProperty2")); + assertEquals("jobProperty1value", step1.getProperty("jobProperty1")); + assertEquals("jobProperty2value", step1.getProperty("jobProperty2")); + + Properties job = batchPropertyContext.getJobProperties(); + assertEquals(3, job.size()); + assertEquals("decider1PropertyOverride", job.getProperty("deciderProperty1")); + assertEquals("jobProperty1value", job.getProperty("jobProperty1")); + assertEquals("jobProperty2value", job.getProperty("jobProperty2")); } } 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 index de1408ba9..277ffa6f0 100644 --- 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 @@ -69,7 +69,7 @@ - + @@ -132,7 +132,7 @@ - + true