001/*
002 * Copyright 2013-2014 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.springframework.batch.core.jsr.configuration.xml;
017
018import java.util.List;
019
020import org.springframework.batch.core.configuration.xml.ExceptionElementParser;
021import org.springframework.batch.core.jsr.configuration.support.BatchArtifactType;
022import org.springframework.batch.core.step.item.ChunkOrientedTasklet;
023import org.springframework.batch.item.ItemProcessor;
024import org.springframework.batch.item.ItemReader;
025import org.springframework.batch.item.ItemWriter;
026import org.springframework.beans.MutablePropertyValues;
027import org.springframework.beans.factory.config.RuntimeBeanReference;
028import org.springframework.beans.factory.config.TypedStringValue;
029import org.springframework.beans.factory.support.AbstractBeanDefinition;
030import org.springframework.beans.factory.support.ManagedList;
031import org.springframework.beans.factory.support.ManagedMap;
032import org.springframework.beans.factory.xml.ParserContext;
033import org.springframework.util.StringUtils;
034import org.springframework.util.xml.DomUtils;
035import org.w3c.dom.Element;
036import org.w3c.dom.Node;
037import org.w3c.dom.NodeList;
038
039/**
040 * Parser for the <chunk /> element as specified in JSR-352.  The current state
041 * parses a chunk element into it's related batch artifacts ({@link ChunkOrientedTasklet}, {@link ItemReader},
042 * {@link ItemProcessor}, and {@link ItemWriter}).
043 *
044 * @author Michael Minella
045 * @author Chris Schaefer
046 * @since 3.0
047 *
048 */
049public class ChunkParser {
050        private static final String TIME_LIMIT_ATTRIBUTE = "time-limit";
051        private static final String ITEM_COUNT_ATTRIBUTE = "item-count";
052        private static final String CHECKPOINT_ALGORITHM_ELEMENT = "checkpoint-algorithm";
053        private static final String CLASS_ATTRIBUTE = "class";
054        private static final String INCLUDE_ELEMENT = "include";
055        private static final String NO_ROLLBACK_EXCEPTION_CLASSES_ELEMENT = "no-rollback-exception-classes";
056        private static final String RETRYABLE_EXCEPTION_CLASSES_ELEMENT = "retryable-exception-classes";
057        private static final String SKIPPABLE_EXCEPTION_CLASSES_ELEMENT = "skippable-exception-classes";
058        private static final String WRITER_ELEMENT = "writer";
059        private static final String PROCESSOR_ELEMENT = "processor";
060        private static final String READER_ELEMENT = "reader";
061        private static final String REF_ATTRIBUTE = "ref";
062        private static final String RETRY_LIMIT_ATTRIBUTE = "retry-limit";
063        private static final String SKIP_LIMIT_ATTRIBUTE = "skip-limit";
064        private static final String CUSTOM_CHECKPOINT_POLICY = "custom";
065        private static final String ITEM_CHECKPOINT_POLICY = "item";
066        private static final String CHECKPOINT_POLICY_ATTRIBUTE = "checkpoint-policy";
067
068        public void parse(Element element, AbstractBeanDefinition bd, ParserContext parserContext, String stepName) {
069                MutablePropertyValues propertyValues = bd.getPropertyValues();
070                bd.setBeanClass(StepFactoryBean.class);
071                bd.setAttribute("isNamespaceStep", false);
072
073                propertyValues.addPropertyValue("hasChunkElement", Boolean.TRUE);
074
075                String checkpointPolicy = element.getAttribute(CHECKPOINT_POLICY_ATTRIBUTE);
076                if(StringUtils.hasText(checkpointPolicy)) {
077                        if(checkpointPolicy.equals(ITEM_CHECKPOINT_POLICY)) {
078                                String itemCount = element.getAttribute(ITEM_COUNT_ATTRIBUTE);
079                                if (StringUtils.hasText(itemCount)) {
080                                        propertyValues.addPropertyValue("commitInterval", itemCount);
081                                } else {
082                                        propertyValues.addPropertyValue("commitInterval", "10");
083                                }
084
085                                parseSimpleAttribute(element, propertyValues, TIME_LIMIT_ATTRIBUTE, "timeout");
086                        } else if(checkpointPolicy.equals(CUSTOM_CHECKPOINT_POLICY)) {
087                                parseCustomCheckpointAlgorithm(element, parserContext, propertyValues, stepName);
088                        }
089                } else {
090                        String itemCount = element.getAttribute(ITEM_COUNT_ATTRIBUTE);
091                        if (StringUtils.hasText(itemCount)) {
092                                propertyValues.addPropertyValue("commitInterval", itemCount);
093                        } else {
094                                propertyValues.addPropertyValue("commitInterval", "10");
095                        }
096
097                        parseSimpleAttribute(element, propertyValues, TIME_LIMIT_ATTRIBUTE, "timeout");
098                }
099
100                parseSimpleAttribute(element, propertyValues, SKIP_LIMIT_ATTRIBUTE, "skipLimit");
101                parseSimpleAttribute(element, propertyValues, RETRY_LIMIT_ATTRIBUTE, "retryLimit");
102
103                NodeList children = element.getChildNodes();
104                for (int i = 0; i < children.getLength(); i++) {
105                        Node nd = children.item(i);
106
107                        parseChildElement(element, parserContext, propertyValues, nd, stepName);
108                }
109        }
110
111        private void parseSimpleAttribute(Element element,
112                        MutablePropertyValues propertyValues, String attributeName, String propertyName) {
113                String propertyValue = element.getAttribute(attributeName);
114                if (StringUtils.hasText(propertyValue)) {
115                        propertyValues.addPropertyValue(propertyName, propertyValue);
116                }
117        }
118
119        private void parseChildElement(Element element, ParserContext parserContext,
120                        MutablePropertyValues propertyValues, Node nd, String stepName) {
121                if (nd instanceof Element) {
122                        Element nestedElement = (Element) nd;
123                        String name = nestedElement.getLocalName();
124                        String artifactName = nestedElement.getAttribute(REF_ATTRIBUTE);
125
126                        if(name.equals(READER_ELEMENT)) {
127                                if (StringUtils.hasText(artifactName)) {
128                                        propertyValues.addPropertyValue("stepItemReader", new RuntimeBeanReference(artifactName));
129                                }
130
131                                new PropertyParser(artifactName, parserContext, BatchArtifactType.STEP_ARTIFACT, stepName).parseProperties(nestedElement);
132                        } else if(name.equals(PROCESSOR_ELEMENT)) {
133                                if (StringUtils.hasText(artifactName)) {
134                                        propertyValues.addPropertyValue("stepItemProcessor", new RuntimeBeanReference(artifactName));
135                                }
136
137                                new PropertyParser(artifactName, parserContext, BatchArtifactType.STEP_ARTIFACT, stepName).parseProperties(nestedElement);
138                        } else if(name.equals(WRITER_ELEMENT)) {
139                                if (StringUtils.hasText(artifactName)) {
140                                        propertyValues.addPropertyValue("stepItemWriter", new RuntimeBeanReference(artifactName));
141                                }
142
143                                new PropertyParser(artifactName, parserContext, BatchArtifactType.STEP_ARTIFACT, stepName).parseProperties(nestedElement);
144                        } else if(name.equals(SKIPPABLE_EXCEPTION_CLASSES_ELEMENT)) {
145                                ManagedMap<TypedStringValue, Boolean> exceptionClasses = new ExceptionElementParser().parse(element, parserContext, SKIPPABLE_EXCEPTION_CLASSES_ELEMENT);
146                                if(exceptionClasses != null) {
147                                        propertyValues.addPropertyValue("skippableExceptionClasses", exceptionClasses);
148                                }
149                        } else if(name.equals(RETRYABLE_EXCEPTION_CLASSES_ELEMENT)) {
150                                ManagedMap<TypedStringValue, Boolean> exceptionClasses = new ExceptionElementParser().parse(element, parserContext, RETRYABLE_EXCEPTION_CLASSES_ELEMENT);
151                                if(exceptionClasses != null) {
152                                        propertyValues.addPropertyValue("retryableExceptionClasses", exceptionClasses);
153                                }
154                        } else if(name.equals(NO_ROLLBACK_EXCEPTION_CLASSES_ELEMENT)) {
155                                //TODO: Update to support excludes
156                                ManagedList<TypedStringValue> list = new ManagedList<TypedStringValue>();
157
158                                for (Element child : DomUtils.getChildElementsByTagName(nestedElement, INCLUDE_ELEMENT)) {
159                                        String className = child.getAttribute(CLASS_ATTRIBUTE);
160                                        list.add(new TypedStringValue(className, Class.class));
161                                }
162
163                                propertyValues.addPropertyValue("noRollbackExceptionClasses", list);
164                        }
165                }
166        }
167
168        private void parseCustomCheckpointAlgorithm(Element element, ParserContext parserContext, MutablePropertyValues propertyValues, String stepName) {
169                List<Element> elements = DomUtils.getChildElementsByTagName(element, CHECKPOINT_ALGORITHM_ELEMENT);
170
171                if(elements.size() == 1) {
172                        Element checkpointAlgorithmElement = elements.get(0);
173
174                        String name = checkpointAlgorithmElement.getAttribute(REF_ATTRIBUTE);
175                        if(StringUtils.hasText(name)) {
176                                propertyValues.addPropertyValue("stepChunkCompletionPolicy", new RuntimeBeanReference(name));
177                        }
178
179                        new PropertyParser(name, parserContext, BatchArtifactType.STEP_ARTIFACT, stepName).parseProperties(checkpointAlgorithmElement);
180                } else if(elements.size() > 1){
181                        parserContext.getReaderContext().error(
182                                        "The <checkpoint-algorithm/> element may not appear more than once in a single <"
183                                                        + element.getNodeName() + "/>.", element);
184                }
185        }
186}