001/*
002 * Copyright 2006-2009 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.configuration.xml;
017
018import org.w3c.dom.Element;
019import org.w3c.dom.Node;
020import org.w3c.dom.NodeList;
021
022import org.springframework.batch.core.listener.StepListenerMetaData;
023import org.springframework.beans.MutablePropertyValues;
024import org.springframework.beans.factory.config.BeanDefinition;
025import org.springframework.beans.factory.config.BeanDefinitionHolder;
026import org.springframework.beans.factory.config.RuntimeBeanReference;
027import org.springframework.beans.factory.config.TypedStringValue;
028import org.springframework.beans.factory.support.AbstractBeanDefinition;
029import org.springframework.beans.factory.support.BeanDefinitionBuilder;
030import org.springframework.beans.factory.support.GenericBeanDefinition;
031import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
032import org.springframework.beans.factory.xml.ParserContext;
033import org.springframework.util.StringUtils;
034import org.springframework.util.xml.DomUtils;
035
036/**
037 * Internal parser for the <step/> elements inside a job. A step element
038 * references a bean definition for a
039 * {@link org.springframework.batch.core.Step} and goes on to (optionally) list
040 * a set of transitions from that step to others with <next on="pattern"
041 * to="stepName"/>. Used by the {@link JobParser}.
042 *
043 * @author Dave Syer
044 * @author Thomas Risberg
045 * @author Josh Long
046 * @see JobParser
047 * @since 2.0
048 */
049public abstract class AbstractStepParser {
050
051        protected static final String ID_ATTR = "id";
052
053        private static final String PARENT_ATTR = "parent";
054
055        private static final String REF_ATTR = "ref";
056        
057        private static final String ALLOW_START_ATTR = "allow-start-if-complete";
058
059        private static final String TASKLET_ELE = "tasklet";
060
061        private static final String PARTITION_ELE = "partition";
062
063        private static final String JOB_ELE = "job";
064
065        private static final String JOB_PARAMS_EXTRACTOR_ATTR = "job-parameters-extractor";
066
067        private static final String JOB_LAUNCHER_ATTR = "job-launcher";
068
069        private static final String STEP_ATTR = "step";
070
071        private static final String STEP_ELE = STEP_ATTR;
072
073        private static final String PARTITIONER_ATTR = "partitioner";
074
075        private static final String AGGREGATOR_ATTR = "aggregator";
076
077        private static final String HANDLER_ATTR = "handler";
078
079        private static final String HANDLER_ELE = "handler";
080
081        private static final String TASK_EXECUTOR_ATTR = "task-executor";
082
083        private static final String GRID_SIZE_ATTR = "grid-size";
084
085        private static final String FLOW_ELE = "flow";
086
087        private static final String JOB_REPO_ATTR = "job-repository";
088
089        private static final StepListenerParser stepListenerParser = new StepListenerParser(StepListenerMetaData.stepExecutionListenerMetaData());
090
091        /**
092         * @param stepElement   The <step/> element
093         * @param parserContext instance of {@link ParserContext}.
094         * @param jobFactoryRef the reference to the {@link JobParserJobFactoryBean}
095         *                      from the enclosing tag. Use 'null' if unknown.
096         * @return {@link AbstractBeanDefinition} for the stepElement.
097         */
098        protected AbstractBeanDefinition parseStep(Element stepElement, ParserContext parserContext, String jobFactoryRef) {
099
100                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
101                AbstractBeanDefinition bd = builder.getRawBeanDefinition();
102
103                // look at all nested elements
104                NodeList children = stepElement.getChildNodes();
105                
106                for (int i = 0; i < children.getLength(); i++) {
107                        Node nd = children.item(i);
108
109                        if (nd instanceof Element) {
110                                Element nestedElement = (Element) nd;
111                                String name = nestedElement.getLocalName();
112
113                                if (TASKLET_ELE.equals(name)) {
114                                        boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement);
115                                        new TaskletParser().parseTasklet(stepElement, nestedElement, bd, parserContext, stepUnderspecified);
116                                }
117                                else if (FLOW_ELE.equals(name)) {
118                                        boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement);
119                                        parseFlow(stepElement, nestedElement, bd, parserContext, stepUnderspecified);
120                                }
121                                else if (PARTITION_ELE.equals(name)) {
122                                        boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement);
123                                        parsePartition(stepElement, nestedElement, bd, parserContext, stepUnderspecified, jobFactoryRef);
124                                }
125                                else if (JOB_ELE.equals(name)) {
126                                        boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement);
127                                        parseJob(stepElement, nestedElement, bd, parserContext, stepUnderspecified);
128                                }
129                                else if ("description".equals(name)) {
130                                        bd.setDescription(nestedElement.getTextContent());
131                                }
132
133                                // nested bean reference/declaration
134                                else {
135                                        String ns = nestedElement.getNamespaceURI();
136                                        Object value = null;
137                                        boolean skip = false;
138
139                                        // Spring NS
140                                        if ((ns == null && name.equals(BeanDefinitionParserDelegate.BEAN_ELEMENT))
141                                                        || ns.equals(BeanDefinitionParserDelegate.BEANS_NAMESPACE_URI)) {
142                                                BeanDefinitionHolder holder = parserContext.getDelegate().parseBeanDefinitionElement(nestedElement);
143                                                value = parserContext.getDelegate().decorateBeanDefinitionIfRequired(nestedElement, holder);
144                                        }
145                                        // Spring Batch transitions
146                                        else if (ns.equals("http://www.springframework.org/schema/batch")) {
147                                                // don't parse
148                                                skip = true;
149                                        }
150                                        // Custom NS
151                                        else {
152                                                value = parserContext.getDelegate().parseCustomElement(nestedElement);
153                                        }
154                                        
155                                        if (!skip) {
156                                                bd.setBeanClass(StepParserStepFactoryBean.class);
157                                                bd.setAttribute("isNamespaceStep", true);
158                                                builder.addPropertyValue("tasklet", value);
159                                        }
160                                }
161                        }
162                }
163
164                String parentRef = stepElement.getAttribute(PARENT_ATTR);
165                if (StringUtils.hasText(parentRef)) {
166                        bd.setParentName(parentRef);
167                }
168
169                String isAbstract = stepElement.getAttribute("abstract");
170                if (StringUtils.hasText(isAbstract)) {
171                        bd.setAbstract(Boolean.valueOf(isAbstract));
172                }
173
174                String jobRepositoryRef = stepElement.getAttribute(JOB_REPO_ATTR);
175                if (StringUtils.hasText(jobRepositoryRef)) {
176                        builder.addPropertyReference("jobRepository", jobRepositoryRef);
177                }
178
179                if (StringUtils.hasText(jobFactoryRef)) {
180                        bd.setAttribute("jobParserJobFactoryBeanRef", jobFactoryRef);
181                }
182
183                //add the allow parser here
184                String isAllowStart = stepElement.getAttribute(ALLOW_START_ATTR);
185                if (StringUtils.hasText(isAllowStart)) {
186                        //check if the value is already set from an inner element
187                        if (!bd.getPropertyValues().contains("allowStartIfComplete")) {
188                                //set the value as a property
189                                bd.getPropertyValues().add("allowStartIfComplete", Boolean.valueOf(isAllowStart));
190                        }//end if
191                }
192                
193                stepListenerParser.handleListenersElement(stepElement, bd, parserContext);
194                return bd;
195        }
196
197        private void parsePartition(Element stepElement, Element partitionElement, AbstractBeanDefinition bd, ParserContext parserContext, boolean stepUnderspecified, String jobFactoryRef ) {
198
199                bd.setBeanClass(StepParserStepFactoryBean.class);
200                bd.setAttribute("isNamespaceStep", true);
201                String stepRef = partitionElement.getAttribute(STEP_ATTR);
202                String partitionerRef = partitionElement.getAttribute(PARTITIONER_ATTR);
203                String aggregatorRef = partitionElement.getAttribute(AGGREGATOR_ATTR);
204                String handlerRef = partitionElement.getAttribute(HANDLER_ATTR);
205
206                if (!StringUtils.hasText(partitionerRef)) {
207                        parserContext.getReaderContext().error("You must specify a partitioner", partitionElement);
208                        return;
209                }
210
211                MutablePropertyValues propertyValues = bd.getPropertyValues();
212
213                propertyValues.addPropertyValue("partitioner", new RuntimeBeanReference(partitionerRef));
214                if (StringUtils.hasText(aggregatorRef)) {
215                        propertyValues.addPropertyValue("stepExecutionAggregator", new RuntimeBeanReference(aggregatorRef));                    
216                }
217
218                boolean customHandler = false;
219                if (!StringUtils.hasText(handlerRef)) {
220                        Element handlerElement = DomUtils.getChildElementByTagName(partitionElement, HANDLER_ELE);
221                        if (handlerElement != null) {
222                                String taskExecutorRef = handlerElement.getAttribute(TASK_EXECUTOR_ATTR);
223                                if (StringUtils.hasText(taskExecutorRef)) {
224                                        propertyValues.addPropertyValue("taskExecutor", new RuntimeBeanReference(taskExecutorRef));
225                                }
226                                String gridSize = handlerElement.getAttribute(GRID_SIZE_ATTR);
227                                if (StringUtils.hasText(gridSize)) {
228                                        propertyValues.addPropertyValue("gridSize", new TypedStringValue(gridSize));
229                                }
230                        }
231                } else {
232                        customHandler = true;
233                        BeanDefinition partitionHandler = BeanDefinitionBuilder.genericBeanDefinition().getRawBeanDefinition();
234                        partitionHandler.setParentName(handlerRef);
235                        propertyValues.addPropertyValue("partitionHandler", partitionHandler);
236                }
237
238                Element inlineStepElement = DomUtils.getChildElementByTagName(partitionElement, STEP_ELE);
239                if (inlineStepElement == null && !StringUtils.hasText(stepRef) && !customHandler) {
240                        parserContext.getReaderContext().error("You must specify a step", partitionElement);
241                        return;
242                }
243
244                if (StringUtils.hasText(stepRef)) {
245                        propertyValues.addPropertyValue("step", new RuntimeBeanReference(stepRef));
246                } else if( inlineStepElement!=null) {
247                        AbstractBeanDefinition stepDefinition = parseStep(inlineStepElement, parserContext, jobFactoryRef);
248                        stepDefinition.getPropertyValues().addPropertyValue("name", stepElement.getAttribute(ID_ATTR));
249                        propertyValues.addPropertyValue("step", stepDefinition );
250                }
251
252        }
253
254        private void parseJob(Element stepElement, Element jobElement, AbstractBeanDefinition bd, ParserContext parserContext, boolean stepUnderspecified) {
255
256                bd.setBeanClass(StepParserStepFactoryBean.class);
257                bd.setAttribute("isNamespaceStep", true);
258                String jobRef = jobElement.getAttribute(REF_ATTR);
259
260                if (!StringUtils.hasText(jobRef)) {
261                        parserContext.getReaderContext().error("You must specify a job", jobElement);
262                        return;
263                }
264
265                MutablePropertyValues propertyValues = bd.getPropertyValues();
266                propertyValues.addPropertyValue("job", new RuntimeBeanReference(jobRef));
267
268                String jobParametersExtractor = jobElement.getAttribute(JOB_PARAMS_EXTRACTOR_ATTR);
269                String jobLauncher = jobElement.getAttribute(JOB_LAUNCHER_ATTR);
270
271                if (StringUtils.hasText(jobParametersExtractor)) {
272                        propertyValues.addPropertyValue("jobParametersExtractor", new RuntimeBeanReference(jobParametersExtractor));
273                }
274                if (StringUtils.hasText(jobLauncher)) {
275                        propertyValues.addPropertyValue("jobLauncher", new RuntimeBeanReference(jobLauncher));
276                }
277
278        }
279
280
281        private void parseFlow(Element stepElement, Element flowElement, AbstractBeanDefinition bd,
282                               ParserContext parserContext, boolean stepUnderspecified) {
283
284                bd.setBeanClass(StepParserStepFactoryBean.class);
285                bd.setAttribute("isNamespaceStep", true);
286                String flowRef = flowElement.getAttribute(PARENT_ATTR);
287                String idAttribute = stepElement.getAttribute(ID_ATTR);
288
289                BeanDefinition flowDefinition = new GenericBeanDefinition();
290                flowDefinition.setParentName(flowRef);
291                MutablePropertyValues propertyValues = flowDefinition.getPropertyValues();
292                if (StringUtils.hasText(idAttribute)) {
293                        propertyValues.addPropertyValue("name", idAttribute);
294                }
295
296                bd.getPropertyValues().addPropertyValue("flow", flowDefinition);
297
298        }
299
300}