001/*
002 * Copyright 2002-2017 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.scheduling.annotation;
018
019import java.lang.annotation.Annotation;
020import java.util.HashSet;
021import java.util.LinkedHashSet;
022import java.util.Set;
023import java.util.concurrent.Executor;
024
025import org.aopalliance.aop.Advice;
026
027import org.springframework.aop.Pointcut;
028import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
029import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
030import org.springframework.aop.support.AbstractPointcutAdvisor;
031import org.springframework.aop.support.ComposablePointcut;
032import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
033import org.springframework.beans.factory.BeanFactory;
034import org.springframework.beans.factory.BeanFactoryAware;
035import org.springframework.util.Assert;
036import org.springframework.util.ClassUtils;
037
038/**
039 * Advisor that activates asynchronous method execution through the {@link Async}
040 * annotation. This annotation can be used at the method and type level in
041 * implementation classes as well as in service interfaces.
042 *
043 * <p>This advisor detects the EJB 3.1 {@code javax.ejb.Asynchronous}
044 * annotation as well, treating it exactly like Spring's own {@code Async}.
045 * Furthermore, a custom async annotation type may get specified through the
046 * {@link #setAsyncAnnotationType "asyncAnnotationType"} property.
047 *
048 * @author Juergen Hoeller
049 * @since 3.0
050 * @see org.springframework.dao.annotation.PersistenceExceptionTranslationAdvisor
051 * @see org.springframework.stereotype.Repository
052 * @see org.springframework.dao.DataAccessException
053 * @see org.springframework.dao.support.PersistenceExceptionTranslator
054 */
055@SuppressWarnings("serial")
056public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
057
058        private AsyncUncaughtExceptionHandler exceptionHandler;
059
060        private Advice advice;
061
062        private Pointcut pointcut;
063
064
065        /**
066         * Create a new {@code AsyncAnnotationAdvisor} for bean-style configuration.
067         */
068        public AsyncAnnotationAdvisor() {
069                this(null, null);
070        }
071
072        /**
073         * Create a new {@code AsyncAnnotationAdvisor} for the given task executor.
074         * @param executor the task executor to use for asynchronous methods
075         * (can be {@code null} to trigger default executor resolution)
076         * @param exceptionHandler the {@link AsyncUncaughtExceptionHandler} to use to
077         * handle unexpected exception thrown by asynchronous method executions
078         * @see AnnotationAsyncExecutionInterceptor#getDefaultExecutor(BeanFactory)
079         */
080        @SuppressWarnings("unchecked")
081        public AsyncAnnotationAdvisor(Executor executor, AsyncUncaughtExceptionHandler exceptionHandler) {
082                Set<Class<? extends Annotation>> asyncAnnotationTypes = new LinkedHashSet<Class<? extends Annotation>>(2);
083                asyncAnnotationTypes.add(Async.class);
084                try {
085                        asyncAnnotationTypes.add((Class<? extends Annotation>)
086                                        ClassUtils.forName("javax.ejb.Asynchronous", AsyncAnnotationAdvisor.class.getClassLoader()));
087                }
088                catch (ClassNotFoundException ex) {
089                        // If EJB 3.1 API not present, simply ignore.
090                }
091                if (exceptionHandler != null) {
092                        this.exceptionHandler = exceptionHandler;
093                }
094                else {
095                        this.exceptionHandler = new SimpleAsyncUncaughtExceptionHandler();
096                }
097                this.advice = buildAdvice(executor, this.exceptionHandler);
098                this.pointcut = buildPointcut(asyncAnnotationTypes);
099        }
100
101
102        /**
103         * Specify the default task executor to use for asynchronous methods.
104         */
105        public void setTaskExecutor(Executor executor) {
106                this.advice = buildAdvice(executor, this.exceptionHandler);
107        }
108
109        /**
110         * Set the 'async' annotation type.
111         * <p>The default async annotation type is the {@link Async} annotation, as well
112         * as the EJB 3.1 {@code javax.ejb.Asynchronous} annotation (if present).
113         * <p>This setter property exists so that developers can provide their own
114         * (non-Spring-specific) annotation type to indicate that a method is to
115         * be executed asynchronously.
116         * @param asyncAnnotationType the desired annotation type
117         */
118        public void setAsyncAnnotationType(Class<? extends Annotation> asyncAnnotationType) {
119                Assert.notNull(asyncAnnotationType, "'asyncAnnotationType' must not be null");
120                Set<Class<? extends Annotation>> asyncAnnotationTypes = new HashSet<Class<? extends Annotation>>();
121                asyncAnnotationTypes.add(asyncAnnotationType);
122                this.pointcut = buildPointcut(asyncAnnotationTypes);
123        }
124
125        /**
126         * Set the {@code BeanFactory} to be used when looking up executors by qualifier.
127         */
128        @Override
129        public void setBeanFactory(BeanFactory beanFactory) {
130                if (this.advice instanceof BeanFactoryAware) {
131                        ((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);
132                }
133        }
134
135
136        @Override
137        public Advice getAdvice() {
138                return this.advice;
139        }
140
141        @Override
142        public Pointcut getPointcut() {
143                return this.pointcut;
144        }
145
146
147        protected Advice buildAdvice(Executor executor, AsyncUncaughtExceptionHandler exceptionHandler) {
148                return new AnnotationAsyncExecutionInterceptor(executor, exceptionHandler);
149        }
150
151        /**
152         * Calculate a pointcut for the given async annotation types, if any.
153         * @param asyncAnnotationTypes the async annotation types to introspect
154         * @return the applicable Pointcut object, or {@code null} if none
155         */
156        protected Pointcut buildPointcut(Set<Class<? extends Annotation>> asyncAnnotationTypes) {
157                ComposablePointcut result = null;
158                for (Class<? extends Annotation> asyncAnnotationType : asyncAnnotationTypes) {
159                        Pointcut cpc = new AnnotationMatchingPointcut(asyncAnnotationType, true);
160                        Pointcut mpc = AnnotationMatchingPointcut.forMethodAnnotation(asyncAnnotationType);
161                        if (result == null) {
162                                result = new ComposablePointcut(cpc);
163                        }
164                        else {
165                                result.union(cpc);
166                        }
167                        result = result.union(mpc);
168                }
169                return result;
170        }
171
172}