001/*
002 * Copyright 2002-2012 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.access;
018
019import java.lang.reflect.InvocationTargetException;
020import java.lang.reflect.Method;
021import javax.ejb.CreateException;
022import javax.ejb.EJBLocalHome;
023import javax.ejb.EJBLocalObject;
024import javax.naming.NamingException;
025
026import org.aopalliance.intercept.MethodInvocation;
027
028/**
029 * Invoker for a local Stateless Session Bean.
030 * Designed for EJB 2.x, but works for EJB 3 Session Beans as well.
031 *
032 * <p>Caches the home object, since a local EJB home can never go stale.
033 * See {@link org.springframework.jndi.JndiObjectLocator} for info on
034 * how to specify the JNDI location of the target EJB.
035 *
036 * <p>In a bean container, this class is normally best used as a singleton. However,
037 * if that bean container pre-instantiates singletons (as do the XML ApplicationContext
038 * variants) you may have a problem if the bean container is loaded before the EJB
039 * container loads the target EJB. That is because by default the JNDI lookup will be
040 * performed in the init method of this class and cached, but the EJB will not have been
041 * bound at the target location yet. The best solution is to set the lookupHomeOnStartup
042 * property to false, in which case the home will be fetched on first access to the EJB.
043 * (This flag is only true by default for backwards compatibility reasons).
044 *
045 * @author Rod Johnson
046 * @author Juergen Hoeller
047 * @see AbstractSlsbInvokerInterceptor#setLookupHomeOnStartup
048 * @see AbstractSlsbInvokerInterceptor#setCacheHome
049 */
050public class LocalSlsbInvokerInterceptor extends AbstractSlsbInvokerInterceptor {
051
052        private volatile boolean homeAsComponent = false;
053
054
055        /**
056         * This implementation "creates" a new EJB instance for each invocation.
057         * Can be overridden for custom invocation strategies.
058         * <p>Alternatively, override {@link #getSessionBeanInstance} and
059         * {@link #releaseSessionBeanInstance} to change EJB instance creation,
060         * for example to hold a single shared EJB instance.
061         */
062        @Override
063        public Object invokeInContext(MethodInvocation invocation) throws Throwable {
064                Object ejb = null;
065                try {
066                        ejb = getSessionBeanInstance();
067                        Method method = invocation.getMethod();
068                        if (method.getDeclaringClass().isInstance(ejb)) {
069                                // directly implemented
070                                return method.invoke(ejb, invocation.getArguments());
071                        }
072                        else {
073                                // not directly implemented
074                                Method ejbMethod = ejb.getClass().getMethod(method.getName(), method.getParameterTypes());
075                                return ejbMethod.invoke(ejb, invocation.getArguments());
076                        }
077                }
078                catch (InvocationTargetException ex) {
079                        Throwable targetEx = ex.getTargetException();
080                        if (logger.isDebugEnabled()) {
081                                logger.debug("Method of local EJB [" + getJndiName() + "] threw exception", targetEx);
082                        }
083                        if (targetEx instanceof CreateException) {
084                                throw new EjbAccessException("Could not create local EJB [" + getJndiName() + "]", targetEx);
085                        }
086                        else {
087                                throw targetEx;
088                        }
089                }
090                catch (NamingException ex) {
091                        throw new EjbAccessException("Failed to locate local EJB [" + getJndiName() + "]", ex);
092                }
093                catch (IllegalAccessException ex) {
094                        throw new EjbAccessException("Could not access method [" + invocation.getMethod().getName() +
095                                "] of local EJB [" + getJndiName() + "]", ex);
096                }
097                finally {
098                        if (ejb instanceof EJBLocalObject) {
099                                releaseSessionBeanInstance((EJBLocalObject) ejb);
100                        }
101                }
102        }
103
104        /**
105         * Check for EJB3-style home object that serves as EJB component directly.
106         */
107        @Override
108        protected Method getCreateMethod(Object home) throws EjbAccessException {
109                if (this.homeAsComponent) {
110                        return null;
111                }
112                if (!(home instanceof EJBLocalHome)) {
113                        // An EJB3 Session Bean...
114                        this.homeAsComponent = true;
115                        return null;
116                }
117                return super.getCreateMethod(home);
118        }
119
120        /**
121         * Return an EJB instance to delegate the call to.
122         * Default implementation delegates to newSessionBeanInstance.
123         * @throws NamingException if thrown by JNDI
124         * @throws InvocationTargetException if thrown by the create method
125         * @see #newSessionBeanInstance
126         */
127        protected Object getSessionBeanInstance() throws NamingException, InvocationTargetException {
128                return newSessionBeanInstance();
129        }
130
131        /**
132         * Release the given EJB instance.
133         * Default implementation delegates to removeSessionBeanInstance.
134         * @param ejb the EJB instance to release
135         * @see #removeSessionBeanInstance
136         */
137        protected void releaseSessionBeanInstance(EJBLocalObject ejb) {
138                removeSessionBeanInstance(ejb);
139        }
140
141        /**
142         * Return a new instance of the stateless session bean.
143         * Can be overridden to change the algorithm.
144         * @throws NamingException if thrown by JNDI
145         * @throws InvocationTargetException if thrown by the create method
146         * @see #create
147         */
148        protected Object newSessionBeanInstance() throws NamingException, InvocationTargetException {
149                if (logger.isDebugEnabled()) {
150                        logger.debug("Trying to create reference to local EJB");
151                }
152                Object ejbInstance = create();
153                if (logger.isDebugEnabled()) {
154                        logger.debug("Obtained reference to local EJB: " + ejbInstance);
155                }
156                return ejbInstance;
157        }
158
159        /**
160         * Remove the given EJB instance.
161         * @param ejb the EJB instance to remove
162         * @see javax.ejb.EJBLocalObject#remove()
163         */
164        protected void removeSessionBeanInstance(EJBLocalObject ejb) {
165                if (ejb != null && !this.homeAsComponent) {
166                        try {
167                                ejb.remove();
168                        }
169                        catch (Throwable ex) {
170                                logger.warn("Could not invoke 'remove' on local EJB proxy", ex);
171                        }
172                }
173        }
174
175}