EMMA Coverage Report (generated Fri Aug 21 15:59:46 BST 2009)
[all classes][org.springframework.batch.core.configuration.xml]

COVERAGE SUMMARY FOR SOURCE FILE [FlowParser.java]

nameclass, %method, %block, %line, %
FlowParser.java100% (1/1)100% (13/13)91%  (643/706)94%  (142.3/151)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class FlowParser100% (1/1)100% (13/13)91%  (643/706)94%  (142.3/151)
verifyUniquePattern (Element, List, Element, ParserContext): void 100% (1/1)50%  (13/26)67%  (4/6)
doParse (Element, ParserContext, BeanDefinitionBuilder): void 100% (1/1)85%  (188/220)91%  (39.3/43)
getNextElements (ParserContext, String, BeanDefinition, Element): Collection 100% (1/1)87%  (122/140)88%  (22/25)
<static initializer> 100% (1/1)100% (11/11)100% (4/4)
FlowParser (String, String): void 100% (1/1)100% (9/9)100% (4/4)
createTransition (FlowExecutionStatus, String, String, String, BeanDefinition... 100% (1/1)100% (97/97)100% (21/21)
findAllReachableElements (String, Map, Set): void 100% (1/1)100% (32/32)100% (7/7)
findReachableElements (Element): Set 100% (1/1)100% (63/63)100% (13/13)
getBatchStatusFromEndTransitionName (String): FlowExecutionStatus 100% (1/1)100% (20/20)100% (7/7)
getBeanClass (Element): Class 100% (1/1)100% (2/2)100% (1/1)
getNextElements (ParserContext, BeanDefinition, Element): Collection 100% (1/1)100% (6/6)100% (1/1)
getStateTransitionReference (ParserContext, BeanDefinition, String, String): ... 100% (1/1)100% (33/33)100% (10/10)
parseTransitionElement (Element, String, BeanDefinition, ParserContext): Coll... 100% (1/1)100% (47/47)100% (9/9)

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

[all classes][org.springframework.batch.core.configuration.xml]
EMMA 2.0.5312 (C) Vladimir Roubtsov