/* * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.ejb.interceptor; import java.util.Map; import java.util.WeakHashMap; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.ejb.EJBException; import javax.ejb.PostActivate; import javax.ejb.PrePassivate; import javax.interceptor.InvocationContext; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.access.BeanFactoryLocator; import org.springframework.beans.factory.access.BeanFactoryReference; import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; import org.springframework.context.ApplicationContext; import org.springframework.context.access.ContextSingletonBeanFactoryLocator; /** * EJB3-compliant interceptor class that injects Spring beans into * fields and methods which are annotated with <code>@Autowired</code>. * Performs injection after construction as well as after activation * of a passivated bean. * * <p>To be applied through an <code>@Interceptors</code> annotation in * the EJB Session Bean or Message-Driven Bean class, or through an * <code>interceptor-binding</code> XML element in the EJB deployment descriptor. * * <p>Delegates to Spring's {@link AutowiredAnnotationBeanPostProcessor} * underneath, allowing for customization of its specific settings through * overriding the {@link #configureBeanPostProcessor} template method. * * <p>The actual BeanFactory to obtain Spring beans from is determined * by the {@link #getBeanFactory} template method. The default implementation * obtains the Spring {@link ContextSingletonBeanFactoryLocator}, initialized * from the default resource location <strong>classpath*:beanRefContext.xml</strong>, * and obtains the single ApplicationContext defined there. * * <p><b>NOTE: If you have more than one shared ApplicationContext definition available * in your EJB class loader, you need to override the {@link #getBeanFactoryLocatorKey} * method and provide a specific locator key for each autowired EJB.</b> * Alternatively, override the {@link #getBeanFactory} template method and * obtain the target factory explicitly. * * <p><b>WARNING: Do not define the same bean as Spring-managed bean and as * EJB3 session bean in the same deployment unit.</b> In particular, be * careful when using the <code><context:component-scan></code> feature * in combination with the deployment of Spring-based EJB3 session beans: * Make sure that the EJB3 session beans are <i>not</i> autodetected as * Spring-managed beans as well, using appropriate package restrictions. * * @author Juergen Hoeller * @since 2.5.1 * @see org.springframework.beans.factory.annotation.Autowired * @see org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator * @see #getBeanFactoryLocatorKey * @see org.springframework.ejb.support.AbstractEnterpriseBean#setBeanFactoryLocator * @see org.springframework.ejb.support.AbstractEnterpriseBean#setBeanFactoryLocatorKey */ public class SpringBeanAutowiringInterceptor { /* * We're keeping the BeanFactoryReference per target object in order to * allow for using a shared interceptor instance on pooled target beans. * This is not strictly necessary for EJB3 Session Beans and Message-Driven * Beans, where interceptor instances get created per target bean instance. * It simply protects against future usage of the interceptor in a shared scenario. */ private final Map<Object, BeanFactoryReference> beanFactoryReferences = new WeakHashMap<Object, BeanFactoryReference>(); /** * Autowire the target bean after construction as well as after passivation. * @param invocationContext the EJB3 invocation context */ @PostConstruct @PostActivate public void autowireBean(InvocationContext invocationContext) { doAutowireBean(invocationContext.getTarget()); try { invocationContext.proceed(); } catch (RuntimeException ex) { throw ex; } catch (Exception ex) { // Cannot declare a checked exception on WebSphere here - so we need to wrap. throw new EJBException(ex); } } /** * Actually autowire the target bean after construction/passivation. * @param target the target bean to autowire */ protected void doAutowireBean(Object target) { AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); configureBeanPostProcessor(bpp, target); bpp.setBeanFactory(getBeanFactory(target)); bpp.processInjection(target); } /** * Template method for configuring the * {@link AutowiredAnnotationBeanPostProcessor} used for autowiring. * @param processor the AutowiredAnnotationBeanPostProcessor to configure * @param target the target bean to autowire with this processor */ protected void configureBeanPostProcessor(AutowiredAnnotationBeanPostProcessor processor, Object target) { } /** * Determine the BeanFactory for autowiring the given target bean. * @param target the target bean to autowire * @return the BeanFactory to use (never <code>null</code>) * @see #getBeanFactoryReference */ protected BeanFactory getBeanFactory(Object target) { BeanFactory factory = getBeanFactoryReference(target).getFactory(); if (factory instanceof ApplicationContext) { factory = ((ApplicationContext) factory).getAutowireCapableBeanFactory(); } return factory; } /** * Determine the BeanFactoryReference for the given target bean. * <p>The default implementation delegates to {@link #getBeanFactoryLocator} * and {@link #getBeanFactoryLocatorKey}. * @param target the target bean to autowire * @return the BeanFactoryReference to use (never <code>null</code>) * @see #getBeanFactoryLocator * @see #getBeanFactoryLocatorKey * @see org.springframework.beans.factory.access.BeanFactoryLocator#useBeanFactory(String) */ protected BeanFactoryReference getBeanFactoryReference(Object target) { String key = getBeanFactoryLocatorKey(target); BeanFactoryReference ref = getBeanFactoryLocator(target).useBeanFactory(key); this.beanFactoryReferences.put(target, ref); return ref; } /** * Determine the BeanFactoryLocator to obtain the BeanFactoryReference from. * <p>The default implementation exposes Spring's default * {@link ContextSingletonBeanFactoryLocator}. * @param target the target bean to autowire * @return the BeanFactoryLocator to use (never <code>null</code>) * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator#getInstance() */ protected BeanFactoryLocator getBeanFactoryLocator(Object target) { return ContextSingletonBeanFactoryLocator.getInstance(); } /** * Determine the BeanFactoryLocator key to use. This typically indicates * the bean name of the ApplicationContext definition in * <strong>classpath*:beanRefContext.xml</strong> resource files. * <p>The default is <code>null</code>, indicating the single * ApplicationContext defined in the locator. This must be overridden * if more than one shared ApplicationContext definition is available. * @param target the target bean to autowire * @return the BeanFactoryLocator key to use (or <code>null</code> for * referring to the single ApplicationContext defined in the locator) */ protected String getBeanFactoryLocatorKey(Object target) { return null; } /** * Release the factory which has been used for autowiring the target bean. * @param invocationContext the EJB3 invocation context */ @PreDestroy @PrePassivate public void releaseBean(InvocationContext invocationContext) { doReleaseBean(invocationContext.getTarget()); try { invocationContext.proceed(); } catch (RuntimeException ex) { throw ex; } catch (Exception ex) { // Cannot declare a checked exception on WebSphere here - so we need to wrap. throw new EJBException(ex); } } /** * Actually release the BeanFactoryReference for the given target bean. * @param target the target bean to release */ protected void doReleaseBean(Object target) { BeanFactoryReference ref = this.beanFactoryReferences.remove(target); if (ref != null) { ref.release(); } } }