EMMA Coverage Report (generated Thu May 22 12:08:10 CDT 2014)
[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%  (141.6/146)

COVERAGE BREAKDOWN BY CLASS AND METHOD

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

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