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}