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 java.util.List;
19
20 import org.springframework.batch.core.step.tasklet.MethodInvokingTaskletAdapter;
21 import org.springframework.beans.BeanMetadataElement;
22 import org.springframework.beans.MutablePropertyValues;
23 import org.springframework.beans.factory.config.BeanDefinition;
24 import org.springframework.beans.factory.config.BeanDefinitionHolder;
25 import org.springframework.beans.factory.config.RuntimeBeanReference;
26 import org.springframework.beans.factory.config.TypedStringValue;
27 import org.springframework.beans.factory.support.AbstractBeanDefinition;
28 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
29 import org.springframework.beans.factory.support.ManagedList;
30 import org.springframework.beans.factory.xml.ParserContext;
31 import org.springframework.util.StringUtils;
32 import org.springframework.util.xml.DomUtils;
33 import org.w3c.dom.Element;
34
35
36
37
38
39
40
41
42
43 public class TaskletParser {
44
45
46
47
48 private static final String TRANSACTION_MANAGER_ATTR = "transaction-manager";
49
50 private static final String TASKLET_REF_ATTR = "ref";
51
52 private static final String TASKLET_METHOD_ATTR = "method";
53
54 private static final String BEAN_ELE = "bean";
55
56 private static final String REF_ELE = "ref";
57
58 private static final String TASK_EXECUTOR_ATTR = "task-executor";
59
60 private static final String CHUNK_ELE = "chunk";
61
62 private static final String TX_ATTRIBUTES_ELE = "transaction-attributes";
63
64 private static final String MERGE_ATTR = "merge";
65
66 private static final ChunkElementParser chunkElementParser = new ChunkElementParser();
67
68
69 private static final StepListenerParser stepListenerParser = new StepListenerParser();
70
71 public void parseTasklet(Element stepElement, Element taskletElement, AbstractBeanDefinition bd,
72 ParserContext parserContext, boolean stepUnderspecified) {
73
74 bd.setBeanClass(StepParserStepFactoryBean.class);
75 bd.setAttribute("isNamespaceStep", true);
76
77 String taskletRef = taskletElement.getAttribute(TASKLET_REF_ATTR);
78 String taskletMethod = taskletElement.getAttribute(TASKLET_METHOD_ATTR);
79 @SuppressWarnings("unchecked")
80 List<Element> chunkElements = DomUtils.getChildElementsByTagName(taskletElement, CHUNK_ELE);
81 @SuppressWarnings("unchecked")
82 List<Element> beanElements = DomUtils.getChildElementsByTagName(taskletElement, BEAN_ELE);
83 @SuppressWarnings("unchecked")
84 List<Element> refElements = DomUtils.getChildElementsByTagName(taskletElement, REF_ELE);
85
86 validateTaskletAttributesAndSubelements(taskletElement, parserContext, stepUnderspecified, taskletRef,
87 chunkElements, beanElements, refElements);
88
89 if (!chunkElements.isEmpty()) {
90 chunkElementParser.parse(chunkElements.get(0), bd, parserContext, stepUnderspecified);
91 }
92 else {
93 BeanMetadataElement bme = null;
94 if (StringUtils.hasText(taskletRef)) {
95 bme = new RuntimeBeanReference(taskletRef);
96 }
97 else if (beanElements.size() == 1) {
98 Element beanElement = beanElements.get(0);
99 BeanDefinitionHolder beanDefinitionHolder = parserContext.getDelegate().parseBeanDefinitionElement(
100 beanElement, bd);
101 parserContext.getDelegate().decorateBeanDefinitionIfRequired(beanElement, beanDefinitionHolder);
102 bme = beanDefinitionHolder;
103 }
104 else if (refElements.size() == 1) {
105 bme = (BeanMetadataElement) parserContext.getDelegate().parsePropertySubElement(refElements.get(0),
106 null);
107 }
108
109 if (StringUtils.hasText(taskletMethod)) {
110 bme = getTaskletAdapter(bme, taskletMethod);
111 }
112
113 if (bme != null) {
114 bd.getPropertyValues().addPropertyValue("tasklet", bme);
115 }
116 }
117
118 handleTaskletElement(taskletElement, bd, parserContext);
119 }
120
121
122
123
124 private BeanMetadataElement getTaskletAdapter(BeanMetadataElement bme, String taskletMethod) {
125 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MethodInvokingTaskletAdapter.class);
126 builder.addPropertyValue("targetMethod", taskletMethod);
127 builder.addPropertyValue("targetObject", bme);
128 return builder.getBeanDefinition();
129 }
130
131 private void validateTaskletAttributesAndSubelements(Element taskletElement, ParserContext parserContext,
132 boolean stepUnderspecified, String taskletRef, List<Element> chunkElements, List<Element> beanElements,
133 List<Element> refElements) {
134 int total = (StringUtils.hasText(taskletRef) ? 1 : 0) + chunkElements.size() + beanElements.size()
135 + refElements.size();
136
137 StringBuilder found = new StringBuilder();
138 if (total > 1) {
139 if (StringUtils.hasText(taskletRef)) {
140 found.append("'" + TASKLET_REF_ATTR + "' attribute, ");
141 }
142 if (chunkElements.size() == 1) {
143 found.append("<" + CHUNK_ELE + "/> element, ");
144 }
145 else if (chunkElements.size() > 1) {
146 found.append(chunkElements.size() + " <" + CHUNK_ELE + "/> elements, ");
147 }
148 if (beanElements.size() == 1) {
149 found.append("<" + BEAN_ELE + "/> element, ");
150 }
151 else if (beanElements.size() > 1) {
152 found.append(beanElements.size() + " <" + BEAN_ELE + "/> elements, ");
153 }
154 if (refElements.size() == 1) {
155 found.append("<" + REF_ELE + "/> element, ");
156 }
157 else if (refElements.size() > 1) {
158 found.append(refElements.size() + " <" + REF_ELE + "/> elements, ");
159 }
160 found.delete(found.length() - 2, found.length());
161 }
162 else {
163 found.append("None");
164 }
165
166 String error = null;
167 if (stepUnderspecified) {
168 if (total > 1) {
169 error = "may not have more than";
170 }
171 }
172 else if (total != 1) {
173 error = "must have exactly";
174 }
175
176 if (error != null) {
177 parserContext.getReaderContext().error(
178 "The <" + taskletElement.getTagName() + "/> element " + error + " one of: '" + TASKLET_REF_ATTR
179 + "' attribute, <" + CHUNK_ELE + "/> element, <" + BEAN_ELE + "/> attribute, or <"
180 + REF_ELE + "/> element. Found: " + found + ".", taskletElement);
181 }
182 }
183
184 private void handleTaskletElement(Element taskletElement, AbstractBeanDefinition bd, ParserContext parserContext) {
185 MutablePropertyValues propertyValues = bd.getPropertyValues();
186 handleTaskletAttributes(taskletElement, propertyValues);
187 handleTransactionAttributesElement(taskletElement, propertyValues);
188 stepListenerParser.handleListenersElement(taskletElement, bd, parserContext);
189 handleExceptionElement(taskletElement, parserContext, propertyValues, "no-rollback-exception-classes",
190 "noRollbackExceptionClasses");
191 bd.setRole(BeanDefinition.ROLE_SUPPORT);
192 bd.setSource(parserContext.extractSource(taskletElement));
193 }
194
195 private void handleTransactionAttributesElement(Element stepElement, MutablePropertyValues propertyValues) {
196 @SuppressWarnings("unchecked")
197 List<Element> txAttrElements = DomUtils.getChildElementsByTagName(stepElement, TX_ATTRIBUTES_ELE);
198 if (txAttrElements.size() == 1) {
199 Element txAttrElement = txAttrElements.get(0);
200 String propagation = txAttrElement.getAttribute("propagation");
201 if (StringUtils.hasText(propagation)) {
202 propertyValues.addPropertyValue("propagation", propagation);
203 }
204 String isolation = txAttrElement.getAttribute("isolation");
205 if (StringUtils.hasText(isolation)) {
206 propertyValues.addPropertyValue("isolation", isolation);
207 }
208 String timeout = txAttrElement.getAttribute("timeout");
209 if (StringUtils.hasText(timeout)) {
210 propertyValues.addPropertyValue("transactionTimeout", timeout);
211 }
212 }
213 }
214
215 @SuppressWarnings("unchecked")
216 private void handleExceptionElement(Element element, ParserContext parserContext,
217 MutablePropertyValues propertyValues, String exceptionListName, String propertyName) {
218 List<Element> children = DomUtils.getChildElementsByTagName(element, exceptionListName);
219 if (children.size() == 1) {
220 Element exceptionClassesElement = children.get(0);
221 ManagedList list = new ManagedList();
222 list.setMergeEnabled(exceptionClassesElement.hasAttribute(MERGE_ATTR)
223 && Boolean.valueOf(exceptionClassesElement.getAttribute(MERGE_ATTR)));
224 addExceptionClasses("include", exceptionClassesElement, list, parserContext);
225 propertyValues.addPropertyValue(propertyName, list);
226 }
227 else if (children.size() > 1) {
228 parserContext.getReaderContext().error(
229 "The <" + exceptionListName + "/> element may not appear more than once in a single <"
230 + element.getNodeName() + "/>.", element);
231 }
232 }
233
234 @SuppressWarnings("unchecked")
235 private void addExceptionClasses(String elementName, Element exceptionClassesElement, ManagedList list,
236 ParserContext parserContext) {
237 for (Element child : (List<Element>) DomUtils.getChildElementsByTagName(exceptionClassesElement, elementName)) {
238 String className = child.getAttribute("class");
239 list.add(new TypedStringValue(className, Class.class));
240 }
241 }
242
243 private void handleTaskletAttributes(Element taskletElement, MutablePropertyValues propertyValues) {
244 String transactionManagerRef = taskletElement.getAttribute(TRANSACTION_MANAGER_ATTR);
245 if (StringUtils.hasText(transactionManagerRef)) {
246 propertyValues.addPropertyValue("transactionManager", new RuntimeBeanReference(transactionManagerRef));
247 }
248 String startLimit = taskletElement.getAttribute("start-limit");
249 if (StringUtils.hasText(startLimit)) {
250 propertyValues.addPropertyValue("startLimit", startLimit);
251 }
252 String allowStartIfComplete = taskletElement.getAttribute("allow-start-if-complete");
253 if (StringUtils.hasText(allowStartIfComplete)) {
254 propertyValues.addPropertyValue("allowStartIfComplete", allowStartIfComplete);
255 }
256 String taskExecutorBeanId = taskletElement.getAttribute(TASK_EXECUTOR_ATTR);
257 if (StringUtils.hasText(taskExecutorBeanId)) {
258 RuntimeBeanReference taskExecutorRef = new RuntimeBeanReference(taskExecutorBeanId);
259 propertyValues.addPropertyValue("taskExecutor", taskExecutorRef);
260 }
261 String throttleLimit = taskletElement.getAttribute("throttle-limit");
262 if (StringUtils.hasText(throttleLimit)) {
263 propertyValues.addPropertyValue("throttleLimit", throttleLimit);
264 }
265 }
266
267 }