001/*
002 * Copyright 2002-2018 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 */
016
017package org.springframework.context.annotation;
018
019import java.lang.annotation.Annotation;
020import java.util.Set;
021import java.util.regex.Pattern;
022
023import org.w3c.dom.Element;
024import org.w3c.dom.Node;
025import org.w3c.dom.NodeList;
026
027import org.springframework.beans.BeanUtils;
028import org.springframework.beans.factory.config.BeanDefinition;
029import org.springframework.beans.factory.config.BeanDefinitionHolder;
030import org.springframework.beans.factory.parsing.BeanComponentDefinition;
031import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
032import org.springframework.beans.factory.support.BeanNameGenerator;
033import org.springframework.beans.factory.xml.BeanDefinitionParser;
034import org.springframework.beans.factory.xml.ParserContext;
035import org.springframework.beans.factory.xml.XmlReaderContext;
036import org.springframework.context.ConfigurableApplicationContext;
037import org.springframework.core.type.filter.AnnotationTypeFilter;
038import org.springframework.core.type.filter.AspectJTypeFilter;
039import org.springframework.core.type.filter.AssignableTypeFilter;
040import org.springframework.core.type.filter.RegexPatternTypeFilter;
041import org.springframework.core.type.filter.TypeFilter;
042import org.springframework.util.ClassUtils;
043import org.springframework.util.StringUtils;
044
045/**
046 * Parser for the {@code <context:component-scan/>} element.
047 *
048 * @author Mark Fisher
049 * @author Ramnivas Laddad
050 * @author Juergen Hoeller
051 * @since 2.5
052 */
053public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {
054
055        private static final String BASE_PACKAGE_ATTRIBUTE = "base-package";
056
057        private static final String RESOURCE_PATTERN_ATTRIBUTE = "resource-pattern";
058
059        private static final String USE_DEFAULT_FILTERS_ATTRIBUTE = "use-default-filters";
060
061        private static final String ANNOTATION_CONFIG_ATTRIBUTE = "annotation-config";
062
063        private static final String NAME_GENERATOR_ATTRIBUTE = "name-generator";
064
065        private static final String SCOPE_RESOLVER_ATTRIBUTE = "scope-resolver";
066
067        private static final String SCOPED_PROXY_ATTRIBUTE = "scoped-proxy";
068
069        private static final String EXCLUDE_FILTER_ELEMENT = "exclude-filter";
070
071        private static final String INCLUDE_FILTER_ELEMENT = "include-filter";
072
073        private static final String FILTER_TYPE_ATTRIBUTE = "type";
074
075        private static final String FILTER_EXPRESSION_ATTRIBUTE = "expression";
076
077
078        @Override
079        public BeanDefinition parse(Element element, ParserContext parserContext) {
080                String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
081                basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
082                String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
083                                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
084
085                // Actually scan for bean definitions and register them.
086                ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
087                Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
088                registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
089
090                return null;
091        }
092
093        protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {
094                boolean useDefaultFilters = true;
095                if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {
096                        useDefaultFilters = Boolean.valueOf(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));
097                }
098
099                // Delegate bean definition registration to scanner class.
100                ClassPathBeanDefinitionScanner scanner = createScanner(parserContext.getReaderContext(), useDefaultFilters);
101                scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());
102                scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());
103
104                if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {
105                        scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));
106                }
107
108                try {
109                        parseBeanNameGenerator(element, scanner);
110                }
111                catch (Exception ex) {
112                        parserContext.getReaderContext().error(ex.getMessage(), parserContext.extractSource(element), ex.getCause());
113                }
114
115                try {
116                        parseScope(element, scanner);
117                }
118                catch (Exception ex) {
119                        parserContext.getReaderContext().error(ex.getMessage(), parserContext.extractSource(element), ex.getCause());
120                }
121
122                parseTypeFilters(element, scanner, parserContext);
123
124                return scanner;
125        }
126
127        protected ClassPathBeanDefinitionScanner createScanner(XmlReaderContext readerContext, boolean useDefaultFilters) {
128                return new ClassPathBeanDefinitionScanner(readerContext.getRegistry(), useDefaultFilters,
129                                readerContext.getEnvironment(), readerContext.getResourceLoader());
130        }
131
132        protected void registerComponents(
133                        XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) {
134
135                Object source = readerContext.extractSource(element);
136                CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source);
137
138                for (BeanDefinitionHolder beanDefHolder : beanDefinitions) {
139                        compositeDef.addNestedComponent(new BeanComponentDefinition(beanDefHolder));
140                }
141
142                // Register annotation config processors, if necessary.
143                boolean annotationConfig = true;
144                if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {
145                        annotationConfig = Boolean.valueOf(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
146                }
147                if (annotationConfig) {
148                        Set<BeanDefinitionHolder> processorDefinitions =
149                                        AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);
150                        for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
151                                compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));
152                        }
153                }
154
155                readerContext.fireComponentRegistered(compositeDef);
156        }
157
158        protected void parseBeanNameGenerator(Element element, ClassPathBeanDefinitionScanner scanner) {
159                if (element.hasAttribute(NAME_GENERATOR_ATTRIBUTE)) {
160                        BeanNameGenerator beanNameGenerator = (BeanNameGenerator) instantiateUserDefinedStrategy(
161                                        element.getAttribute(NAME_GENERATOR_ATTRIBUTE), BeanNameGenerator.class,
162                                        scanner.getResourceLoader().getClassLoader());
163                        scanner.setBeanNameGenerator(beanNameGenerator);
164                }
165        }
166
167        protected void parseScope(Element element, ClassPathBeanDefinitionScanner scanner) {
168                // Register ScopeMetadataResolver if class name provided.
169                if (element.hasAttribute(SCOPE_RESOLVER_ATTRIBUTE)) {
170                        if (element.hasAttribute(SCOPED_PROXY_ATTRIBUTE)) {
171                                throw new IllegalArgumentException(
172                                                "Cannot define both 'scope-resolver' and 'scoped-proxy' on <component-scan> tag");
173                        }
174                        ScopeMetadataResolver scopeMetadataResolver = (ScopeMetadataResolver) instantiateUserDefinedStrategy(
175                                        element.getAttribute(SCOPE_RESOLVER_ATTRIBUTE), ScopeMetadataResolver.class,
176                                        scanner.getResourceLoader().getClassLoader());
177                        scanner.setScopeMetadataResolver(scopeMetadataResolver);
178                }
179
180                if (element.hasAttribute(SCOPED_PROXY_ATTRIBUTE)) {
181                        String mode = element.getAttribute(SCOPED_PROXY_ATTRIBUTE);
182                        if ("targetClass".equals(mode)) {
183                                scanner.setScopedProxyMode(ScopedProxyMode.TARGET_CLASS);
184                        }
185                        else if ("interfaces".equals(mode)) {
186                                scanner.setScopedProxyMode(ScopedProxyMode.INTERFACES);
187                        }
188                        else if ("no".equals(mode)) {
189                                scanner.setScopedProxyMode(ScopedProxyMode.NO);
190                        }
191                        else {
192                                throw new IllegalArgumentException("scoped-proxy only supports 'no', 'interfaces' and 'targetClass'");
193                        }
194                }
195        }
196
197        protected void parseTypeFilters(Element element, ClassPathBeanDefinitionScanner scanner, ParserContext parserContext) {
198                // Parse exclude and include filter elements.
199                ClassLoader classLoader = scanner.getResourceLoader().getClassLoader();
200                NodeList nodeList = element.getChildNodes();
201                for (int i = 0; i < nodeList.getLength(); i++) {
202                        Node node = nodeList.item(i);
203                        if (node.getNodeType() == Node.ELEMENT_NODE) {
204                                String localName = parserContext.getDelegate().getLocalName(node);
205                                try {
206                                        if (INCLUDE_FILTER_ELEMENT.equals(localName)) {
207                                                TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext);
208                                                scanner.addIncludeFilter(typeFilter);
209                                        }
210                                        else if (EXCLUDE_FILTER_ELEMENT.equals(localName)) {
211                                                TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext);
212                                                scanner.addExcludeFilter(typeFilter);
213                                        }
214                                }
215                                catch (ClassNotFoundException ex) {
216                                        parserContext.getReaderContext().warning(
217                                                        "Ignoring non-present type filter class: " + ex, parserContext.extractSource(element));
218                                }
219                                catch (Exception ex) {
220                                        parserContext.getReaderContext().error(
221                                                        ex.getMessage(), parserContext.extractSource(element), ex.getCause());
222                                }
223                        }
224                }
225        }
226
227        @SuppressWarnings("unchecked")
228        protected TypeFilter createTypeFilter(Element element, ClassLoader classLoader,
229                        ParserContext parserContext) throws ClassNotFoundException {
230
231                String filterType = element.getAttribute(FILTER_TYPE_ATTRIBUTE);
232                String expression = element.getAttribute(FILTER_EXPRESSION_ATTRIBUTE);
233                expression = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(expression);
234                if ("annotation".equals(filterType)) {
235                        return new AnnotationTypeFilter((Class<Annotation>) ClassUtils.forName(expression, classLoader));
236                }
237                else if ("assignable".equals(filterType)) {
238                        return new AssignableTypeFilter(ClassUtils.forName(expression, classLoader));
239                }
240                else if ("aspectj".equals(filterType)) {
241                        return new AspectJTypeFilter(expression, classLoader);
242                }
243                else if ("regex".equals(filterType)) {
244                        return new RegexPatternTypeFilter(Pattern.compile(expression));
245                }
246                else if ("custom".equals(filterType)) {
247                        Class<?> filterClass = ClassUtils.forName(expression, classLoader);
248                        if (!TypeFilter.class.isAssignableFrom(filterClass)) {
249                                throw new IllegalArgumentException(
250                                                "Class is not assignable to [" + TypeFilter.class.getName() + "]: " + expression);
251                        }
252                        return (TypeFilter) BeanUtils.instantiateClass(filterClass);
253                }
254                else {
255                        throw new IllegalArgumentException("Unsupported filter type: " + filterType);
256                }
257        }
258
259        @SuppressWarnings("unchecked")
260        private Object instantiateUserDefinedStrategy(String className, Class<?> strategyType, ClassLoader classLoader) {
261                Object result;
262                try {
263                        result = classLoader.loadClass(className).newInstance();
264                }
265                catch (ClassNotFoundException ex) {
266                        throw new IllegalArgumentException("Class [" + className + "] for strategy [" +
267                                        strategyType.getName() + "] not found", ex);
268                }
269                catch (Throwable ex) {
270                        throw new IllegalArgumentException("Unable to instantiate class [" + className + "] for strategy [" +
271                                        strategyType.getName() + "]: a zero-argument constructor is required", ex);
272                }
273
274                if (!strategyType.isAssignableFrom(result.getClass())) {
275                        throw new IllegalArgumentException("Provided class name must be an implementation of " + strategyType);
276                }
277                return result;
278        }
279
280}