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