1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.springframework.batch.core.configuration.xml;
17
18 import org.springframework.batch.core.listener.StepListenerMetaData;
19 import org.springframework.batch.core.step.item.ForceRollbackForWriteSkipException;
20 import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
21 import org.springframework.beans.MutablePropertyValues;
22 import org.springframework.beans.factory.config.BeanDefinition;
23 import org.springframework.beans.factory.config.BeanDefinitionHolder;
24 import org.springframework.beans.factory.config.RuntimeBeanReference;
25 import org.springframework.beans.factory.config.TypedStringValue;
26 import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
27 import org.springframework.beans.factory.support.AbstractBeanDefinition;
28 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
29 import org.springframework.beans.factory.support.GenericBeanDefinition;
30 import org.springframework.beans.factory.support.ManagedList;
31 import org.springframework.beans.factory.support.ManagedMap;
32 import org.springframework.beans.factory.xml.ParserContext;
33 import org.springframework.util.StringUtils;
34 import org.springframework.util.xml.DomUtils;
35 import org.w3c.dom.Element;
36
37 import java.util.List;
38
39
40
41
42
43
44
45 public class ChunkElementParser {
46
47 private static final String REF_ATTR = "ref";
48
49 private static final String MERGE_ATTR = "merge";
50
51 private static final String COMMIT_INTERVAL_ATTR = "commit-interval";
52
53 private static final String CHUNK_COMPLETION_POLICY_ATTR = "chunk-completion-policy";
54
55 private static final String BEAN_ELE = "bean";
56
57 private static final String REF_ELE = "ref";
58
59 private static final String ITEM_READER_ADAPTER_CLASS = "org.springframework.batch.item.adapter.ItemReaderAdapter";
60
61 private static final String ITEM_PROCESSOR_ADAPTER_CLASS = "org.springframework.batch.item.adapter.ItemProcessorAdapter";
62
63 private static final String ITEM_WRITER_ADAPTER_CLASS = "org.springframework.batch.item.adapter.ItemWriterAdapter";
64
65 private static final StepListenerParser stepListenerParser = new StepListenerParser(
66 StepListenerMetaData.itemListenerMetaData());
67
68
69
70
71
72 protected void parse(Element element, AbstractBeanDefinition bd, ParserContext parserContext, boolean underspecified) {
73
74 MutablePropertyValues propertyValues = bd.getPropertyValues();
75
76 propertyValues.addPropertyValue("hasChunkElement", Boolean.TRUE);
77
78 handleItemHandler(bd, "reader", "itemReader", ITEM_READER_ADAPTER_CLASS, true, element, parserContext,
79 propertyValues, underspecified);
80 handleItemHandler(bd, "processor", "itemProcessor", ITEM_PROCESSOR_ADAPTER_CLASS, false, element, parserContext,
81 propertyValues, underspecified);
82 handleItemHandler(bd, "writer", "itemWriter", ITEM_WRITER_ADAPTER_CLASS, true, element, parserContext,
83 propertyValues, underspecified);
84
85 String commitInterval = element.getAttribute(COMMIT_INTERVAL_ATTR);
86 if (StringUtils.hasText(commitInterval)) {
87 if (commitInterval.startsWith("#")) {
88
89 BeanDefinitionBuilder completionPolicy = BeanDefinitionBuilder
90 .genericBeanDefinition(SimpleCompletionPolicy.class);
91 completionPolicy.addConstructorArgValue(commitInterval);
92 completionPolicy.setScope("step");
93 propertyValues.addPropertyValue("chunkCompletionPolicy", completionPolicy.getBeanDefinition());
94 }
95 else {
96 propertyValues.addPropertyValue("commitInterval", commitInterval);
97 }
98 }
99
100 String completionPolicyRef = element.getAttribute(CHUNK_COMPLETION_POLICY_ATTR);
101 if (StringUtils.hasText(completionPolicyRef)) {
102 RuntimeBeanReference completionPolicy = new RuntimeBeanReference(completionPolicyRef);
103 propertyValues.addPropertyValue("chunkCompletionPolicy", completionPolicy);
104 }
105
106 if (!underspecified
107 && propertyValues.contains("commitInterval") == propertyValues.contains("chunkCompletionPolicy")) {
108 if (propertyValues.contains("commitInterval")) {
109 parserContext.getReaderContext().error(
110 "The <" + element.getNodeName() + "/> element must contain either '" + COMMIT_INTERVAL_ATTR
111 + "' " + "or '" + CHUNK_COMPLETION_POLICY_ATTR + "', but not both.", element);
112 }
113 else {
114 parserContext.getReaderContext().error(
115 "The <" + element.getNodeName() + "/> element must contain either '" + COMMIT_INTERVAL_ATTR
116 + "' " + "or '" + CHUNK_COMPLETION_POLICY_ATTR + "'.", element);
117
118 }
119 }
120
121 String skipLimit = element.getAttribute("skip-limit");
122 ManagedMap skippableExceptions = handleExceptionElement(element, parserContext, "skippable-exception-classes");
123 if (StringUtils.hasText(skipLimit)) {
124 if (skippableExceptions == null) {
125 skippableExceptions = new ManagedMap();
126 skippableExceptions.setMergeEnabled(true);
127 }
128 propertyValues.addPropertyValue("skipLimit", skipLimit);
129 }
130 if (skippableExceptions != null) {
131
132
133 propertyValues.addPropertyValue("skippableExceptionClasses", skippableExceptions);
134 }
135
136 handleItemHandler(bd, "skip-policy", "skipPolicy", null, false, element, parserContext, propertyValues,
137 underspecified);
138
139 String retryLimit = element.getAttribute("retry-limit");
140 ManagedMap retryableExceptions = handleExceptionElement(element, parserContext, "retryable-exception-classes");
141 if (StringUtils.hasText(retryLimit)) {
142 if (retryableExceptions == null) {
143 retryableExceptions = new ManagedMap();
144 retryableExceptions.setMergeEnabled(true);
145 }
146 propertyValues.addPropertyValue("retryLimit", retryLimit);
147 }
148 if (retryableExceptions != null) {
149
150
151 propertyValues.addPropertyValue("retryableExceptionClasses", retryableExceptions);
152 }
153
154 handleItemHandler(bd, "retry-policy", "retryPolicy", null, false, element, parserContext, propertyValues,
155 underspecified);
156
157 String cacheCapacity = element.getAttribute("cache-capacity");
158 if (StringUtils.hasText(cacheCapacity)) {
159 propertyValues.addPropertyValue("cacheCapacity", cacheCapacity);
160 }
161
162 String isReaderTransactionalQueue = element.getAttribute("reader-transactional-queue");
163 if (StringUtils.hasText(isReaderTransactionalQueue)) {
164 propertyValues.addPropertyValue("isReaderTransactionalQueue", isReaderTransactionalQueue);
165 }
166
167 String isProcessorTransactional = element.getAttribute("processor-transactional");
168 if (StringUtils.hasText(isProcessorTransactional)) {
169 propertyValues.addPropertyValue("processorTransactional", isProcessorTransactional);
170 }
171
172 handleRetryListenersElement(element, propertyValues, parserContext, bd);
173
174 handleStreamsElement(element, propertyValues, parserContext);
175
176 stepListenerParser.handleListenersElement(element, bd, parserContext);
177
178 }
179
180
181
182
183 private void handleItemHandler(AbstractBeanDefinition enclosing, String handlerName, String propertyName, String adapterClassName, boolean required,
184 Element element, ParserContext parserContext, MutablePropertyValues propertyValues, boolean underspecified) {
185 String refName = element.getAttribute(handlerName);
186 @SuppressWarnings("unchecked")
187 List<Element> children = DomUtils.getChildElementsByTagName(element, handlerName);
188 if (children.size() == 1) {
189 if (StringUtils.hasText(refName)) {
190 parserContext.getReaderContext().error(
191 "The <" + element.getNodeName() + "/> element may not have both a '" + handlerName
192 + "' attribute and a <" + handlerName + "/> element.", element);
193 }
194 handleItemHandlerElement(enclosing, propertyName, adapterClassName, propertyValues, children.get(0), parserContext);
195 }
196 else if (children.size() > 1) {
197 parserContext.getReaderContext().error(
198 "The <" + handlerName + "/> element may not appear more than once in a single <"
199 + element.getNodeName() + "/>.", element);
200 }
201 else if (StringUtils.hasText(refName)) {
202 propertyValues.addPropertyValue(propertyName, new RuntimeBeanReference(refName));
203 }
204 else if (required && !underspecified) {
205 parserContext.getReaderContext().error(
206 "The <" + element.getNodeName() + "/> element has neither a '" + handlerName
207 + "' attribute nor a <" + handlerName + "/> element.", element);
208 }
209 }
210
211
212
213
214
215 @SuppressWarnings("unchecked")
216 private void handleItemHandlerElement(AbstractBeanDefinition enclosing, String propertyName, String adapterClassName,
217 MutablePropertyValues propertyValues, Element element, ParserContext parserContext) {
218 List<Element> beanElements = DomUtils.getChildElementsByTagName(element, BEAN_ELE);
219 List<Element> refElements = DomUtils.getChildElementsByTagName(element, REF_ELE);
220 if (beanElements.size() + refElements.size() != 1) {
221 parserContext.getReaderContext().error(
222 "The <" + element.getNodeName() + "/> must have exactly one of either a <" + BEAN_ELE
223 + "/> element or a <" + REF_ELE + "/> element.", element);
224 }
225 else if (beanElements.size() == 1) {
226 Element beanElement = beanElements.get(0);
227 BeanDefinitionHolder beanDefinitionHolder = parserContext.getDelegate().parseBeanDefinitionElement(
228 beanElement, enclosing);
229 parserContext.getDelegate().decorateBeanDefinitionIfRequired(beanElement, beanDefinitionHolder);
230
231 propertyValues.addPropertyValue(propertyName, beanDefinitionHolder);
232 }
233 else if (refElements.size() == 1) {
234 propertyValues.addPropertyValue(propertyName,
235 parserContext.getDelegate().parsePropertySubElement(refElements.get(0), null));
236 }
237
238 handleAdapterMethodAttribute(propertyName, adapterClassName, propertyValues, element);
239 }
240
241
242
243
244
245 private void handleAdapterMethodAttribute(String propertyName, String adapterClassName,
246 MutablePropertyValues stepPvs, Element element) {
247 String adapterMethodName = element.getAttribute("adapter-method");
248 if (StringUtils.hasText(adapterMethodName)) {
249
250
251
252 AbstractBeanDefinition adapterDef = new GenericBeanDefinition();
253 adapterDef.setBeanClassName(adapterClassName);
254 MutablePropertyValues adapterPvs = adapterDef.getPropertyValues();
255 adapterPvs.addPropertyValue("targetMethod", adapterMethodName);
256
257 adapterPvs.addPropertyValue("targetObject", stepPvs.getPropertyValue(propertyName).getValue());
258
259
260
261
262 stepPvs.addPropertyValue(propertyName, adapterDef);
263 }
264 }
265
266 private void handleRetryListenersElement(Element element, MutablePropertyValues propertyValues,
267 ParserContext parserContext, BeanDefinition enclosing) {
268 Element listenersElement = DomUtils.getChildElementByTagName(element, "retry-listeners");
269 if (listenersElement != null) {
270 CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(listenersElement.getTagName(),
271 parserContext.extractSource(element));
272 parserContext.pushContainingComponent(compositeDef);
273 ManagedList retryListenerBeans = new ManagedList();
274 retryListenerBeans.setMergeEnabled(listenersElement.hasAttribute(MERGE_ATTR)
275 && Boolean.valueOf(listenersElement.getAttribute(MERGE_ATTR)));
276 handleRetryListenerElements(parserContext, listenersElement, retryListenerBeans, enclosing);
277 propertyValues.addPropertyValue("retryListeners", retryListenerBeans);
278 parserContext.popAndRegisterContainingComponent();
279 }
280 }
281
282 @SuppressWarnings("unchecked")
283 private void handleRetryListenerElements(ParserContext parserContext, Element element, ManagedList beans,
284 BeanDefinition enclosing) {
285 List<Element> listenerElements = DomUtils.getChildElementsByTagName(element, "listener");
286 if (listenerElements != null) {
287 for (Element listenerElement : listenerElements) {
288 beans.add(AbstractListenerParser.parseListenerElement(listenerElement, parserContext, enclosing));
289 }
290 }
291 }
292
293 @SuppressWarnings("unchecked")
294 private void handleStreamsElement(Element element, MutablePropertyValues propertyValues, ParserContext parserContext) {
295 Element streamsElement = DomUtils.getChildElementByTagName(element, "streams");
296 if (streamsElement != null) {
297 ManagedList streamBeans = new ManagedList();
298 streamBeans.setMergeEnabled(streamsElement.hasAttribute(MERGE_ATTR)
299 && Boolean.valueOf(streamsElement.getAttribute(MERGE_ATTR)));
300 List<Element> streamElements = DomUtils.getChildElementsByTagName(streamsElement, "stream");
301 if (streamElements != null) {
302 for (Element streamElement : streamElements) {
303 String streamRef = streamElement.getAttribute(REF_ATTR);
304 if (StringUtils.hasText(streamRef)) {
305 streamBeans.add(new RuntimeBeanReference(streamRef));
306 }
307 else {
308 parserContext.getReaderContext().error(
309 REF_ATTR + " not specified for <" + streamElement.getTagName() + "> element", element);
310 }
311 }
312 }
313 propertyValues.addPropertyValue("streams", streamBeans);
314 }
315 }
316
317 @SuppressWarnings("unchecked")
318 private ManagedMap handleExceptionElement(Element element, ParserContext parserContext, String exceptionListName) {
319 List<Element> children = DomUtils.getChildElementsByTagName(element, exceptionListName);
320 if (children.size() == 1) {
321 ManagedMap map = new ManagedMap();
322 Element exceptionClassesElement = children.get(0);
323 map.setMergeEnabled(exceptionClassesElement.hasAttribute(MERGE_ATTR)
324 && Boolean.valueOf(exceptionClassesElement.getAttribute(MERGE_ATTR)));
325 addExceptionClasses("include", true, exceptionClassesElement, map, parserContext);
326 addExceptionClasses("exclude", false, exceptionClassesElement, map, parserContext);
327 map.put(ForceRollbackForWriteSkipException.class, true);
328 return map;
329 }
330 else if (children.size() > 1) {
331 parserContext.getReaderContext().error(
332 "The <" + exceptionListName + "/> element may not appear more than once in a single <"
333 + element.getNodeName() + "/>.", element);
334 }
335 return null;
336 }
337
338 @SuppressWarnings("unchecked")
339 private void addExceptionClasses(String elementName, boolean include, Element exceptionClassesElement,
340 ManagedMap map, ParserContext parserContext) {
341 for (Element child : (List<Element>) DomUtils.getChildElementsByTagName(exceptionClassesElement, elementName)) {
342 String className = child.getAttribute("class");
343 map.put(new TypedStringValue(className, Class.class), include);
344 }
345 }
346
347 }