001/* 002 * Copyright 2006-2013 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.springframework.batch.core.job.flow.support.DefaultStateTransitionComparator; 019import org.springframework.batch.core.job.flow.support.StateTransition; 020import org.springframework.beans.PropertyValue; 021import org.springframework.beans.factory.config.BeanDefinition; 022import org.springframework.beans.factory.config.TypedStringValue; 023import org.springframework.beans.factory.support.AbstractBeanDefinition; 024import org.springframework.beans.factory.support.BeanDefinitionBuilder; 025import org.springframework.beans.factory.support.BeanDefinitionRegistry; 026import org.springframework.beans.factory.support.ManagedMap; 027import org.springframework.beans.factory.xml.ParserContext; 028import org.springframework.util.StringUtils; 029import org.w3c.dom.Element; 030 031import java.util.Comparator; 032import java.util.Map; 033 034/** 035 * Utility methods used in parsing of the batch core namespace 036 * 037 * @author Thomas Risberg 038 * @author Michael Minella 039 */ 040public class CoreNamespaceUtils { 041 042 private static final String STEP_SCOPE_PROCESSOR_BEAN_NAME = "org.springframework.batch.core.scope.internalStepScope"; 043 044 private static final String XML_CONFIG_STEP_SCOPE_PROCESSOR_CLASS_NAME = "org.springframework.batch.core.scope.StepScope"; 045 046 private static final String JAVA_CONFIG_SCOPE_CLASS_NAME = "org.springframework.batch.core.configuration.annotation.ScopeConfiguration"; 047 048 private static final String JOB_SCOPE_PROCESSOR_BEAN_NAME = "org.springframework.batch.core.scope.internalJobScope"; 049 050 private static final String JOB_SCOPE_PROCESSOR_CLASS_NAME = "org.springframework.batch.core.scope.JobScope"; 051 052 private static final String CUSTOM_EDITOR_CONFIGURER_CLASS_NAME = "org.springframework.beans.factory.config.CustomEditorConfigurer"; 053 054 private static final String RANGE_ARRAY_CLASS_NAME = "org.springframework.batch.item.file.transform.Range[]"; 055 056 private static final String RANGE_ARRAY_EDITOR_CLASS_NAME = "org.springframework.batch.item.file.transform.RangeArrayPropertyEditor"; 057 058 private static final String CORE_NAMESPACE_POST_PROCESSOR_CLASS_NAME = "org.springframework.batch.core.configuration.xml.CoreNamespacePostProcessor"; 059 060 public static void autoregisterBeansForNamespace(ParserContext parserContext, Object source) { 061 checkForStepScope(parserContext, source); 062 checkForJobScope(parserContext, source); 063 addRangePropertyEditor(parserContext); 064 addCoreNamespacePostProcessor(parserContext); 065 addStateTransitionComparator(parserContext); 066 } 067 068 private static void checkForStepScope(ParserContext parserContext, Object source) { 069 checkForScope(parserContext, source, XML_CONFIG_STEP_SCOPE_PROCESSOR_CLASS_NAME, STEP_SCOPE_PROCESSOR_BEAN_NAME); 070 } 071 072 private static void checkForJobScope(ParserContext parserContext, Object source) { 073 checkForScope(parserContext, source, JOB_SCOPE_PROCESSOR_CLASS_NAME, JOB_SCOPE_PROCESSOR_BEAN_NAME); 074 } 075 076 private static void checkForScope(ParserContext parserContext, Object source, String scopeClassName, 077 String scopeBeanName) { 078 boolean foundScope = false; 079 String[] beanNames = parserContext.getRegistry().getBeanDefinitionNames(); 080 for (String beanName : beanNames) { 081 BeanDefinition bd = parserContext.getRegistry().getBeanDefinition(beanName); 082 if (scopeClassName.equals(bd.getBeanClassName()) || JAVA_CONFIG_SCOPE_CLASS_NAME.equals(bd.getBeanClassName())) { 083 foundScope = true; 084 break; 085 } 086 } 087 if (!foundScope) { 088 BeanDefinitionBuilder stepScopeBuilder = BeanDefinitionBuilder 089 .genericBeanDefinition(scopeClassName); 090 AbstractBeanDefinition abd = stepScopeBuilder.getBeanDefinition(); 091 abd.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); 092 abd.setSource(source); 093 parserContext.getRegistry().registerBeanDefinition(scopeBeanName, abd); 094 } 095 } 096 097 /** 098 * Register a {@link Comparator} to be used to sort {@link StateTransition}s 099 * 100 * @param parserContext 101 */ 102 private static void addStateTransitionComparator(ParserContext parserContext) { 103 BeanDefinitionRegistry registry = parserContext.getRegistry(); 104 if (!stateTransitionComparatorAlreadyDefined(registry)) { 105 AbstractBeanDefinition defaultStateTransitionComparator = BeanDefinitionBuilder.genericBeanDefinition( 106 DefaultStateTransitionComparator.class).getBeanDefinition(); 107 registry.registerBeanDefinition(DefaultStateTransitionComparator.STATE_TRANSITION_COMPARATOR, defaultStateTransitionComparator); 108 } 109 } 110 111 private static boolean stateTransitionComparatorAlreadyDefined(BeanDefinitionRegistry registry) { 112 return registry.containsBeanDefinition(DefaultStateTransitionComparator.STATE_TRANSITION_COMPARATOR); 113 } 114 115 /** 116 * Register a RangePropertyEditor if one does not already exist. 117 * 118 * @param parserContext 119 */ 120 private static void addRangePropertyEditor(ParserContext parserContext) { 121 BeanDefinitionRegistry registry = parserContext.getRegistry(); 122 if (!rangeArrayEditorAlreadyDefined(registry)) { 123 AbstractBeanDefinition customEditorConfigurer = BeanDefinitionBuilder.genericBeanDefinition( 124 CUSTOM_EDITOR_CONFIGURER_CLASS_NAME).getBeanDefinition(); 125 customEditorConfigurer.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); 126 ManagedMap<String, String> editors = new ManagedMap<String, String>(); 127 editors.put(RANGE_ARRAY_CLASS_NAME, RANGE_ARRAY_EDITOR_CLASS_NAME); 128 customEditorConfigurer.getPropertyValues().addPropertyValue("customEditors", editors); 129 registry.registerBeanDefinition(CUSTOM_EDITOR_CONFIGURER_CLASS_NAME, customEditorConfigurer); 130 } 131 } 132 133 private static boolean rangeArrayEditorAlreadyDefined(BeanDefinitionRegistry registry) { 134 for (String beanName : registry.getBeanDefinitionNames()) { 135 BeanDefinition bd = registry.getBeanDefinition(beanName); 136 if (CUSTOM_EDITOR_CONFIGURER_CLASS_NAME.equals(bd.getBeanClassName())) { 137 PropertyValue pv = bd.getPropertyValues().getPropertyValue("customEditors"); 138 if (pv != null) { 139 for (Map.Entry<?, ?> entry : ((Map<?, ?>) pv.getValue()).entrySet()) { 140 if (entry.getKey() instanceof TypedStringValue) { 141 if (RANGE_ARRAY_CLASS_NAME.equals(((TypedStringValue) entry.getKey()).getValue())) { 142 return true; 143 } 144 } 145 else if (entry.getKey() instanceof String) { 146 if (RANGE_ARRAY_CLASS_NAME.equals(entry.getKey())) { 147 return true; 148 } 149 } 150 } 151 } 152 } 153 } 154 return false; 155 } 156 157 /** 158 * @param parserContext 159 */ 160 private static void addCoreNamespacePostProcessor(ParserContext parserContext) { 161 BeanDefinitionRegistry registry = parserContext.getRegistry(); 162 if (!coreNamespaceBeanPostProcessorAlreadyDefined(registry)) { 163 AbstractBeanDefinition postProcessorBeanDef = BeanDefinitionBuilder.genericBeanDefinition( 164 CORE_NAMESPACE_POST_PROCESSOR_CLASS_NAME).getBeanDefinition(); 165 postProcessorBeanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); 166 registry.registerBeanDefinition(CORE_NAMESPACE_POST_PROCESSOR_CLASS_NAME, postProcessorBeanDef); 167 } 168 } 169 170 private static boolean coreNamespaceBeanPostProcessorAlreadyDefined(BeanDefinitionRegistry registry) { 171 for (String beanName : registry.getBeanDefinitionNames()) { 172 BeanDefinition bd = registry.getBeanDefinition(beanName); 173 if (CORE_NAMESPACE_POST_PROCESSOR_CLASS_NAME.equals(bd.getBeanClassName())) { 174 return true; 175 } 176 } 177 return false; 178 } 179 180 /** 181 * Should this element be treated as incomplete? If it has a parent or is 182 * abstract, then it may not have all properties. 183 * 184 * @param element to be evaluated. 185 * @return TRUE if the element is abstract or has a parent 186 */ 187 public static boolean isUnderspecified(Element element) { 188 return isAbstract(element) || StringUtils.hasText(element.getAttribute("parent")); 189 } 190 191 /** 192 * @param element to be evaluated. 193 * @return TRUE if the element is abstract 194 */ 195 public static boolean isAbstract(Element element) { 196 String abstractAttr = element.getAttribute("abstract"); 197 return StringUtils.hasText(abstractAttr) && Boolean.valueOf(abstractAttr); 198 } 199 200 /** 201 * Check that the schema location declared in the source file being parsed 202 * matches the Spring Batch version. (The old 2.0 schema is not 100% 203 * compatible with the new parser, so it is an error to explicitly define 204 * 2.0. It might be an error to declare spring-batch.xsd as an alias, but 205 * you are only going to find that out when one of the sub parses breaks.) 206 * 207 * @param element the element that is to be parsed next 208 * @return true if we find a schema declaration that matches 209 */ 210 public static boolean namespaceMatchesVersion(Element element) { 211 return matchesVersionInternal(element) 212 && matchesVersionInternal(element.getOwnerDocument().getDocumentElement()); 213 } 214 215 private static boolean matchesVersionInternal(Element element) { 216 String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation"); 217 return schemaLocation.matches("(?m).*spring-batch-3.0.xsd.*") 218 || schemaLocation.matches("(?m).*spring-batch-2.2.xsd.*") 219 || schemaLocation.matches("(?m).*spring-batch.xsd.*") 220 || !schemaLocation.matches("(?m).*spring-batch.*"); 221 } 222 223}