001/*
002 * Copyright 2002-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 */
016
017package org.springframework.ejb.interceptor;
018
019import java.util.Map;
020import java.util.WeakHashMap;
021import javax.annotation.PostConstruct;
022import javax.annotation.PreDestroy;
023import javax.ejb.EJBException;
024import javax.ejb.PostActivate;
025import javax.ejb.PrePassivate;
026import javax.interceptor.InvocationContext;
027
028import org.springframework.beans.factory.BeanFactory;
029import org.springframework.beans.factory.access.BeanFactoryLocator;
030import org.springframework.beans.factory.access.BeanFactoryReference;
031import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
032import org.springframework.context.ApplicationContext;
033import org.springframework.context.access.ContextSingletonBeanFactoryLocator;
034
035/**
036 * EJB3-compliant interceptor class that injects Spring beans into
037 * fields and methods which are annotated with {@code @Autowired}.
038 * Performs injection after construction as well as after activation
039 * of a passivated bean.
040 *
041 * <p>To be applied through an {@code @Interceptors} annotation in
042 * the EJB Session Bean or Message-Driven Bean class, or through an
043 * {@code interceptor-binding} XML element in the EJB deployment descriptor.
044 *
045 * <p>Delegates to Spring's {@link AutowiredAnnotationBeanPostProcessor}
046 * underneath, allowing for customization of its specific settings through
047 * overriding the {@link #configureBeanPostProcessor} template method.
048 *
049 * <p>The actual BeanFactory to obtain Spring beans from is determined
050 * by the {@link #getBeanFactory} template method. The default implementation
051 * obtains the Spring {@link ContextSingletonBeanFactoryLocator}, initialized
052 * from the default resource location <strong>classpath*:beanRefContext.xml</strong>,
053 * and obtains the single ApplicationContext defined there.
054 *
055 * <p><b>NOTE: If you have more than one shared ApplicationContext definition available
056 * in your EJB class loader, you need to override the {@link #getBeanFactoryLocatorKey}
057 * method and provide a specific locator key for each autowired EJB.</b>
058 * Alternatively, override the {@link #getBeanFactory} template method and
059 * obtain the target factory explicitly.
060 *
061 * <p><b>WARNING: Do not define the same bean as Spring-managed bean and as
062 * EJB3 session bean in the same deployment unit.</b> In particular, be
063 * careful when using the {@code <context:component-scan>} feature
064 * in combination with the deployment of Spring-based EJB3 session beans:
065 * Make sure that the EJB3 session beans are <i>not</i> autodetected as
066 * Spring-managed beans as well, using appropriate package restrictions.
067 *
068 * @author Juergen Hoeller
069 * @since 2.5.1
070 * @see org.springframework.beans.factory.annotation.Autowired
071 * @see org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
072 * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator
073 * @see #getBeanFactoryLocatorKey
074 */
075public class SpringBeanAutowiringInterceptor {
076
077        /*
078         * We're keeping the BeanFactoryReference per target object in order to
079         * allow for using a shared interceptor instance on pooled target beans.
080         * This is not strictly necessary for EJB3 Session Beans and Message-Driven
081         * Beans, where interceptor instances get created per target bean instance.
082         * It simply protects against future usage of the interceptor in a shared scenario.
083         */
084        private final Map<Object, BeanFactoryReference> beanFactoryReferences =
085                        new WeakHashMap<Object, BeanFactoryReference>();
086
087
088        /**
089         * Autowire the target bean after construction as well as after passivation.
090         * @param invocationContext the EJB3 invocation context
091         */
092        @PostConstruct
093        @PostActivate
094        public void autowireBean(InvocationContext invocationContext) {
095                doAutowireBean(invocationContext.getTarget());
096                try {
097                        invocationContext.proceed();
098                }
099                catch (RuntimeException ex) {
100                        doReleaseBean(invocationContext.getTarget());
101                        throw ex;
102                }
103                catch (Error err) {
104                        doReleaseBean(invocationContext.getTarget());
105                        throw err;
106                }
107                catch (Exception ex) {
108                        doReleaseBean(invocationContext.getTarget());
109                        // Cannot declare a checked exception on WebSphere here - so we need to wrap.
110                        throw new EJBException(ex);
111                }
112        }
113
114        /**
115         * Actually autowire the target bean after construction/passivation.
116         * @param target the target bean to autowire
117         */
118        protected void doAutowireBean(Object target) {
119                AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
120                configureBeanPostProcessor(bpp, target);
121                bpp.setBeanFactory(getBeanFactory(target));
122                bpp.processInjection(target);
123        }
124
125        /**
126         * Template method for configuring the
127         * {@link AutowiredAnnotationBeanPostProcessor} used for autowiring.
128         * @param processor the AutowiredAnnotationBeanPostProcessor to configure
129         * @param target the target bean to autowire with this processor
130         */
131        protected void configureBeanPostProcessor(AutowiredAnnotationBeanPostProcessor processor, Object target) {
132        }
133
134        /**
135         * Determine the BeanFactory for autowiring the given target bean.
136         * @param target the target bean to autowire
137         * @return the BeanFactory to use (never {@code null})
138         * @see #getBeanFactoryReference
139         */
140        protected BeanFactory getBeanFactory(Object target) {
141                BeanFactory factory = getBeanFactoryReference(target).getFactory();
142                if (factory instanceof ApplicationContext) {
143                        factory = ((ApplicationContext) factory).getAutowireCapableBeanFactory();
144                }
145                return factory;
146        }
147
148        /**
149         * Determine the BeanFactoryReference for the given target bean.
150         * <p>The default implementation delegates to {@link #getBeanFactoryLocator}
151         * and {@link #getBeanFactoryLocatorKey}.
152         * @param target the target bean to autowire
153         * @return the BeanFactoryReference to use (never {@code null})
154         * @see #getBeanFactoryLocator
155         * @see #getBeanFactoryLocatorKey
156         * @see org.springframework.beans.factory.access.BeanFactoryLocator#useBeanFactory(String)
157         */
158        protected BeanFactoryReference getBeanFactoryReference(Object target) {
159                String key = getBeanFactoryLocatorKey(target);
160                BeanFactoryReference ref = getBeanFactoryLocator(target).useBeanFactory(key);
161                this.beanFactoryReferences.put(target, ref);
162                return ref;
163        }
164
165        /**
166         * Determine the BeanFactoryLocator to obtain the BeanFactoryReference from.
167         * <p>The default implementation exposes Spring's default
168         * {@link ContextSingletonBeanFactoryLocator}.
169         * @param target the target bean to autowire
170         * @return the BeanFactoryLocator to use (never {@code null})
171         * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator#getInstance()
172         */
173        protected BeanFactoryLocator getBeanFactoryLocator(Object target) {
174                return ContextSingletonBeanFactoryLocator.getInstance();
175        }
176
177        /**
178         * Determine the BeanFactoryLocator key to use. This typically indicates
179         * the bean name of the ApplicationContext definition in
180         * <strong>classpath*:beanRefContext.xml</strong> resource files.
181         * <p>The default is {@code null}, indicating the single
182         * ApplicationContext defined in the locator. This must be overridden
183         * if more than one shared ApplicationContext definition is available.
184         * @param target the target bean to autowire
185         * @return the BeanFactoryLocator key to use (or {@code null} for
186         * referring to the single ApplicationContext defined in the locator)
187         */
188        protected String getBeanFactoryLocatorKey(Object target) {
189                return null;
190        }
191
192
193        /**
194         * Release the factory which has been used for autowiring the target bean.
195         * @param invocationContext the EJB3 invocation context
196         */
197        @PreDestroy
198        @PrePassivate
199        public void releaseBean(InvocationContext invocationContext) {
200                doReleaseBean(invocationContext.getTarget());
201                try {
202                        invocationContext.proceed();
203                }
204                catch (RuntimeException ex) {
205                        throw ex;
206                }
207                catch (Exception ex) {
208                        // Cannot declare a checked exception on WebSphere here - so we need to wrap.
209                        throw new EJBException(ex);
210                }
211        }
212
213        /**
214         * Actually release the BeanFactoryReference for the given target bean.
215         * @param target the target bean to release
216         */
217        protected void doReleaseBean(Object target) {
218                BeanFactoryReference ref = this.beanFactoryReferences.remove(target);
219                if (ref != null) {
220                        ref.release();
221                }
222        }
223
224}