View Javadoc

1   /*
2    * Copyright 2006-2010 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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   * Parse a tasklet element for a step.
37   * 
38   * @author Dave Syer
39   * 
40   * @since 2.1
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  	// TODO: BATCH-1689, make this StepListenerParser.taskletListenerMetaData()
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 	 * Create a {@link MethodInvokingTaskletAdapter} for the POJO specified.
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 }