View Javadoc

1   /*
2    * Copyright 2006-2009 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 org.springframework.batch.core.listener.StepListenerMetaData;
19  import org.springframework.beans.MutablePropertyValues;
20  import org.springframework.beans.factory.config.BeanDefinition;
21  import org.springframework.beans.factory.config.BeanDefinitionHolder;
22  import org.springframework.beans.factory.config.RuntimeBeanReference;
23  import org.springframework.beans.factory.config.TypedStringValue;
24  import org.springframework.beans.factory.support.AbstractBeanDefinition;
25  import org.springframework.beans.factory.support.BeanDefinitionBuilder;
26  import org.springframework.beans.factory.support.GenericBeanDefinition;
27  import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
28  import org.springframework.beans.factory.xml.ParserContext;
29  import org.springframework.util.StringUtils;
30  import org.springframework.util.xml.DomUtils;
31  import org.w3c.dom.Element;
32  import org.w3c.dom.Node;
33  import org.w3c.dom.NodeList;
34  
35  /**
36   * Internal parser for the <step/> elements inside a job. A step element
37   * references a bean definition for a
38   * {@link org.springframework.batch.core.Step} and goes on to (optionally) list
39   * a set of transitions from that step to others with <next on="pattern"
40   * to="stepName"/>. Used by the {@link JobParser}.
41   *
42   * @author Dave Syer
43   * @author Thomas Risberg
44   * @author Josh Long
45   * @see JobParser
46   * @since 2.0
47   */
48  public abstract class AbstractStepParser {
49  
50  	protected static final String ID_ATTR = "id";
51  
52  	private static final String PARENT_ATTR = "parent";
53  
54  	private static final String REF_ATTR = "ref";
55  	
56  	private static final String ALLOW_START_ATTR = "allow-start-if-complete";
57  
58  	private static final String TASKLET_ELE = "tasklet";
59  
60  	private static final String PARTITION_ELE = "partition";
61  
62  	private static final String JOB_ELE = "job";
63  
64  	private static final String JOB_PARAMS_EXTRACTOR_ATTR = "job-parameters-extractor";
65  
66  	private static final String JOB_LAUNCHER_ATTR = "job-launcher";
67  
68  	private static final String STEP_ATTR = "step";
69  
70  	private static final String STEP_ELE = STEP_ATTR;
71  
72  	private static final String PARTITIONER_ATTR = "partitioner";
73  
74  	private static final String AGGREGATOR_ATTR = "aggregator";
75  
76  	private static final String HANDLER_ATTR = "handler";
77  
78  	private static final String HANDLER_ELE = "handler";
79  
80  	private static final String TASK_EXECUTOR_ATTR = "task-executor";
81  
82  	private static final String GRID_SIZE_ATTR = "grid-size";
83  
84  	private static final String FLOW_ELE = "flow";
85  
86  	private static final String JOB_REPO_ATTR = "job-repository";
87  
88  	private static final StepListenerParser stepListenerParser = new StepListenerParser(StepListenerMetaData.stepExecutionListenerMetaData());
89  
90  	/**
91  	 * @param stepElement   The <step/> element
92  	 * @param parserContext
93  	 * @param jobFactoryRef the reference to the {@link JobParserJobFactoryBean}
94  	 *                      from the enclosing tag. Use 'null' if unknown.
95  	 */
96  	protected AbstractBeanDefinition parseStep(Element stepElement, ParserContext parserContext, String jobFactoryRef) {
97  
98  		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
99  		AbstractBeanDefinition bd = builder.getRawBeanDefinition();
100 
101 		// look at all nested elements
102 		NodeList children = stepElement.getChildNodes();
103 		
104 		for (int i = 0; i < children.getLength(); i++) {
105 			Node nd = children.item(i);
106 
107 			if (nd instanceof Element) {
108 				Element nestedElement = (Element) nd;
109 				String name = nestedElement.getLocalName();
110 
111 				if (TASKLET_ELE.equals(name)) {
112 					boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement);
113 					new TaskletParser().parseTasklet(stepElement, nestedElement, bd, parserContext, stepUnderspecified);
114 				}
115 				else if (FLOW_ELE.equals(name)) {
116 					boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement);
117 					parseFlow(stepElement, nestedElement, bd, parserContext, stepUnderspecified);
118 				}
119 				else if (PARTITION_ELE.equals(name)) {
120 					boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement);
121 					parsePartition(stepElement, nestedElement, bd, parserContext, stepUnderspecified, jobFactoryRef);
122 				}
123 				else if (JOB_ELE.equals(name)) {
124 					boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement);
125 					parseJob(stepElement, nestedElement, bd, parserContext, stepUnderspecified);
126 				}
127 				else if ("description".equals(name)) {
128 					bd.setDescription(nestedElement.getTextContent());
129 				}
130 
131 				// nested bean reference/declaration
132 				else {
133 					String ns = nestedElement.getNamespaceURI();
134 					Object value = null;
135 					boolean skip = false;
136 
137 					// Spring NS
138 					if ((ns == null && name.equals(BeanDefinitionParserDelegate.BEAN_ELEMENT))
139 							|| ns.equals(BeanDefinitionParserDelegate.BEANS_NAMESPACE_URI)) {
140 						BeanDefinitionHolder holder = parserContext.getDelegate().parseBeanDefinitionElement(nestedElement);
141 						value = parserContext.getDelegate().decorateBeanDefinitionIfRequired(nestedElement, holder);
142 					}
143 					// Spring Batch transitions
144 					else if (ns.equals("http://www.springframework.org/schema/batch")) {
145 						// don't parse
146 						skip = true;
147 					}
148 					// Custom NS
149 					else {
150 						value = parserContext.getDelegate().parseCustomElement(nestedElement);
151 					}
152 					
153 					if (!skip) {
154 						bd.setBeanClass(StepParserStepFactoryBean.class);
155 						bd.setAttribute("isNamespaceStep", true);
156 						builder.addPropertyValue("tasklet", value);
157 					}
158 				}
159 			}
160 		}
161 
162 		String parentRef = stepElement.getAttribute(PARENT_ATTR);
163 		if (StringUtils.hasText(parentRef)) {
164 			bd.setParentName(parentRef);
165 		}
166 
167 		String isAbstract = stepElement.getAttribute("abstract");
168 		if (StringUtils.hasText(isAbstract)) {
169 			bd.setAbstract(Boolean.valueOf(isAbstract));
170 		}
171 
172 		String jobRepositoryRef = stepElement.getAttribute(JOB_REPO_ATTR);
173 		if (StringUtils.hasText(jobRepositoryRef)) {
174 			builder.addPropertyReference("jobRepository", jobRepositoryRef);
175 		}
176 
177 		if (StringUtils.hasText(jobFactoryRef)) {
178 			bd.setAttribute("jobParserJobFactoryBeanRef", jobFactoryRef);
179 		}
180 
181 		//add the allow parser here
182 		String isAllowStart = stepElement.getAttribute(ALLOW_START_ATTR);
183 		if (StringUtils.hasText(isAllowStart)) {
184 			//check if the value is already set from an inner element
185 			if (!bd.getPropertyValues().contains("allowStartIfComplete")) {
186 				//set the value as a property
187 				bd.getPropertyValues().add("allowStartIfComplete", Boolean.valueOf(isAllowStart));
188 			}//end if
189 		}
190 		
191 		stepListenerParser.handleListenersElement(stepElement, bd, parserContext);
192 		return bd;
193 	}
194 
195 	private void parsePartition(Element stepElement, Element partitionElement, AbstractBeanDefinition bd, ParserContext parserContext, boolean stepUnderspecified, String jobFactoryRef ) {
196 
197 		bd.setBeanClass(StepParserStepFactoryBean.class);
198 		bd.setAttribute("isNamespaceStep", true);
199 		String stepRef = partitionElement.getAttribute(STEP_ATTR);
200 		String partitionerRef = partitionElement.getAttribute(PARTITIONER_ATTR);
201 		String aggregatorRef = partitionElement.getAttribute(AGGREGATOR_ATTR);
202 		String handlerRef = partitionElement.getAttribute(HANDLER_ATTR);
203 
204 		if (!StringUtils.hasText(partitionerRef)) {
205 			parserContext.getReaderContext().error("You must specify a partitioner", partitionElement);
206 			return;
207 		}
208 
209 		MutablePropertyValues propertyValues = bd.getPropertyValues();
210 
211 		propertyValues.addPropertyValue("partitioner", new RuntimeBeanReference(partitionerRef));
212 		if (StringUtils.hasText(aggregatorRef)) {
213 			propertyValues.addPropertyValue("stepExecutionAggregator", new RuntimeBeanReference(aggregatorRef));			
214 		}
215 
216 		boolean customHandler = false;
217 		if (!StringUtils.hasText(handlerRef)) {
218 			Element handlerElement = DomUtils.getChildElementByTagName(partitionElement, HANDLER_ELE);
219 			if (handlerElement != null) {
220 				String taskExecutorRef = handlerElement.getAttribute(TASK_EXECUTOR_ATTR);
221 				if (StringUtils.hasText(taskExecutorRef)) {
222 					propertyValues.addPropertyValue("taskExecutor", new RuntimeBeanReference(taskExecutorRef));
223 				}
224 				String gridSize = handlerElement.getAttribute(GRID_SIZE_ATTR);
225 				if (StringUtils.hasText(gridSize)) {
226 					propertyValues.addPropertyValue("gridSize", new TypedStringValue(gridSize));
227 				}
228 			}
229 		} else {
230 			customHandler = true;
231 			BeanDefinition partitionHandler = BeanDefinitionBuilder.genericBeanDefinition().getRawBeanDefinition();
232 			partitionHandler.setParentName(handlerRef);
233 			propertyValues.addPropertyValue("partitionHandler", partitionHandler);
234 		}
235 
236 		Element inlineStepElement = DomUtils.getChildElementByTagName(partitionElement, STEP_ELE);
237 		if (inlineStepElement == null && !StringUtils.hasText(stepRef) && !customHandler) {
238 			parserContext.getReaderContext().error("You must specify a step", partitionElement);
239 			return;
240 		}
241 
242 		if (StringUtils.hasText(stepRef)) {
243 			propertyValues.addPropertyValue("step", new RuntimeBeanReference(stepRef));
244 		} else if( inlineStepElement!=null) {
245 			AbstractBeanDefinition stepDefinition = parseStep(inlineStepElement, parserContext, jobFactoryRef);
246 			stepDefinition.getPropertyValues().addPropertyValue("name", stepElement.getAttribute(ID_ATTR));
247 			propertyValues.addPropertyValue("step", stepDefinition );
248 		}
249 
250 	}
251 
252 	private void parseJob(Element stepElement, Element jobElement, AbstractBeanDefinition bd, ParserContext parserContext, boolean stepUnderspecified) {
253 
254 		bd.setBeanClass(StepParserStepFactoryBean.class);
255 		bd.setAttribute("isNamespaceStep", true);
256 		String jobRef = jobElement.getAttribute(REF_ATTR);
257 
258 		if (!StringUtils.hasText(jobRef)) {
259 			parserContext.getReaderContext().error("You must specify a job", jobElement);
260 			return;
261 		}
262 
263 		MutablePropertyValues propertyValues = bd.getPropertyValues();
264 		propertyValues.addPropertyValue("job", new RuntimeBeanReference(jobRef));
265 
266 		String jobParametersExtractor = jobElement.getAttribute(JOB_PARAMS_EXTRACTOR_ATTR);
267 		String jobLauncher = jobElement.getAttribute(JOB_LAUNCHER_ATTR);
268 
269 		if (StringUtils.hasText(jobParametersExtractor)) {
270 			propertyValues.addPropertyValue("jobParametersExtractor", new RuntimeBeanReference(jobParametersExtractor));
271 		}
272 		if (StringUtils.hasText(jobLauncher)) {
273 			propertyValues.addPropertyValue("jobLauncher", new RuntimeBeanReference(jobLauncher));
274 		}
275 
276 	}
277 
278 
279 	private void parseFlow(Element stepElement, Element flowElement, AbstractBeanDefinition bd,
280 	                       ParserContext parserContext, boolean stepUnderspecified) {
281 
282 		bd.setBeanClass(StepParserStepFactoryBean.class);
283 		bd.setAttribute("isNamespaceStep", true);
284 		String flowRef = flowElement.getAttribute(PARENT_ATTR);
285 		String idAttribute = stepElement.getAttribute(ID_ATTR);
286 
287 		BeanDefinition flowDefinition = new GenericBeanDefinition();
288 		flowDefinition.setParentName(flowRef);
289 		MutablePropertyValues propertyValues = flowDefinition.getPropertyValues();
290 		if (StringUtils.hasText(idAttribute)) {
291 			propertyValues.addPropertyValue("name", idAttribute);
292 		}
293 
294 		bd.getPropertyValues().addPropertyValue("flow", flowDefinition);
295 
296 	}
297 
298 }