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}