1   /*
2    * Copyright 2002-2013 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.ejb.interceptor;
18  
19  import java.util.Map;
20  import java.util.WeakHashMap;
21  import javax.annotation.PostConstruct;
22  import javax.annotation.PreDestroy;
23  import javax.ejb.EJBException;
24  import javax.ejb.PostActivate;
25  import javax.ejb.PrePassivate;
26  import javax.interceptor.InvocationContext;
27  
28  import org.springframework.beans.factory.BeanFactory;
29  import org.springframework.beans.factory.access.BeanFactoryLocator;
30  import org.springframework.beans.factory.access.BeanFactoryReference;
31  import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
32  import org.springframework.context.ApplicationContext;
33  import org.springframework.context.access.ContextSingletonBeanFactoryLocator;
34  
35  /**
36   * EJB3-compliant interceptor class that injects Spring beans into
37   * fields and methods which are annotated with {@code @Autowired}.
38   * Performs injection after construction as well as after activation
39   * of a passivated bean.
40   *
41   * <p>To be applied through an {@code @Interceptors} annotation in
42   * the EJB Session Bean or Message-Driven Bean class, or through an
43   * {@code interceptor-binding} XML element in the EJB deployment descriptor.
44   *
45   * <p>Delegates to Spring's {@link AutowiredAnnotationBeanPostProcessor}
46   * underneath, allowing for customization of its specific settings through
47   * overriding the {@link #configureBeanPostProcessor} template method.
48   *
49   * <p>The actual BeanFactory to obtain Spring beans from is determined
50   * by the {@link #getBeanFactory} template method. The default implementation
51   * obtains the Spring {@link ContextSingletonBeanFactoryLocator}, initialized
52   * from the default resource location <strong>classpath*:beanRefContext.xml</strong>,
53   * and obtains the single ApplicationContext defined there.
54   *
55   * <p><b>NOTE: If you have more than one shared ApplicationContext definition available
56   * in your EJB class loader, you need to override the {@link #getBeanFactoryLocatorKey}
57   * method and provide a specific locator key for each autowired EJB.</b>
58   * Alternatively, override the {@link #getBeanFactory} template method and
59   * obtain the target factory explicitly.
60   *
61   * <p><b>WARNING: Do not define the same bean as Spring-managed bean and as
62   * EJB3 session bean in the same deployment unit.</b> In particular, be
63   * careful when using the {@code &lt;context:component-scan&gt;} feature
64   * in combination with the deployment of Spring-based EJB3 session beans:
65   * Make sure that the EJB3 session beans are <i>not</i> autodetected as
66   * Spring-managed beans as well, using appropriate package restrictions.
67   *
68   * @author Juergen Hoeller
69   * @since 2.5.1
70   * @see org.springframework.beans.factory.annotation.Autowired
71   * @see org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
72   * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator
73   * @see #getBeanFactoryLocatorKey
74   */
75  public class SpringBeanAutowiringInterceptor {
76  
77  	/*
78  	 * We're keeping the BeanFactoryReference per target object in order to
79  	 * allow for using a shared interceptor instance on pooled target beans.
80  	 * This is not strictly necessary for EJB3 Session Beans and Message-Driven
81  	 * Beans, where interceptor instances get created per target bean instance.
82  	 * It simply protects against future usage of the interceptor in a shared scenario.
83  	 */
84  	private final Map<Object, BeanFactoryReference> beanFactoryReferences =
85  			new WeakHashMap<Object, BeanFactoryReference>();
86  
87  
88  	/**
89  	 * Autowire the target bean after construction as well as after passivation.
90  	 * @param invocationContext the EJB3 invocation context
91  	 */
92  	@PostConstruct
93  	@PostActivate
94  	public void autowireBean(InvocationContext invocationContext) {
95  		doAutowireBean(invocationContext.getTarget());
96  		try {
97  			invocationContext.proceed();
98  		}
99  		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 }