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.ArrayList;
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26
27 import org.springframework.batch.core.job.flow.FlowExecutionStatus;
28 import org.springframework.beans.factory.config.BeanDefinition;
29 import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
30 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
31 import org.springframework.beans.factory.support.ManagedList;
32 import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
33 import org.springframework.beans.factory.xml.ParserContext;
34 import org.springframework.util.StringUtils;
35 import org.springframework.util.xml.DomUtils;
36 import org.w3c.dom.Element;
37 import org.w3c.dom.Node;
38 import org.w3c.dom.NodeList;
39
40
41
42
43
44 public abstract class AbstractFlowParser extends AbstractSingleBeanDefinitionParser {
45
46 private static final String ID_ATTR = "id";
47
48 private static final String STEP_ELE = "step";
49
50 private static final String FLOW_ELE = "flow";
51
52 private static final String DECISION_ELE = "decision";
53
54 private static final String SPLIT_ELE = "split";
55
56 private static final String NEXT_ATTR = "next";
57
58 private static final String NEXT_ELE = "next";
59
60 private static final String END_ELE = "end";
61
62 private static final String FAIL_ELE = "fail";
63
64 private static final String STOP_ELE = "stop";
65
66 private static final String ON_ATTR = "on";
67
68 private static final String TO_ATTR = "to";
69
70 private static final String RESTART_ATTR = "restart";
71
72 private static final String EXIT_CODE_ATTR = "exit-code";
73
74 private static final InlineStepParser stepParser = new InlineStepParser();
75
76 private static final FlowElementParser flowParser = new FlowElementParser();
77
78 private static final DecisionParser decisionParser = new DecisionParser();
79
80
81 private static int endCounter = 0;
82
83 private String jobFactoryRef;
84
85
86
87
88
89
90
91
92 protected void setJobFactoryRef(String jobFactoryRef) {
93 this.jobFactoryRef = jobFactoryRef;
94 }
95
96
97
98
99
100
101 @Override
102 protected Class<?> getBeanClass(Element element) {
103 return SimpleFlowFactoryBean.class;
104 }
105
106
107
108
109
110 @Override
111 protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
112
113 List<BeanDefinition> stateTransitions = new ArrayList<BeanDefinition>();
114
115 SplitParser splitParser = new SplitParser(jobFactoryRef);
116 CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(),
117 parserContext.extractSource(element));
118 parserContext.pushContainingComponent(compositeDef);
119
120 boolean stepExists = false;
121 Map<String, Set<String>> reachableElementMap = new HashMap<String, Set<String>>();
122 String startElement = null;
123 NodeList children = element.getChildNodes();
124 for (int i = 0; i < children.getLength(); i++) {
125 Node node = children.item(i);
126 if (node instanceof Element) {
127 String nodeName = node.getLocalName();
128 Element child = (Element) node;
129 if (nodeName.equals(STEP_ELE)) {
130 stateTransitions.addAll(stepParser.parse(child, parserContext, jobFactoryRef));
131 stepExists = true;
132 }
133 else if (nodeName.equals(DECISION_ELE)) {
134 stateTransitions.addAll(decisionParser.parse(child, parserContext));
135 }
136 else if (nodeName.equals(FLOW_ELE)) {
137 stateTransitions.addAll(flowParser.parse(child, parserContext));
138 stepExists = true;
139 }
140 else if (nodeName.equals(SPLIT_ELE)) {
141 stateTransitions.addAll(splitParser
142 .parse(child, new ParserContext(parserContext.getReaderContext(), parserContext
143 .getDelegate(), builder.getBeanDefinition())));
144 stepExists = true;
145 }
146
147 if (Arrays.asList(STEP_ELE, DECISION_ELE, SPLIT_ELE, FLOW_ELE).contains(nodeName)) {
148 reachableElementMap.put(child.getAttribute(ID_ATTR), findReachableElements(child));
149 if (startElement == null) {
150 startElement = child.getAttribute(ID_ATTR);
151 }
152 }
153 }
154 }
155
156 String flowName = (String) builder.getRawBeanDefinition().getAttribute("flowName");
157 if (!stepExists && !StringUtils.hasText(element.getAttribute("parent"))) {
158 parserContext.getReaderContext().error("The flow [" + flowName + "] must contain at least one step, flow or split",
159 element);
160 }
161
162
163 Set<String> allReachableElements = new HashSet<String>();
164 findAllReachableElements(startElement, reachableElementMap, allReachableElements);
165 for (String elementId : reachableElementMap.keySet()) {
166 if (!allReachableElements.contains(elementId)) {
167 parserContext.getReaderContext().error("The element [" + elementId + "] is unreachable", element);
168 }
169 }
170
171 ManagedList managedList = new ManagedList();
172 @SuppressWarnings( { "unchecked", "unused" })
173 boolean dummy = managedList.addAll(stateTransitions);
174 builder.addPropertyValue("stateTransitions", managedList);
175
176 }
177
178
179
180
181
182
183
184 private Set<String> findReachableElements(Element element) {
185 Set<String> reachableElements = new HashSet<String>();
186
187 String nextAttribute = element.getAttribute(NEXT_ATTR);
188 if (StringUtils.hasText(nextAttribute)) {
189 reachableElements.add(nextAttribute);
190 }
191
192 List<Element> nextElements = DomUtils.getChildElementsByTagName(element, NEXT_ELE);
193 for (Element nextElement : nextElements) {
194 String toAttribute = nextElement.getAttribute(TO_ATTR);
195 reachableElements.add(toAttribute);
196 }
197
198 List<Element> stopElements = DomUtils.getChildElementsByTagName(element, STOP_ELE);
199 for (Element stopElement : stopElements) {
200 String restartAttribute = stopElement.getAttribute(RESTART_ATTR);
201 reachableElements.add(restartAttribute);
202 }
203
204 return reachableElements;
205 }
206
207
208
209
210
211
212
213
214 private void findAllReachableElements(String startElement, Map<String, Set<String>> reachableElementMap,
215 Set<String> accumulator) {
216 Set<String> reachableIds = reachableElementMap.get(startElement);
217 accumulator.add(startElement);
218 if (reachableIds != null) {
219 for (String reachable : reachableIds) {
220
221 if (!accumulator.contains(reachable)) {
222 findAllReachableElements(reachable, reachableElementMap, accumulator);
223 }
224 }
225 }
226 }
227
228
229
230
231
232
233
234
235
236 protected static Collection<BeanDefinition> getNextElements(ParserContext parserContext, BeanDefinition stateDef,
237 Element element) {
238 return getNextElements(parserContext, null, stateDef, element);
239 }
240
241
242
243
244
245
246
247
248
249
250
251 protected static Collection<BeanDefinition> getNextElements(ParserContext parserContext, String stepId,
252 BeanDefinition stateDef, Element element) {
253
254 Collection<BeanDefinition> list = new ArrayList<BeanDefinition>();
255
256 String shortNextAttribute = element.getAttribute(NEXT_ATTR);
257 boolean hasNextAttribute = StringUtils.hasText(shortNextAttribute);
258 if (hasNextAttribute) {
259 list.add(getStateTransitionReference(parserContext, stateDef, null, shortNextAttribute));
260 }
261
262 boolean transitionElementExists = false;
263 List<String> patterns = new ArrayList<String>();
264 for (String transitionName : new String[] { NEXT_ELE, STOP_ELE, END_ELE, FAIL_ELE }) {
265 List<Element> transitionElements = DomUtils.getChildElementsByTagName(element, transitionName);
266 for (Element transitionElement : transitionElements) {
267 verifyUniquePattern(transitionElement, patterns, element, parserContext);
268 list.addAll(parseTransitionElement(transitionElement, stepId, stateDef, parserContext));
269 transitionElementExists = true;
270 }
271 }
272
273 if (!transitionElementExists) {
274 list.addAll(createTransition(FlowExecutionStatus.FAILED, FlowExecutionStatus.FAILED.getName(), null, null,
275 stateDef, parserContext, false));
276 list.addAll(createTransition(FlowExecutionStatus.UNKNOWN, FlowExecutionStatus.UNKNOWN.getName(), null, null,
277 stateDef, parserContext, false));
278 if (!hasNextAttribute) {
279 list.addAll(createTransition(FlowExecutionStatus.COMPLETED, null, null, null, stateDef, parserContext,
280 false));
281 }
282 }
283 else if (hasNextAttribute) {
284 parserContext.getReaderContext().error(
285 "The <" + element.getNodeName() + "/> may not contain a '" + NEXT_ATTR
286 + "' attribute and a transition element", element);
287 }
288
289 return list;
290 }
291
292
293
294
295
296
297
298 private static void verifyUniquePattern(Element transitionElement, List<String> patterns, Element element,
299 ParserContext parserContext) {
300 String onAttribute = transitionElement.getAttribute(ON_ATTR);
301 if (patterns.contains(onAttribute)) {
302 parserContext.getReaderContext().error("Duplicate transition pattern found for '" + onAttribute + "'",
303 element);
304 }
305 patterns.add(onAttribute);
306 }
307
308
309
310
311
312
313
314
315
316 private static Collection<BeanDefinition> parseTransitionElement(Element transitionElement, String stateId,
317 BeanDefinition stateDef, ParserContext parserContext) {
318
319 FlowExecutionStatus status = getBatchStatusFromEndTransitionName(transitionElement.getNodeName());
320 String onAttribute = transitionElement.getAttribute(ON_ATTR);
321 String restartAttribute = transitionElement.getAttribute(RESTART_ATTR);
322 String nextAttribute = transitionElement.getAttribute(TO_ATTR);
323 if (!StringUtils.hasText(nextAttribute)) {
324 nextAttribute = restartAttribute;
325 }
326 boolean abandon = stateId != null && StringUtils.hasText(restartAttribute) && !restartAttribute.equals(stateId);
327 String exitCodeAttribute = transitionElement.getAttribute(EXIT_CODE_ATTR);
328
329 return createTransition(status, onAttribute, nextAttribute, exitCodeAttribute, stateDef, parserContext, abandon);
330 }
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347 private static Collection<BeanDefinition> createTransition(FlowExecutionStatus status, String on, String next,
348 String exitCode, BeanDefinition stateDef, ParserContext parserContext, boolean abandon) {
349
350 BeanDefinition endState = null;
351
352 if (status.isEnd()) {
353
354 BeanDefinitionBuilder endBuilder = BeanDefinitionBuilder
355 .genericBeanDefinition("org.springframework.batch.core.job.flow.support.state.EndState");
356
357 boolean exitCodeExists = StringUtils.hasText(exitCode);
358
359 endBuilder.addConstructorArgValue(status);
360
361 endBuilder.addConstructorArgValue(exitCodeExists ? exitCode : status.getName());
362
363 String endName = (status == FlowExecutionStatus.STOPPED ? STOP_ELE
364 : status == FlowExecutionStatus.FAILED ? FAIL_ELE : END_ELE)
365 + (endCounter++);
366 endBuilder.addConstructorArgValue(endName);
367
368 endBuilder.addConstructorArgValue(abandon);
369
370 String nextOnEnd = exitCodeExists ? null : next;
371 endState = getStateTransitionReference(parserContext, endBuilder.getBeanDefinition(), null, nextOnEnd);
372 next = endName;
373
374 }
375
376 Collection<BeanDefinition> list = new ArrayList<BeanDefinition>();
377 list.add(getStateTransitionReference(parserContext, stateDef, on, next));
378 if (endState != null) {
379
380
381
382
383 list.add(endState);
384 }
385 return list;
386 }
387
388
389
390
391
392 private static FlowExecutionStatus getBatchStatusFromEndTransitionName(String elementName) {
393 elementName = stripNamespace(elementName);
394 if (STOP_ELE.equals(elementName)) {
395 return FlowExecutionStatus.STOPPED;
396 }
397 else if (END_ELE.equals(elementName)) {
398 return FlowExecutionStatus.COMPLETED;
399 }
400 else if (FAIL_ELE.equals(elementName)) {
401 return FlowExecutionStatus.FAILED;
402 }
403 else {
404 return FlowExecutionStatus.UNKNOWN;
405 }
406 }
407
408
409
410
411 private static String stripNamespace(String elementName){
412 if(elementName.startsWith("batch:")){
413 return elementName.substring(6);
414 }
415 else{
416 return elementName;
417 }
418 }
419
420
421
422
423
424
425
426
427
428 public static BeanDefinition getStateTransitionReference(ParserContext parserContext,
429 BeanDefinition stateDefinition, String on, String next) {
430
431 BeanDefinitionBuilder nextBuilder = BeanDefinitionBuilder
432 .genericBeanDefinition("org.springframework.batch.core.job.flow.support.StateTransition");
433 nextBuilder.addConstructorArgValue(stateDefinition);
434
435 if (StringUtils.hasText(on)) {
436 nextBuilder.addConstructorArgValue(on);
437 }
438
439 if (StringUtils.hasText(next)) {
440 nextBuilder.setFactoryMethod("createStateTransition");
441 nextBuilder.addConstructorArgValue(next);
442 }
443 else {
444 nextBuilder.setFactoryMethod("createEndStateTransition");
445 }
446
447 return nextBuilder.getBeanDefinition();
448
449 }
450
451 }