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 | */ |
16 | package org.springframework.batch.core.configuration.xml; |
17 | |
18 | import java.util.Arrays; |
19 | import java.util.List; |
20 | |
21 | import org.springframework.beans.BeanMetadataElement; |
22 | import org.springframework.beans.factory.config.BeanDefinition; |
23 | import org.springframework.beans.factory.config.BeanDefinitionHolder; |
24 | import org.springframework.beans.factory.config.RuntimeBeanReference; |
25 | import org.springframework.beans.factory.parsing.CompositeComponentDefinition; |
26 | import org.springframework.beans.factory.support.BeanDefinitionBuilder; |
27 | import org.springframework.beans.factory.support.ManagedList; |
28 | import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; |
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 | |
34 | /** |
35 | * Parser for the lt;job/gt; element in the Batch namespace. Sets up and returns |
36 | * a bean definition for a {@link org.springframework.batch.core.Job}. |
37 | * |
38 | * @author Dave Syer |
39 | * |
40 | */ |
41 | public class JobParser extends AbstractSingleBeanDefinitionParser { |
42 | |
43 | private static final String MERGE_ATTR = "merge"; |
44 | |
45 | private static final String REF_ATTR = "ref"; |
46 | |
47 | private static final String BEAN_ELE = "bean"; |
48 | |
49 | private static final String REF_ELE = "ref"; |
50 | |
51 | private static final JobExecutionListenerParser jobListenerParser = new JobExecutionListenerParser(); |
52 | |
53 | @Override |
54 | protected Class<JobParserJobFactoryBean> getBeanClass(Element element) { |
55 | return JobParserJobFactoryBean.class; |
56 | } |
57 | |
58 | /** |
59 | * Create a bean definition for a |
60 | * {@link org.springframework.batch.core.job.flow.FlowJob}. Nested step |
61 | * elements are delegated to an {@link InlineStepParser}. |
62 | * |
63 | * @see AbstractSingleBeanDefinitionParser#doParse(Element, ParserContext, |
64 | * BeanDefinitionBuilder) |
65 | */ |
66 | @SuppressWarnings("unchecked") |
67 | @Override |
68 | protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { |
69 | |
70 | if (!CoreNamespaceUtils.namespaceMatchesVersion(element)) { |
71 | parserContext.getReaderContext().error( |
72 | "You are using a version of the spring-batch XSD that is not compatible with Spring Batch 3.0." + |
73 | " Please upgrade your schema declarations (or use the spring-batch.xsd alias if you are " + |
74 | "feeling lucky).", element); |
75 | return; |
76 | } |
77 | |
78 | CoreNamespaceUtils.autoregisterBeansForNamespace(parserContext, parserContext.extractSource(element)); |
79 | |
80 | String jobName = element.getAttribute("id"); |
81 | builder.addConstructorArgValue(jobName); |
82 | |
83 | boolean isAbstract = CoreNamespaceUtils.isAbstract(element); |
84 | builder.setAbstract(isAbstract); |
85 | |
86 | String parentRef = element.getAttribute("parent"); |
87 | if (StringUtils.hasText(parentRef)) { |
88 | builder.setParentName(parentRef); |
89 | } |
90 | |
91 | String repositoryAttribute = element.getAttribute("job-repository"); |
92 | if (StringUtils.hasText(repositoryAttribute)) { |
93 | builder.addPropertyReference("jobRepository", repositoryAttribute); |
94 | } |
95 | |
96 | Element validator = DomUtils.getChildElementByTagName(element, "validator"); |
97 | if (validator != null) { |
98 | builder.addPropertyValue("jobParametersValidator", parseBeanElement(validator, parserContext)); |
99 | } |
100 | |
101 | String restartableAttribute = element.getAttribute("restartable"); |
102 | if (StringUtils.hasText(restartableAttribute)) { |
103 | builder.addPropertyValue("restartable", restartableAttribute); |
104 | } |
105 | |
106 | String incrementer = (element.getAttribute("incrementer")); |
107 | if (StringUtils.hasText(incrementer)) { |
108 | builder.addPropertyReference("jobParametersIncrementer", incrementer); |
109 | } |
110 | |
111 | if (isAbstract) { |
112 | for (String tagName : Arrays.asList("step", "decision", "split")) { |
113 | if (!DomUtils.getChildElementsByTagName(element, tagName).isEmpty()) { |
114 | parserContext.getReaderContext().error( |
115 | "The <" + tagName + "/> element may not appear on a <job/> with abstract=\"true\" [" |
116 | + jobName + "]", element); |
117 | } |
118 | } |
119 | } |
120 | else { |
121 | InlineFlowParser flowParser = new InlineFlowParser(jobName, jobName); |
122 | BeanDefinition flowDef = flowParser.parse(element, parserContext); |
123 | builder.addPropertyValue("flow", flowDef); |
124 | } |
125 | |
126 | Element description = DomUtils.getChildElementByTagName(element, "description"); |
127 | if (description != null) { |
128 | builder.getBeanDefinition().setDescription(description.getTextContent()); |
129 | } |
130 | |
131 | List<Element> listenersElements = DomUtils.getChildElementsByTagName(element, "listeners"); |
132 | if (listenersElements.size() == 1) { |
133 | Element listenersElement = listenersElements.get(0); |
134 | CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(listenersElement.getTagName(), |
135 | parserContext.extractSource(element)); |
136 | parserContext.pushContainingComponent(compositeDef); |
137 | ManagedList listeners = new ManagedList(); |
138 | listeners.setMergeEnabled(listenersElement.hasAttribute(MERGE_ATTR) |
139 | && Boolean.valueOf(listenersElement.getAttribute(MERGE_ATTR))); |
140 | List<Element> listenerElements = DomUtils.getChildElementsByTagName(listenersElement, "listener"); |
141 | for (Element listenerElement : listenerElements) { |
142 | listeners.add(jobListenerParser.parse(listenerElement, parserContext)); |
143 | } |
144 | builder.addPropertyValue("jobExecutionListeners", listeners); |
145 | parserContext.popAndRegisterContainingComponent(); |
146 | } |
147 | else if (listenersElements.size() > 1) { |
148 | parserContext.getReaderContext().error( |
149 | "The '<listeners/>' element may not appear more than once in a single <job/>.", element); |
150 | } |
151 | |
152 | } |
153 | |
154 | public BeanMetadataElement parseBeanElement(Element element, ParserContext parserContext) { |
155 | String refAttribute = element.getAttribute(REF_ATTR); |
156 | Element beanElement = DomUtils.getChildElementByTagName(element, BEAN_ELE); |
157 | Element refElement = DomUtils.getChildElementByTagName(element, REF_ELE); |
158 | |
159 | if (StringUtils.hasText(refAttribute)) { |
160 | return new RuntimeBeanReference(refAttribute); |
161 | } |
162 | else if (beanElement != null) { |
163 | BeanDefinitionHolder beanDefinitionHolder = parserContext.getDelegate().parseBeanDefinitionElement( |
164 | beanElement); |
165 | parserContext.getDelegate().decorateBeanDefinitionIfRequired(beanElement, beanDefinitionHolder); |
166 | return beanDefinitionHolder; |
167 | } |
168 | else if (refElement != null) { |
169 | return (BeanMetadataElement) parserContext.getDelegate().parsePropertySubElement(refElement, null); |
170 | } |
171 | |
172 | parserContext.getReaderContext().error( |
173 | "One of ref attribute or a nested bean definition or ref element must be specified", element); |
174 | return null; |
175 | } |
176 | |
177 | } |