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.rmi.RemoteException;
021import javax.ejb.CreateException;
022import javax.ejb.EJBObject;
023import javax.naming.NamingException;
024
025import org.aopalliance.intercept.MethodInvocation;
026
027import org.springframework.beans.factory.DisposableBean;
028import org.springframework.remoting.RemoteLookupFailureException;
029import org.springframework.remoting.rmi.RmiClientInterceptorUtils;
030
031/**
032 * Basic invoker for a remote Stateless Session Bean.
033 * Designed for EJB 2.x, but works for EJB 3 Session Beans as well.
034 *
035 * <p>"Creates" a new EJB instance for each invocation, or caches the session
036 * bean instance for all invocations (see {@link #setCacheSessionBean}).
037 * See {@link org.springframework.jndi.JndiObjectLocator} for info on
038 * how to specify the JNDI location of the target EJB.
039 *
040 * <p>In a bean container, this class is normally best used as a singleton. However,
041 * if that bean container pre-instantiates singletons (as do the XML ApplicationContext
042 * variants) you may have a problem if the bean container is loaded before the EJB
043 * container loads the target EJB. That is because by default the JNDI lookup will be
044 * performed in the init method of this class and cached, but the EJB will not have been
045 * bound at the target location yet. The best solution is to set the "lookupHomeOnStartup"
046 * property to "false", in which case the home will be fetched on first access to the EJB.
047 * (This flag is only true by default for backwards compatibility reasons).
048 *
049 * <p>This invoker is typically used with an RMI business interface, which serves
050 * as super-interface of the EJB component interface. Alternatively, this invoker
051 * can also proxy a remote SLSB with a matching non-RMI business interface, i.e. an
052 * interface that mirrors the EJB business methods but does not declare RemoteExceptions.
053 * In the latter case, RemoteExceptions thrown by the EJB stub will automatically get
054 * converted to Spring's unchecked RemoteAccessException.
055 *
056 * @author Rod Johnson
057 * @author Juergen Hoeller
058 * @since 09.05.2003
059 * @see org.springframework.remoting.RemoteAccessException
060 * @see AbstractSlsbInvokerInterceptor#setLookupHomeOnStartup
061 * @see AbstractSlsbInvokerInterceptor#setCacheHome
062 * @see AbstractRemoteSlsbInvokerInterceptor#setRefreshHomeOnConnectFailure
063 */
064public class SimpleRemoteSlsbInvokerInterceptor extends AbstractRemoteSlsbInvokerInterceptor
065                implements DisposableBean {
066
067        private boolean cacheSessionBean = false;
068
069        private Object beanInstance;
070
071        private final Object beanInstanceMonitor = new Object();
072
073
074        /**
075         * Set whether to cache the actual session bean object.
076         * <p>Off by default for standard EJB compliance. Turn this flag
077         * on to optimize session bean access for servers that are
078         * known to allow for caching the actual session bean object.
079         * @see #setCacheHome
080         */
081        public void setCacheSessionBean(boolean cacheSessionBean) {
082                this.cacheSessionBean = cacheSessionBean;
083        }
084
085
086        /**
087         * This implementation "creates" a new EJB instance for each invocation.
088         * Can be overridden for custom invocation strategies.
089         * <p>Alternatively, override {@link #getSessionBeanInstance} and
090         * {@link #releaseSessionBeanInstance} to change EJB instance creation,
091         * for example to hold a single shared EJB component instance.
092         */
093        @Override
094        protected Object doInvoke(MethodInvocation invocation) throws Throwable {
095                Object ejb = null;
096                try {
097                        ejb = getSessionBeanInstance();
098                        return RmiClientInterceptorUtils.invokeRemoteMethod(invocation, ejb);
099                }
100                catch (NamingException ex) {
101                        throw new RemoteLookupFailureException("Failed to locate remote EJB [" + getJndiName() + "]", ex);
102                }
103                catch (InvocationTargetException ex) {
104                        Throwable targetEx = ex.getTargetException();
105                        if (targetEx instanceof RemoteException) {
106                                RemoteException rex = (RemoteException) targetEx;
107                                throw RmiClientInterceptorUtils.convertRmiAccessException(
108                                        invocation.getMethod(), rex, isConnectFailure(rex), getJndiName());
109                        }
110                        else if (targetEx instanceof CreateException) {
111                                throw RmiClientInterceptorUtils.convertRmiAccessException(
112                                        invocation.getMethod(), targetEx, "Could not create remote EJB [" + getJndiName() + "]");
113                        }
114                        throw targetEx;
115                }
116                finally {
117                        if (ejb instanceof EJBObject) {
118                                releaseSessionBeanInstance((EJBObject) ejb);
119                        }
120                }
121        }
122
123        /**
124         * Return an EJB component instance to delegate the call to.
125         * <p>The default implementation delegates to {@link #newSessionBeanInstance}.
126         * @return the EJB component instance
127         * @throws NamingException if thrown by JNDI
128         * @throws InvocationTargetException if thrown by the create method
129         * @see #newSessionBeanInstance
130         */
131        protected Object getSessionBeanInstance() throws NamingException, InvocationTargetException {
132                if (this.cacheSessionBean) {
133                        synchronized (this.beanInstanceMonitor) {
134                                if (this.beanInstance == null) {
135                                        this.beanInstance = newSessionBeanInstance();
136                                }
137                                return this.beanInstance;
138                        }
139                }
140                else {
141                        return newSessionBeanInstance();
142                }
143        }
144
145        /**
146         * Release the given EJB instance.
147         * <p>The default implementation delegates to {@link #removeSessionBeanInstance}.
148         * @param ejb the EJB component instance to release
149         * @see #removeSessionBeanInstance
150         */
151        protected void releaseSessionBeanInstance(EJBObject ejb) {
152                if (!this.cacheSessionBean) {
153                        removeSessionBeanInstance(ejb);
154                }
155        }
156
157        /**
158         * Reset the cached session bean instance, if necessary.
159         */
160        @Override
161        protected void refreshHome() throws NamingException {
162                super.refreshHome();
163                if (this.cacheSessionBean) {
164                        synchronized (this.beanInstanceMonitor) {
165                                this.beanInstance = null;
166                        }
167                }
168        }
169
170        /**
171         * Remove the cached session bean instance, if necessary.
172         */
173        @Override
174        public void destroy() {
175                if (this.cacheSessionBean) {
176                        synchronized (this.beanInstanceMonitor) {
177                                if (this.beanInstance instanceof EJBObject) {
178                                        removeSessionBeanInstance((EJBObject) this.beanInstance);
179                                }
180                        }
181                }
182        }
183
184}