1 | /* |
2 | * Copyright 2006-2009 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 | */ |
16 | package org.springframework.batch.core.configuration.xml; |
17 | |
18 | import static org.springframework.batch.core.configuration.xml.AbstractStepParser.handleExceptionElement; |
19 | |
20 | import java.util.List; |
21 | |
22 | import org.springframework.beans.MutablePropertyValues; |
23 | import org.springframework.beans.factory.config.BeanReference; |
24 | import org.springframework.beans.factory.config.RuntimeBeanReference; |
25 | import org.springframework.beans.factory.parsing.CompositeComponentDefinition; |
26 | import org.springframework.beans.factory.support.AbstractBeanDefinition; |
27 | import org.springframework.beans.factory.support.ManagedList; |
28 | import org.springframework.beans.factory.support.RootBeanDefinition; |
29 | import org.springframework.beans.factory.xml.ParserContext; |
30 | import org.springframework.util.StringUtils; |
31 | import org.springframework.util.xml.DomUtils; |
32 | import org.w3c.dom.Element; |
33 | import org.w3c.dom.NamedNodeMap; |
34 | |
35 | /** |
36 | * Internal parser for the <chunk/> element inside a step. |
37 | * |
38 | * @author Thomas Risberg |
39 | * @since 2.0 |
40 | */ |
41 | public class ChunkElementParser { |
42 | |
43 | private static final String ID_ATTR = "id"; |
44 | |
45 | private static final String REF_ATTR = "ref"; |
46 | |
47 | private static final String CLASS_ATTR = "class"; |
48 | |
49 | private static final String MERGE_ATTR = "merge"; |
50 | |
51 | private static final String COMMIT_INTERVAL_ATTR = "commit-interval"; |
52 | |
53 | private static final String CHUNK_COMPLETION_POLICY_ATTR = "chunk-completion-policy"; |
54 | |
55 | /** |
56 | * @param element |
57 | * @param parserContext |
58 | */ |
59 | protected void parse(Element element, AbstractBeanDefinition bd, ParserContext parserContext, boolean underspecified) { |
60 | |
61 | MutablePropertyValues propertyValues = bd.getPropertyValues(); |
62 | |
63 | propertyValues.addPropertyValue("hasChunkElement", Boolean.TRUE); |
64 | |
65 | String readerBeanId = element.getAttribute("reader"); |
66 | if (StringUtils.hasText(readerBeanId)) { |
67 | RuntimeBeanReference readerRef = new RuntimeBeanReference(readerBeanId); |
68 | propertyValues.addPropertyValue("itemReader", readerRef); |
69 | } |
70 | |
71 | String processorBeanId = element.getAttribute("processor"); |
72 | if (StringUtils.hasText(processorBeanId)) { |
73 | RuntimeBeanReference processorRef = new RuntimeBeanReference(processorBeanId); |
74 | propertyValues.addPropertyValue("itemProcessor", processorRef); |
75 | } |
76 | |
77 | String writerBeanId = element.getAttribute("writer"); |
78 | if (StringUtils.hasText(writerBeanId)) { |
79 | RuntimeBeanReference writerRef = new RuntimeBeanReference(writerBeanId); |
80 | propertyValues.addPropertyValue("itemWriter", writerRef); |
81 | } |
82 | |
83 | String taskExecutorBeanId = element.getAttribute("task-executor"); |
84 | if (StringUtils.hasText(taskExecutorBeanId)) { |
85 | RuntimeBeanReference taskExecutorRef = new RuntimeBeanReference(taskExecutorBeanId); |
86 | propertyValues.addPropertyValue("taskExecutor", taskExecutorRef); |
87 | } |
88 | |
89 | String commitInterval = element.getAttribute(COMMIT_INTERVAL_ATTR); |
90 | if (StringUtils.hasText(commitInterval)) { |
91 | propertyValues.addPropertyValue("commitInterval", commitInterval); |
92 | } |
93 | |
94 | String completionPolicyRef = element.getAttribute(CHUNK_COMPLETION_POLICY_ATTR); |
95 | if (StringUtils.hasText(completionPolicyRef)) { |
96 | RuntimeBeanReference completionPolicy = new RuntimeBeanReference(completionPolicyRef); |
97 | propertyValues.addPropertyValue("chunkCompletionPolicy", completionPolicy); |
98 | } |
99 | |
100 | if (!underspecified |
101 | && propertyValues.contains("commitInterval") == propertyValues.contains("chunkCompletionPolicy")) { |
102 | if (propertyValues.contains("commitInterval")) { |
103 | parserContext.getReaderContext().error( |
104 | "The <" + element.getNodeName() + "/> element must contain either '" + COMMIT_INTERVAL_ATTR |
105 | + "' " + "or '" + CHUNK_COMPLETION_POLICY_ATTR + "', but not both.", element); |
106 | } |
107 | else { |
108 | parserContext.getReaderContext().error( |
109 | "The <" + element.getNodeName() + "/> element must contain either '" + COMMIT_INTERVAL_ATTR |
110 | + "' " + "or '" + CHUNK_COMPLETION_POLICY_ATTR + "'.", element); |
111 | |
112 | } |
113 | } |
114 | |
115 | String skipLimit = element.getAttribute("skip-limit"); |
116 | if (StringUtils.hasText(skipLimit)) { |
117 | propertyValues.addPropertyValue("skipLimit", skipLimit); |
118 | } |
119 | |
120 | String retryLimit = element.getAttribute("retry-limit"); |
121 | if (StringUtils.hasText(retryLimit)) { |
122 | propertyValues.addPropertyValue("retryLimit", retryLimit); |
123 | } |
124 | |
125 | String cacheCapacity = element.getAttribute("cache-capacity"); |
126 | if (StringUtils.hasText(cacheCapacity)) { |
127 | propertyValues.addPropertyValue("cacheCapacity", cacheCapacity); |
128 | } |
129 | |
130 | String isReaderTransactionalQueue = element.getAttribute("is-reader-transactional-queue"); |
131 | if (StringUtils.hasText(isReaderTransactionalQueue)) { |
132 | propertyValues.addPropertyValue("isReaderTransactionalQueue", isReaderTransactionalQueue); |
133 | } |
134 | |
135 | handleExceptionElement(element, parserContext, propertyValues, "skippable-exception-classes", |
136 | "skippableExceptionClasses"); |
137 | |
138 | handleExceptionElement(element, parserContext, propertyValues, "retryable-exception-classes", |
139 | "retryableExceptionClasses"); |
140 | |
141 | handleExceptionElement(element, parserContext, propertyValues, "fatal-exception-classes", |
142 | "fatalExceptionClasses"); |
143 | |
144 | handleRetryListenersElement(element, propertyValues, parserContext); |
145 | |
146 | handleStreamsElement(element, propertyValues, parserContext); |
147 | |
148 | } |
149 | |
150 | private void handleRetryListenersElement(Element element, MutablePropertyValues propertyValues, |
151 | ParserContext parserContext) { |
152 | Element listenersElement = DomUtils.getChildElementByTagName(element, "retry-listeners"); |
153 | if (listenersElement != null) { |
154 | CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(listenersElement.getTagName(), |
155 | parserContext.extractSource(element)); |
156 | parserContext.pushContainingComponent(compositeDef); |
157 | ManagedList retryListenerBeans = new ManagedList(); |
158 | retryListenerBeans.setMergeEnabled(listenersElement.hasAttribute(MERGE_ATTR) |
159 | && Boolean.valueOf(listenersElement.getAttribute(MERGE_ATTR))); |
160 | handleRetryListenerElements(parserContext, listenersElement, retryListenerBeans); |
161 | propertyValues.addPropertyValue("retryListeners", retryListenerBeans); |
162 | parserContext.popAndRegisterContainingComponent(); |
163 | } |
164 | } |
165 | |
166 | @SuppressWarnings("unchecked") |
167 | private void handleRetryListenerElements(ParserContext parserContext, Element element, ManagedList beans) { |
168 | List<Element> listenerElements = DomUtils.getChildElementsByTagName(element, "listener"); |
169 | if (listenerElements != null) { |
170 | for (Element listenerElement : listenerElements) { |
171 | String id = listenerElement.getAttribute(ID_ATTR); |
172 | String listenerRef = listenerElement.getAttribute(REF_ATTR); |
173 | String className = listenerElement.getAttribute(CLASS_ATTR); |
174 | checkListenerElementAttributes(parserContext, element, listenerElement, id, listenerRef, className); |
175 | if (StringUtils.hasText(listenerRef)) { |
176 | BeanReference bean = new RuntimeBeanReference(listenerRef); |
177 | beans.add(bean); |
178 | } |
179 | else if (StringUtils.hasText(className)) { |
180 | RootBeanDefinition beanDef = new RootBeanDefinition(className, null, null); |
181 | if (!StringUtils.hasText(id)) { |
182 | id = parserContext.getReaderContext().generateBeanName(beanDef); |
183 | } |
184 | beans.add(beanDef); |
185 | } |
186 | else { |
187 | parserContext.getReaderContext().error( |
188 | "Neither '" + REF_ATTR + "' or '" + CLASS_ATTR + "' specified for <" |
189 | + listenerElement.getTagName() + "> element", element); |
190 | } |
191 | } |
192 | } |
193 | } |
194 | |
195 | private void checkListenerElementAttributes(ParserContext parserContext, Element element, Element listenerElement, |
196 | String id, String listenerRef, String className) { |
197 | if (StringUtils.hasText(className) && StringUtils.hasText(listenerRef)) { |
198 | NamedNodeMap attributeNodes = listenerElement.getAttributes(); |
199 | StringBuilder attributes = new StringBuilder(); |
200 | for (int i = 0; i < attributeNodes.getLength(); i++) { |
201 | if (i > 0) { |
202 | attributes.append(" "); |
203 | } |
204 | attributes.append(attributeNodes.item(i)); |
205 | } |
206 | parserContext.getReaderContext().error( |
207 | "Both '" + REF_ATTR + "' and '" + CLASS_ATTR + "' specified; use '" + CLASS_ATTR |
208 | + "' with an optional '" + ID_ATTR + "' or just '" + REF_ATTR + "' for <" |
209 | + listenerElement.getTagName() + "> element specified with attributes: " + attributes, |
210 | element); |
211 | } |
212 | } |
213 | |
214 | @SuppressWarnings("unchecked") |
215 | private void handleStreamsElement(Element element, MutablePropertyValues propertyValues, ParserContext parserContext) { |
216 | Element streamsElement = DomUtils.getChildElementByTagName(element, "streams"); |
217 | if (streamsElement != null) { |
218 | ManagedList streamBeans = new ManagedList(); |
219 | streamBeans.setMergeEnabled(streamsElement.hasAttribute(MERGE_ATTR) |
220 | && Boolean.valueOf(streamsElement.getAttribute(MERGE_ATTR))); |
221 | List<Element> streamElements = DomUtils.getChildElementsByTagName(streamsElement, "stream"); |
222 | if (streamElements != null) { |
223 | for (Element streamElement : streamElements) { |
224 | String streamRef = streamElement.getAttribute(REF_ATTR); |
225 | if (StringUtils.hasText(streamRef)) { |
226 | BeanReference bean = new RuntimeBeanReference(streamRef); |
227 | streamBeans.add(bean); |
228 | } |
229 | else { |
230 | parserContext.getReaderContext().error( |
231 | REF_ATTR + " not specified for <" + streamElement.getTagName() + "> element", element); |
232 | } |
233 | } |
234 | } |
235 | propertyValues.addPropertyValue("streams", streamBeans); |
236 | } |
237 | } |
238 | |
239 | } |