001/* 002 * Copyright 2006-2008 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 java.util.Arrays; 019import java.util.List; 020 021import org.springframework.beans.BeanMetadataElement; 022import org.springframework.beans.factory.config.BeanDefinition; 023import org.springframework.beans.factory.config.BeanDefinitionHolder; 024import org.springframework.beans.factory.config.RuntimeBeanReference; 025import org.springframework.beans.factory.parsing.CompositeComponentDefinition; 026import org.springframework.beans.factory.support.BeanDefinitionBuilder; 027import org.springframework.beans.factory.support.ManagedList; 028import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; 029import org.springframework.beans.factory.xml.ParserContext; 030import org.springframework.util.StringUtils; 031import org.springframework.util.xml.DomUtils; 032import org.w3c.dom.Element; 033 034/** 035 * Parser for the <job/> element in the Batch namespace. Sets up and returns 036 * a bean definition for a {@link org.springframework.batch.core.Job}. 037 * 038 * @author Dave Syer 039 * 040 */ 041public class JobParser extends AbstractSingleBeanDefinitionParser { 042 043 private static final String MERGE_ATTR = "merge"; 044 045 private static final String REF_ATTR = "ref"; 046 047 private static final String BEAN_ELE = "bean"; 048 049 private static final String REF_ELE = "ref"; 050 051 private static final JobExecutionListenerParser jobListenerParser = new JobExecutionListenerParser(); 052 053 @Override 054 protected Class<JobParserJobFactoryBean> getBeanClass(Element element) { 055 return JobParserJobFactoryBean.class; 056 } 057 058 /** 059 * Create a bean definition for a 060 * {@link org.springframework.batch.core.job.flow.FlowJob}. Nested step 061 * elements are delegated to an {@link InlineStepParser}. 062 * 063 * @see AbstractSingleBeanDefinitionParser#doParse(Element, ParserContext, 064 * BeanDefinitionBuilder) 065 */ 066 @Override 067 protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { 068 069 if (!CoreNamespaceUtils.namespaceMatchesVersion(element)) { 070 parserContext.getReaderContext().error( 071 "You are using a version of the spring-batch XSD that is not compatible with Spring Batch 3.0." + 072 " Please upgrade your schema declarations (or use the spring-batch.xsd alias if you are " + 073 "feeling lucky).", element); 074 return; 075 } 076 077 CoreNamespaceUtils.autoregisterBeansForNamespace(parserContext, parserContext.extractSource(element)); 078 079 String jobName = element.getAttribute("id"); 080 builder.addConstructorArgValue(jobName); 081 082 boolean isAbstract = CoreNamespaceUtils.isAbstract(element); 083 builder.setAbstract(isAbstract); 084 085 String parentRef = element.getAttribute("parent"); 086 if (StringUtils.hasText(parentRef)) { 087 builder.setParentName(parentRef); 088 } 089 090 String repositoryAttribute = element.getAttribute("job-repository"); 091 if (StringUtils.hasText(repositoryAttribute)) { 092 builder.addPropertyReference("jobRepository", repositoryAttribute); 093 } 094 095 Element validator = DomUtils.getChildElementByTagName(element, "validator"); 096 if (validator != null) { 097 builder.addPropertyValue("jobParametersValidator", parseBeanElement(validator, parserContext)); 098 } 099 100 String restartableAttribute = element.getAttribute("restartable"); 101 if (StringUtils.hasText(restartableAttribute)) { 102 builder.addPropertyValue("restartable", restartableAttribute); 103 } 104 105 String incrementer = (element.getAttribute("incrementer")); 106 if (StringUtils.hasText(incrementer)) { 107 builder.addPropertyReference("jobParametersIncrementer", incrementer); 108 } 109 110 if (isAbstract) { 111 for (String tagName : Arrays.asList("step", "decision", "split")) { 112 if (!DomUtils.getChildElementsByTagName(element, tagName).isEmpty()) { 113 parserContext.getReaderContext().error( 114 "The <" + tagName + "/> element may not appear on a <job/> with abstract=\"true\" [" 115 + jobName + "]", element); 116 } 117 } 118 } 119 else { 120 InlineFlowParser flowParser = new InlineFlowParser(jobName, jobName); 121 BeanDefinition flowDef = flowParser.parse(element, parserContext); 122 builder.addPropertyValue("flow", flowDef); 123 } 124 125 Element description = DomUtils.getChildElementByTagName(element, "description"); 126 if (description != null) { 127 builder.getBeanDefinition().setDescription(description.getTextContent()); 128 } 129 130 List<Element> listenersElements = DomUtils.getChildElementsByTagName(element, "listeners"); 131 if (listenersElements.size() == 1) { 132 Element listenersElement = listenersElements.get(0); 133 CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(listenersElement.getTagName(), 134 parserContext.extractSource(element)); 135 parserContext.pushContainingComponent(compositeDef); 136 ManagedList<BeanDefinition> listeners = new ManagedList<BeanDefinition>(); 137 listeners.setMergeEnabled(listenersElement.hasAttribute(MERGE_ATTR) 138 && Boolean.valueOf(listenersElement.getAttribute(MERGE_ATTR))); 139 List<Element> listenerElements = DomUtils.getChildElementsByTagName(listenersElement, "listener"); 140 for (Element listenerElement : listenerElements) { 141 listeners.add(jobListenerParser.parse(listenerElement, parserContext)); 142 } 143 builder.addPropertyValue("jobExecutionListeners", listeners); 144 parserContext.popAndRegisterContainingComponent(); 145 } 146 else if (listenersElements.size() > 1) { 147 parserContext.getReaderContext().error( 148 "The '<listeners/>' element may not appear more than once in a single <job/>.", element); 149 } 150 151 } 152 153 public BeanMetadataElement parseBeanElement(Element element, ParserContext parserContext) { 154 String refAttribute = element.getAttribute(REF_ATTR); 155 Element beanElement = DomUtils.getChildElementByTagName(element, BEAN_ELE); 156 Element refElement = DomUtils.getChildElementByTagName(element, REF_ELE); 157 158 if (StringUtils.hasText(refAttribute)) { 159 return new RuntimeBeanReference(refAttribute); 160 } 161 else if (beanElement != null) { 162 BeanDefinitionHolder beanDefinitionHolder = parserContext.getDelegate().parseBeanDefinitionElement( 163 beanElement); 164 parserContext.getDelegate().decorateBeanDefinitionIfRequired(beanElement, beanDefinitionHolder); 165 return beanDefinitionHolder; 166 } 167 else if (refElement != null) { 168 return (BeanMetadataElement) parserContext.getDelegate().parsePropertySubElement(refElement, null); 169 } 170 171 parserContext.getReaderContext().error( 172 "One of ref attribute or a nested bean definition or ref element must be specified", element); 173 return null; 174 } 175 176}