001/* 002 * Copyright 2006-2010 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.springframework.batch.core.configuration.xml; 017 018import java.util.List; 019 020import org.springframework.batch.core.step.tasklet.MethodInvokingTaskletAdapter; 021import org.springframework.beans.BeanMetadataElement; 022import org.springframework.beans.MutablePropertyValues; 023import org.springframework.beans.factory.config.BeanDefinition; 024import org.springframework.beans.factory.config.BeanDefinitionHolder; 025import org.springframework.beans.factory.config.RuntimeBeanReference; 026import org.springframework.beans.factory.config.TypedStringValue; 027import org.springframework.beans.factory.support.AbstractBeanDefinition; 028import org.springframework.beans.factory.support.BeanDefinitionBuilder; 029import org.springframework.beans.factory.support.ManagedList; 030import org.springframework.beans.factory.xml.ParserContext; 031import org.springframework.util.StringUtils; 032import org.springframework.util.xml.DomUtils; 033import org.w3c.dom.Element; 034 035/** 036 * Parse a tasklet element for a step. 037 * 038 * @author Dave Syer 039 * 040 * @since 2.1 041 * 042 */ 043public class TaskletParser { 044 045 private static final String TRANSACTION_MANAGER_ATTR = "transaction-manager"; 046 047 private static final String TASKLET_REF_ATTR = "ref"; 048 049 private static final String TASKLET_METHOD_ATTR = "method"; 050 051 private static final String BEAN_ELE = "bean"; 052 053 private static final String REF_ELE = "ref"; 054 055 private static final String TASK_EXECUTOR_ATTR = "task-executor"; 056 057 private static final String CHUNK_ELE = "chunk"; 058 059 private static final String TX_ATTRIBUTES_ELE = "transaction-attributes"; 060 061 private static final String MERGE_ATTR = "merge"; 062 063 private static final ChunkElementParser chunkElementParser = new ChunkElementParser(); 064 065 // TODO: BATCH-1689, make this StepListenerParser.taskletListenerMetaData() 066 private static final StepListenerParser stepListenerParser = new StepListenerParser(); 067 068 public void parseTasklet(Element stepElement, Element taskletElement, AbstractBeanDefinition bd, 069 ParserContext parserContext, boolean stepUnderspecified) { 070 071 bd.setBeanClass(StepParserStepFactoryBean.class); 072 bd.setAttribute("isNamespaceStep", true); 073 074 String taskletRef = taskletElement.getAttribute(TASKLET_REF_ATTR); 075 String taskletMethod = taskletElement.getAttribute(TASKLET_METHOD_ATTR); 076 List<Element> chunkElements = DomUtils.getChildElementsByTagName(taskletElement, CHUNK_ELE); 077 List<Element> beanElements = DomUtils.getChildElementsByTagName(taskletElement, BEAN_ELE); 078 List<Element> refElements = DomUtils.getChildElementsByTagName(taskletElement, REF_ELE); 079 080 validateTaskletAttributesAndSubelements(taskletElement, parserContext, stepUnderspecified, taskletRef, 081 chunkElements, beanElements, refElements); 082 083 if (!chunkElements.isEmpty()) { 084 chunkElementParser.parse(chunkElements.get(0), bd, parserContext, stepUnderspecified); 085 } 086 else { 087 BeanMetadataElement bme = null; 088 if (StringUtils.hasText(taskletRef)) { 089 bme = new RuntimeBeanReference(taskletRef); 090 } 091 else if (beanElements.size() == 1) { 092 Element beanElement = beanElements.get(0); 093 BeanDefinitionHolder beanDefinitionHolder = parserContext.getDelegate().parseBeanDefinitionElement( 094 beanElement, bd); 095 parserContext.getDelegate().decorateBeanDefinitionIfRequired(beanElement, beanDefinitionHolder); 096 bme = beanDefinitionHolder; 097 } 098 else if (refElements.size() == 1) { 099 bme = (BeanMetadataElement) parserContext.getDelegate().parsePropertySubElement(refElements.get(0), 100 null); 101 } 102 103 if (StringUtils.hasText(taskletMethod)) { 104 bme = getTaskletAdapter(bme, taskletMethod); 105 } 106 107 if (bme != null) { 108 bd.getPropertyValues().addPropertyValue("tasklet", bme); 109 } 110 } 111 112 handleTaskletElement(taskletElement, bd, parserContext); 113 } 114 115 /** 116 * Create a {@link MethodInvokingTaskletAdapter} for the POJO specified. 117 */ 118 private BeanMetadataElement getTaskletAdapter(BeanMetadataElement bme, String taskletMethod) { 119 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MethodInvokingTaskletAdapter.class); 120 builder.addPropertyValue("targetMethod", taskletMethod); 121 builder.addPropertyValue("targetObject", bme); 122 return builder.getBeanDefinition(); 123 } 124 125 private void validateTaskletAttributesAndSubelements(Element taskletElement, ParserContext parserContext, 126 boolean stepUnderspecified, String taskletRef, List<Element> chunkElements, List<Element> beanElements, 127 List<Element> refElements) { 128 int total = (StringUtils.hasText(taskletRef) ? 1 : 0) + chunkElements.size() + beanElements.size() 129 + refElements.size(); 130 131 StringBuilder found = new StringBuilder(); 132 if (total > 1) { 133 if (StringUtils.hasText(taskletRef)) { 134 found.append("'" + TASKLET_REF_ATTR + "' attribute, "); 135 } 136 if (chunkElements.size() == 1) { 137 found.append("<" + CHUNK_ELE + "/> element, "); 138 } 139 else if (chunkElements.size() > 1) { 140 found.append(chunkElements.size() + " <" + CHUNK_ELE + "/> elements, "); 141 } 142 if (beanElements.size() == 1) { 143 found.append("<" + BEAN_ELE + "/> element, "); 144 } 145 else if (beanElements.size() > 1) { 146 found.append(beanElements.size() + " <" + BEAN_ELE + "/> elements, "); 147 } 148 if (refElements.size() == 1) { 149 found.append("<" + REF_ELE + "/> element, "); 150 } 151 else if (refElements.size() > 1) { 152 found.append(refElements.size() + " <" + REF_ELE + "/> elements, "); 153 } 154 found.delete(found.length() - 2, found.length()); 155 } 156 else { 157 found.append("None"); 158 } 159 160 String error = null; 161 if (stepUnderspecified) { 162 if (total > 1) { 163 error = "may not have more than"; 164 } 165 } 166 else if (total != 1) { 167 error = "must have exactly"; 168 } 169 170 if (error != null) { 171 parserContext.getReaderContext().error( 172 "The <" + taskletElement.getTagName() + "/> element " + error + " one of: '" + TASKLET_REF_ATTR 173 + "' attribute, <" + CHUNK_ELE + "/> element, <" + BEAN_ELE + "/> attribute, or <" 174 + REF_ELE + "/> element. Found: " + found + ".", taskletElement); 175 } 176 } 177 178 private void handleTaskletElement(Element taskletElement, AbstractBeanDefinition bd, ParserContext parserContext) { 179 MutablePropertyValues propertyValues = bd.getPropertyValues(); 180 handleTaskletAttributes(taskletElement, propertyValues); 181 handleTransactionAttributesElement(taskletElement, propertyValues); 182 stepListenerParser.handleListenersElement(taskletElement, bd, parserContext); 183 handleExceptionElement(taskletElement, parserContext, propertyValues, "no-rollback-exception-classes", 184 "noRollbackExceptionClasses"); 185 bd.setRole(BeanDefinition.ROLE_SUPPORT); 186 bd.setSource(parserContext.extractSource(taskletElement)); 187 } 188 189 private void handleTransactionAttributesElement(Element stepElement, MutablePropertyValues propertyValues) { 190 List<Element> txAttrElements = DomUtils.getChildElementsByTagName(stepElement, TX_ATTRIBUTES_ELE); 191 if (txAttrElements.size() == 1) { 192 Element txAttrElement = txAttrElements.get(0); 193 String propagation = txAttrElement.getAttribute("propagation"); 194 if (StringUtils.hasText(propagation)) { 195 propertyValues.addPropertyValue("propagation", propagation); 196 } 197 String isolation = txAttrElement.getAttribute("isolation"); 198 if (StringUtils.hasText(isolation)) { 199 propertyValues.addPropertyValue("isolation", isolation); 200 } 201 String timeout = txAttrElement.getAttribute("timeout"); 202 if (StringUtils.hasText(timeout)) { 203 propertyValues.addPropertyValue("transactionTimeout", timeout); 204 } 205 } 206 } 207 208 private void handleExceptionElement(Element element, ParserContext parserContext, 209 MutablePropertyValues propertyValues, String exceptionListName, String propertyName) { 210 List<Element> children = DomUtils.getChildElementsByTagName(element, exceptionListName); 211 if (children.size() == 1) { 212 Element exceptionClassesElement = children.get(0); 213 ManagedList<TypedStringValue> list = new ManagedList<TypedStringValue>(); 214 list.setMergeEnabled(exceptionClassesElement.hasAttribute(MERGE_ATTR) 215 && Boolean.valueOf(exceptionClassesElement.getAttribute(MERGE_ATTR))); 216 addExceptionClasses("include", exceptionClassesElement, list, parserContext); 217 propertyValues.addPropertyValue(propertyName, list); 218 } 219 else if (children.size() > 1) { 220 parserContext.getReaderContext().error( 221 "The <" + exceptionListName + "/> element may not appear more than once in a single <" 222 + element.getNodeName() + "/>.", element); 223 } 224 } 225 226 private void addExceptionClasses(String elementName, Element exceptionClassesElement, ManagedList<TypedStringValue> list, 227 ParserContext parserContext) { 228 for (Element child : DomUtils.getChildElementsByTagName(exceptionClassesElement, elementName)) { 229 String className = child.getAttribute("class"); 230 list.add(new TypedStringValue(className, Class.class)); 231 } 232 } 233 234 private void handleTaskletAttributes(Element taskletElement, MutablePropertyValues propertyValues) { 235 String transactionManagerRef = taskletElement.getAttribute(TRANSACTION_MANAGER_ATTR); 236 if (StringUtils.hasText(transactionManagerRef)) { 237 propertyValues.addPropertyValue("transactionManager", new RuntimeBeanReference(transactionManagerRef)); 238 } 239 String startLimit = taskletElement.getAttribute("start-limit"); 240 if (StringUtils.hasText(startLimit)) { 241 propertyValues.addPropertyValue("startLimit", startLimit); 242 } 243 String allowStartIfComplete = taskletElement.getAttribute("allow-start-if-complete"); 244 if (StringUtils.hasText(allowStartIfComplete)) { 245 propertyValues.addPropertyValue("allowStartIfComplete", allowStartIfComplete); 246 } 247 String taskExecutorBeanId = taskletElement.getAttribute(TASK_EXECUTOR_ATTR); 248 if (StringUtils.hasText(taskExecutorBeanId)) { 249 RuntimeBeanReference taskExecutorRef = new RuntimeBeanReference(taskExecutorBeanId); 250 propertyValues.addPropertyValue("taskExecutor", taskExecutorRef); 251 } 252 String throttleLimit = taskletElement.getAttribute("throttle-limit"); 253 if (StringUtils.hasText(throttleLimit)) { 254 propertyValues.addPropertyValue("throttleLimit", throttleLimit); 255 } 256 } 257 258}