EMMA Coverage Report (generated Thu Jan 24 13:37:04 CST 2013)
[all classes][org.springframework.batch.core.configuration.xml]

COVERAGE SUMMARY FOR SOURCE FILE [AbstractFlowParser.java]

nameclass, %method, %block, %line, %
AbstractFlowParser.java100% (1/1)100% (15/15)91%  (677/743)97%  (138.7/143)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class AbstractFlowParser100% (1/1)100% (15/15)91%  (677/743)97%  (138.7/143)
verifyUniquePattern (Element, List, Element, ParserContext): void 100% (1/1)48%  (13/27)80%  (4/5)
doParse (Element, ParserContext, BeanDefinitionBuilder): void 100% (1/1)86%  (201/234)94%  (37.7/40)
getNextElements (ParserContext, String, BeanDefinition, Element): Collection 100% (1/1)88%  (134/153)95%  (20/21)
<static initializer> 100% (1/1)100% (15/15)100% (4/4)
AbstractFlowParser (): void 100% (1/1)100% (3/3)100% (1/1)
createTransition (FlowExecutionStatus, String, String, String, BeanDefinition... 100% (1/1)100% (91/91)100% (17/17)
findAllReachableElements (String, Map, Set): void 100% (1/1)100% (32/32)100% (7/7)
findReachableElements (Element): Set 100% (1/1)100% (63/63)100% (15/15)
getBatchStatusFromEndTransitionName (String): FlowExecutionStatus 100% (1/1)100% (23/23)100% (8/8)
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% (9/9)
parseTransitionElement (Element, String, BeanDefinition, ParserContext): Coll... 100% (1/1)100% (47/47)100% (9/9)
setJobFactoryRef (String): void 100% (1/1)100% (4/4)100% (2/2)
stripNamespace (String): String 100% (1/1)100% (10/10)100% (3/3)

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

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