001/* 002 * Copyright 2006-2009 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 org.w3c.dom.Element; 019import org.w3c.dom.Node; 020import org.w3c.dom.NodeList; 021 022import org.springframework.batch.core.listener.StepListenerMetaData; 023import org.springframework.beans.MutablePropertyValues; 024import org.springframework.beans.factory.config.BeanDefinition; 025import org.springframework.beans.factory.config.BeanDefinitionHolder; 026import org.springframework.beans.factory.config.RuntimeBeanReference; 027import org.springframework.beans.factory.config.TypedStringValue; 028import org.springframework.beans.factory.support.AbstractBeanDefinition; 029import org.springframework.beans.factory.support.BeanDefinitionBuilder; 030import org.springframework.beans.factory.support.GenericBeanDefinition; 031import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate; 032import org.springframework.beans.factory.xml.ParserContext; 033import org.springframework.util.StringUtils; 034import org.springframework.util.xml.DomUtils; 035 036/** 037 * Internal parser for the <step/> elements inside a job. A step element 038 * references a bean definition for a 039 * {@link org.springframework.batch.core.Step} and goes on to (optionally) list 040 * a set of transitions from that step to others with <next on="pattern" 041 * to="stepName"/>. Used by the {@link JobParser}. 042 * 043 * @author Dave Syer 044 * @author Thomas Risberg 045 * @author Josh Long 046 * @see JobParser 047 * @since 2.0 048 */ 049public abstract class AbstractStepParser { 050 051 protected static final String ID_ATTR = "id"; 052 053 private static final String PARENT_ATTR = "parent"; 054 055 private static final String REF_ATTR = "ref"; 056 057 private static final String ALLOW_START_ATTR = "allow-start-if-complete"; 058 059 private static final String TASKLET_ELE = "tasklet"; 060 061 private static final String PARTITION_ELE = "partition"; 062 063 private static final String JOB_ELE = "job"; 064 065 private static final String JOB_PARAMS_EXTRACTOR_ATTR = "job-parameters-extractor"; 066 067 private static final String JOB_LAUNCHER_ATTR = "job-launcher"; 068 069 private static final String STEP_ATTR = "step"; 070 071 private static final String STEP_ELE = STEP_ATTR; 072 073 private static final String PARTITIONER_ATTR = "partitioner"; 074 075 private static final String AGGREGATOR_ATTR = "aggregator"; 076 077 private static final String HANDLER_ATTR = "handler"; 078 079 private static final String HANDLER_ELE = "handler"; 080 081 private static final String TASK_EXECUTOR_ATTR = "task-executor"; 082 083 private static final String GRID_SIZE_ATTR = "grid-size"; 084 085 private static final String FLOW_ELE = "flow"; 086 087 private static final String JOB_REPO_ATTR = "job-repository"; 088 089 private static final StepListenerParser stepListenerParser = new StepListenerParser(StepListenerMetaData.stepExecutionListenerMetaData()); 090 091 /** 092 * @param stepElement The <step/> element 093 * @param parserContext instance of {@link ParserContext}. 094 * @param jobFactoryRef the reference to the {@link JobParserJobFactoryBean} 095 * from the enclosing tag. Use 'null' if unknown. 096 * @return {@link AbstractBeanDefinition} for the stepElement. 097 */ 098 protected AbstractBeanDefinition parseStep(Element stepElement, ParserContext parserContext, String jobFactoryRef) { 099 100 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); 101 AbstractBeanDefinition bd = builder.getRawBeanDefinition(); 102 103 // look at all nested elements 104 NodeList children = stepElement.getChildNodes(); 105 106 for (int i = 0; i < children.getLength(); i++) { 107 Node nd = children.item(i); 108 109 if (nd instanceof Element) { 110 Element nestedElement = (Element) nd; 111 String name = nestedElement.getLocalName(); 112 113 if (TASKLET_ELE.equals(name)) { 114 boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement); 115 new TaskletParser().parseTasklet(stepElement, nestedElement, bd, parserContext, stepUnderspecified); 116 } 117 else if (FLOW_ELE.equals(name)) { 118 boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement); 119 parseFlow(stepElement, nestedElement, bd, parserContext, stepUnderspecified); 120 } 121 else if (PARTITION_ELE.equals(name)) { 122 boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement); 123 parsePartition(stepElement, nestedElement, bd, parserContext, stepUnderspecified, jobFactoryRef); 124 } 125 else if (JOB_ELE.equals(name)) { 126 boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement); 127 parseJob(stepElement, nestedElement, bd, parserContext, stepUnderspecified); 128 } 129 else if ("description".equals(name)) { 130 bd.setDescription(nestedElement.getTextContent()); 131 } 132 133 // nested bean reference/declaration 134 else { 135 String ns = nestedElement.getNamespaceURI(); 136 Object value = null; 137 boolean skip = false; 138 139 // Spring NS 140 if ((ns == null && name.equals(BeanDefinitionParserDelegate.BEAN_ELEMENT)) 141 || ns.equals(BeanDefinitionParserDelegate.BEANS_NAMESPACE_URI)) { 142 BeanDefinitionHolder holder = parserContext.getDelegate().parseBeanDefinitionElement(nestedElement); 143 value = parserContext.getDelegate().decorateBeanDefinitionIfRequired(nestedElement, holder); 144 } 145 // Spring Batch transitions 146 else if (ns.equals("http://www.springframework.org/schema/batch")) { 147 // don't parse 148 skip = true; 149 } 150 // Custom NS 151 else { 152 value = parserContext.getDelegate().parseCustomElement(nestedElement); 153 } 154 155 if (!skip) { 156 bd.setBeanClass(StepParserStepFactoryBean.class); 157 bd.setAttribute("isNamespaceStep", true); 158 builder.addPropertyValue("tasklet", value); 159 } 160 } 161 } 162 } 163 164 String parentRef = stepElement.getAttribute(PARENT_ATTR); 165 if (StringUtils.hasText(parentRef)) { 166 bd.setParentName(parentRef); 167 } 168 169 String isAbstract = stepElement.getAttribute("abstract"); 170 if (StringUtils.hasText(isAbstract)) { 171 bd.setAbstract(Boolean.valueOf(isAbstract)); 172 } 173 174 String jobRepositoryRef = stepElement.getAttribute(JOB_REPO_ATTR); 175 if (StringUtils.hasText(jobRepositoryRef)) { 176 builder.addPropertyReference("jobRepository", jobRepositoryRef); 177 } 178 179 if (StringUtils.hasText(jobFactoryRef)) { 180 bd.setAttribute("jobParserJobFactoryBeanRef", jobFactoryRef); 181 } 182 183 //add the allow parser here 184 String isAllowStart = stepElement.getAttribute(ALLOW_START_ATTR); 185 if (StringUtils.hasText(isAllowStart)) { 186 //check if the value is already set from an inner element 187 if (!bd.getPropertyValues().contains("allowStartIfComplete")) { 188 //set the value as a property 189 bd.getPropertyValues().add("allowStartIfComplete", Boolean.valueOf(isAllowStart)); 190 }//end if 191 } 192 193 stepListenerParser.handleListenersElement(stepElement, bd, parserContext); 194 return bd; 195 } 196 197 private void parsePartition(Element stepElement, Element partitionElement, AbstractBeanDefinition bd, ParserContext parserContext, boolean stepUnderspecified, String jobFactoryRef ) { 198 199 bd.setBeanClass(StepParserStepFactoryBean.class); 200 bd.setAttribute("isNamespaceStep", true); 201 String stepRef = partitionElement.getAttribute(STEP_ATTR); 202 String partitionerRef = partitionElement.getAttribute(PARTITIONER_ATTR); 203 String aggregatorRef = partitionElement.getAttribute(AGGREGATOR_ATTR); 204 String handlerRef = partitionElement.getAttribute(HANDLER_ATTR); 205 206 if (!StringUtils.hasText(partitionerRef)) { 207 parserContext.getReaderContext().error("You must specify a partitioner", partitionElement); 208 return; 209 } 210 211 MutablePropertyValues propertyValues = bd.getPropertyValues(); 212 213 propertyValues.addPropertyValue("partitioner", new RuntimeBeanReference(partitionerRef)); 214 if (StringUtils.hasText(aggregatorRef)) { 215 propertyValues.addPropertyValue("stepExecutionAggregator", new RuntimeBeanReference(aggregatorRef)); 216 } 217 218 boolean customHandler = false; 219 if (!StringUtils.hasText(handlerRef)) { 220 Element handlerElement = DomUtils.getChildElementByTagName(partitionElement, HANDLER_ELE); 221 if (handlerElement != null) { 222 String taskExecutorRef = handlerElement.getAttribute(TASK_EXECUTOR_ATTR); 223 if (StringUtils.hasText(taskExecutorRef)) { 224 propertyValues.addPropertyValue("taskExecutor", new RuntimeBeanReference(taskExecutorRef)); 225 } 226 String gridSize = handlerElement.getAttribute(GRID_SIZE_ATTR); 227 if (StringUtils.hasText(gridSize)) { 228 propertyValues.addPropertyValue("gridSize", new TypedStringValue(gridSize)); 229 } 230 } 231 } else { 232 customHandler = true; 233 BeanDefinition partitionHandler = BeanDefinitionBuilder.genericBeanDefinition().getRawBeanDefinition(); 234 partitionHandler.setParentName(handlerRef); 235 propertyValues.addPropertyValue("partitionHandler", partitionHandler); 236 } 237 238 Element inlineStepElement = DomUtils.getChildElementByTagName(partitionElement, STEP_ELE); 239 if (inlineStepElement == null && !StringUtils.hasText(stepRef) && !customHandler) { 240 parserContext.getReaderContext().error("You must specify a step", partitionElement); 241 return; 242 } 243 244 if (StringUtils.hasText(stepRef)) { 245 propertyValues.addPropertyValue("step", new RuntimeBeanReference(stepRef)); 246 } else if( inlineStepElement!=null) { 247 AbstractBeanDefinition stepDefinition = parseStep(inlineStepElement, parserContext, jobFactoryRef); 248 stepDefinition.getPropertyValues().addPropertyValue("name", stepElement.getAttribute(ID_ATTR)); 249 propertyValues.addPropertyValue("step", stepDefinition ); 250 } 251 252 } 253 254 private void parseJob(Element stepElement, Element jobElement, AbstractBeanDefinition bd, ParserContext parserContext, boolean stepUnderspecified) { 255 256 bd.setBeanClass(StepParserStepFactoryBean.class); 257 bd.setAttribute("isNamespaceStep", true); 258 String jobRef = jobElement.getAttribute(REF_ATTR); 259 260 if (!StringUtils.hasText(jobRef)) { 261 parserContext.getReaderContext().error("You must specify a job", jobElement); 262 return; 263 } 264 265 MutablePropertyValues propertyValues = bd.getPropertyValues(); 266 propertyValues.addPropertyValue("job", new RuntimeBeanReference(jobRef)); 267 268 String jobParametersExtractor = jobElement.getAttribute(JOB_PARAMS_EXTRACTOR_ATTR); 269 String jobLauncher = jobElement.getAttribute(JOB_LAUNCHER_ATTR); 270 271 if (StringUtils.hasText(jobParametersExtractor)) { 272 propertyValues.addPropertyValue("jobParametersExtractor", new RuntimeBeanReference(jobParametersExtractor)); 273 } 274 if (StringUtils.hasText(jobLauncher)) { 275 propertyValues.addPropertyValue("jobLauncher", new RuntimeBeanReference(jobLauncher)); 276 } 277 278 } 279 280 281 private void parseFlow(Element stepElement, Element flowElement, AbstractBeanDefinition bd, 282 ParserContext parserContext, boolean stepUnderspecified) { 283 284 bd.setBeanClass(StepParserStepFactoryBean.class); 285 bd.setAttribute("isNamespaceStep", true); 286 String flowRef = flowElement.getAttribute(PARENT_ATTR); 287 String idAttribute = stepElement.getAttribute(ID_ATTR); 288 289 BeanDefinition flowDefinition = new GenericBeanDefinition(); 290 flowDefinition.setParentName(flowRef); 291 MutablePropertyValues propertyValues = flowDefinition.getPropertyValues(); 292 if (StringUtils.hasText(idAttribute)) { 293 propertyValues.addPropertyValue("name", idAttribute); 294 } 295 296 bd.getPropertyValues().addPropertyValue("flow", flowDefinition); 297 298 } 299 300}