001/*
002 * Copyright 2002-2016 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.web.method;
018
019import java.lang.annotation.Annotation;
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.Collections;
023import java.util.LinkedHashSet;
024import java.util.List;
025import java.util.Set;
026
027import org.springframework.beans.factory.BeanFactory;
028import org.springframework.beans.factory.BeanFactoryUtils;
029import org.springframework.context.ApplicationContext;
030import org.springframework.core.Ordered;
031import org.springframework.core.annotation.AnnotatedElementUtils;
032import org.springframework.core.annotation.AnnotationUtils;
033import org.springframework.core.annotation.OrderUtils;
034import org.springframework.util.Assert;
035import org.springframework.util.ClassUtils;
036import org.springframework.util.StringUtils;
037import org.springframework.web.bind.annotation.ControllerAdvice;
038
039/**
040 * Encapsulates information about an {@linkplain ControllerAdvice @ControllerAdvice}
041 * Spring-managed bean without necessarily requiring it to be instantiated.
042 *
043 * <p>The {@link #findAnnotatedBeans(ApplicationContext)} method can be used to
044 * discover such beans. However, a {@code ControllerAdviceBean} may be created
045 * from any object, including ones without an {@code @ControllerAdvice}.
046 *
047 * @author Rossen Stoyanchev
048 * @author Brian Clozel
049 * @author Juergen Hoeller
050 * @since 3.2
051 */
052public class ControllerAdviceBean implements Ordered {
053
054        private final Object bean;
055
056        private final BeanFactory beanFactory;
057
058        private final int order;
059
060        private final Set<String> basePackages;
061
062        private final List<Class<?>> assignableTypes;
063
064        private final List<Class<? extends Annotation>> annotations;
065
066
067        /**
068         * Create a {@code ControllerAdviceBean} using the given bean instance.
069         * @param bean the bean instance
070         */
071        public ControllerAdviceBean(Object bean) {
072                this(bean, null);
073        }
074
075        /**
076         * Create a {@code ControllerAdviceBean} using the given bean name.
077         * @param beanName the name of the bean
078         * @param beanFactory a BeanFactory that can be used later to resolve the bean
079         */
080        public ControllerAdviceBean(String beanName, BeanFactory beanFactory) {
081                this((Object) beanName, beanFactory);
082        }
083
084        private ControllerAdviceBean(Object bean, BeanFactory beanFactory) {
085                this.bean = bean;
086                this.beanFactory = beanFactory;
087                Class<?> beanType;
088
089                if (bean instanceof String) {
090                        String beanName = (String) bean;
091                        Assert.hasText(beanName, "Bean name must not be null");
092                        Assert.notNull(beanFactory, "BeanFactory must not be null");
093                        if (!beanFactory.containsBean(beanName)) {
094                                throw new IllegalArgumentException("BeanFactory [" + beanFactory +
095                                                "] does not contain specified controller advice bean '" + beanName + "'");
096                        }
097                        beanType = this.beanFactory.getType(beanName);
098                        this.order = initOrderFromBeanType(beanType);
099                }
100                else {
101                        Assert.notNull(bean, "Bean must not be null");
102                        beanType = bean.getClass();
103                        this.order = initOrderFromBean(bean);
104                }
105
106                ControllerAdvice annotation =
107                                AnnotatedElementUtils.findMergedAnnotation(beanType, ControllerAdvice.class);
108
109                if (annotation != null) {
110                        this.basePackages = initBasePackages(annotation);
111                        this.assignableTypes = Arrays.asList(annotation.assignableTypes());
112                        this.annotations = Arrays.asList(annotation.annotations());
113                }
114                else {
115                        this.basePackages = Collections.emptySet();
116                        this.assignableTypes = Collections.emptyList();
117                        this.annotations = Collections.emptyList();
118                }
119        }
120
121
122        /**
123         * Returns the order value extracted from the {@link ControllerAdvice}
124         * annotation, or {@link Ordered#LOWEST_PRECEDENCE} otherwise.
125         */
126        @Override
127        public int getOrder() {
128                return this.order;
129        }
130
131        /**
132         * Return the type of the contained bean.
133         * <p>If the bean type is a CGLIB-generated class, the original
134         * user-defined class is returned.
135         */
136        public Class<?> getBeanType() {
137                Class<?> clazz = (this.bean instanceof String ?
138                                this.beanFactory.getType((String) this.bean) : this.bean.getClass());
139                return ClassUtils.getUserClass(clazz);
140        }
141
142        /**
143         * Return a bean instance if necessary resolving the bean name through the BeanFactory.
144         */
145        public Object resolveBean() {
146                return (this.bean instanceof String ? this.beanFactory.getBean((String) this.bean) : this.bean);
147        }
148
149        /**
150         * Check whether the given bean type should be assisted by this
151         * {@code @ControllerAdvice} instance.
152         * @param beanType the type of the bean to check
153         * @see org.springframework.web.bind.annotation.ControllerAdvice
154         * @since 4.0
155         */
156        public boolean isApplicableToBeanType(Class<?> beanType) {
157                if (!hasSelectors()) {
158                        return true;
159                }
160                else if (beanType != null) {
161                        for (String basePackage : this.basePackages) {
162                                if (beanType.getName().startsWith(basePackage)) {
163                                        return true;
164                                }
165                        }
166                        for (Class<?> clazz : this.assignableTypes) {
167                                if (ClassUtils.isAssignable(clazz, beanType)) {
168                                        return true;
169                                }
170                        }
171                        for (Class<? extends Annotation> annotationClass : this.annotations) {
172                                if (AnnotationUtils.findAnnotation(beanType, annotationClass) != null) {
173                                        return true;
174                                }
175                        }
176                }
177                return false;
178        }
179
180        private boolean hasSelectors() {
181                return (!this.basePackages.isEmpty() || !this.assignableTypes.isEmpty() || !this.annotations.isEmpty());
182        }
183
184
185        @Override
186        public boolean equals(Object other) {
187                if (this == other) {
188                        return true;
189                }
190                if (!(other instanceof ControllerAdviceBean)) {
191                        return false;
192                }
193                ControllerAdviceBean otherAdvice = (ControllerAdviceBean) other;
194                return (this.bean.equals(otherAdvice.bean) && this.beanFactory == otherAdvice.beanFactory);
195        }
196
197        @Override
198        public int hashCode() {
199                return this.bean.hashCode();
200        }
201
202        @Override
203        public String toString() {
204                return this.bean.toString();
205        }
206
207
208        /**
209         * Find the names of beans annotated with
210         * {@linkplain ControllerAdvice @ControllerAdvice} in the given
211         * ApplicationContext and wrap them as {@code ControllerAdviceBean} instances.
212         */
213        public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext applicationContext) {
214                List<ControllerAdviceBean> beans = new ArrayList<ControllerAdviceBean>();
215                for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class)) {
216                        if (applicationContext.findAnnotationOnBean(name, ControllerAdvice.class) != null) {
217                                beans.add(new ControllerAdviceBean(name, applicationContext));
218                        }
219                }
220                return beans;
221        }
222
223        private static int initOrderFromBean(Object bean) {
224                return (bean instanceof Ordered ? ((Ordered) bean).getOrder() : initOrderFromBeanType(bean.getClass()));
225        }
226
227        private static int initOrderFromBeanType(Class<?> beanType) {
228                return OrderUtils.getOrder(beanType, Ordered.LOWEST_PRECEDENCE);
229        }
230
231        private static Set<String> initBasePackages(ControllerAdvice annotation) {
232                Set<String> basePackages = new LinkedHashSet<String>();
233                for (String basePackage : annotation.basePackages()) {
234                        if (StringUtils.hasText(basePackage)) {
235                                basePackages.add(adaptBasePackage(basePackage));
236                        }
237                }
238                for (Class<?> markerClass : annotation.basePackageClasses()) {
239                        basePackages.add(adaptBasePackage(ClassUtils.getPackageName(markerClass)));
240                }
241                return basePackages;
242        }
243
244        private static String adaptBasePackage(String basePackage) {
245                return (basePackage.endsWith(".") ? basePackage : basePackage + ".");
246        }
247
248}