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 java.rmi.RemoteException;
022import javax.ejb.EJBHome;
023import javax.ejb.EJBObject;
024import javax.naming.NamingException;
025import javax.rmi.PortableRemoteObject;
026
027import org.aopalliance.intercept.MethodInvocation;
028
029import org.springframework.remoting.RemoteConnectFailureException;
030import org.springframework.remoting.RemoteLookupFailureException;
031import org.springframework.remoting.rmi.RmiClientInterceptorUtils;
032
033/**
034 * Base class for interceptors proxying remote Stateless Session Beans.
035 * Designed for EJB 2.x, but works for EJB 3 Session Beans as well.
036 *
037 * <p>Such an interceptor must be the last interceptor in the advice chain.
038 * In this case, there is no target object.
039 *
040 * @author Rod Johnson
041 * @author Juergen Hoeller
042 */
043public abstract class AbstractRemoteSlsbInvokerInterceptor extends AbstractSlsbInvokerInterceptor {
044
045        private Class<?> homeInterface;
046
047        private boolean refreshHomeOnConnectFailure = false;
048
049        private volatile boolean homeAsComponent = false;
050
051
052
053        /**
054         * Set a home interface that this invoker will narrow to before performing
055         * the parameterless SLSB {@code create()} call that returns the actual
056         * SLSB proxy.
057         * <p>Default is none, which will work on all J2EE servers that are not based
058         * on CORBA. A plain {@code javax.ejb.EJBHome} interface is known to be
059         * sufficient to make a WebSphere 5.0 Remote SLSB work. On other servers,
060         * the specific home interface for the target SLSB might be necessary.
061         */
062        public void setHomeInterface(Class<?> homeInterface) {
063                if (homeInterface != null && !homeInterface.isInterface()) {
064                        throw new IllegalArgumentException(
065                                        "Home interface class [" + homeInterface.getClass() + "] is not an interface");
066                }
067                this.homeInterface = homeInterface;
068        }
069
070        /**
071         * Set whether to refresh the EJB home on connect failure.
072         * Default is "false".
073         * <p>Can be turned on to allow for hot restart of the EJB server.
074         * If a cached EJB home throws an RMI exception that indicates a
075         * remote connect failure, a fresh home will be fetched and the
076         * invocation will be retried.
077         * @see java.rmi.ConnectException
078         * @see java.rmi.ConnectIOException
079         * @see java.rmi.NoSuchObjectException
080         */
081        public void setRefreshHomeOnConnectFailure(boolean refreshHomeOnConnectFailure) {
082                this.refreshHomeOnConnectFailure = refreshHomeOnConnectFailure;
083        }
084
085        @Override
086        protected boolean isHomeRefreshable() {
087                return this.refreshHomeOnConnectFailure;
088        }
089
090
091        /**
092         * This overridden lookup implementation performs a narrow operation
093         * after the JNDI lookup, provided that a home interface is specified.
094         * @see #setHomeInterface
095         * @see javax.rmi.PortableRemoteObject#narrow
096         */
097        @Override
098        protected Object lookup() throws NamingException {
099                Object homeObject = super.lookup();
100                if (this.homeInterface != null) {
101                        try {
102                                homeObject = PortableRemoteObject.narrow(homeObject, this.homeInterface);
103                        }
104                        catch (ClassCastException ex) {
105                                throw new RemoteLookupFailureException(
106                                                "Could not narrow EJB home stub to home interface [" + this.homeInterface.getName() + "]", ex);
107                        }
108                }
109                return homeObject;
110        }
111
112        /**
113         * Check for EJB3-style home object that serves as EJB component directly.
114         */
115        @Override
116        protected Method getCreateMethod(Object home) throws EjbAccessException {
117                if (this.homeAsComponent) {
118                        return null;
119                }
120                if (!(home instanceof EJBHome)) {
121                        // An EJB3 Session Bean...
122                        this.homeAsComponent = true;
123                        return null;
124                }
125                return super.getCreateMethod(home);
126        }
127
128
129        /**
130         * Fetches an EJB home object and delegates to {@code doInvoke}.
131         * <p>If configured to refresh on connect failure, it will call
132         * {@link #refreshAndRetry} on corresponding RMI exceptions.
133         * @see #getHome
134         * @see #doInvoke
135         * @see #refreshAndRetry
136         */
137        @Override
138        public Object invokeInContext(MethodInvocation invocation) throws Throwable {
139                try {
140                        return doInvoke(invocation);
141                }
142                catch (RemoteConnectFailureException ex) {
143                        return handleRemoteConnectFailure(invocation, ex);
144                }
145                catch (RemoteException ex) {
146                        if (isConnectFailure(ex)) {
147                                return handleRemoteConnectFailure(invocation, ex);
148                        }
149                        else {
150                                throw ex;
151                        }
152                }
153        }
154
155        /**
156         * Determine whether the given RMI exception indicates a connect failure.
157         * <p>The default implementation delegates to RmiClientInterceptorUtils.
158         * @param ex the RMI exception to check
159         * @return whether the exception should be treated as connect failure
160         * @see org.springframework.remoting.rmi.RmiClientInterceptorUtils#isConnectFailure
161         */
162        protected boolean isConnectFailure(RemoteException ex) {
163                return RmiClientInterceptorUtils.isConnectFailure(ex);
164        }
165
166        private Object handleRemoteConnectFailure(MethodInvocation invocation, Exception ex) throws Throwable {
167                if (this.refreshHomeOnConnectFailure) {
168                        if (logger.isDebugEnabled()) {
169                                logger.debug("Could not connect to remote EJB [" + getJndiName() + "] - retrying", ex);
170                        }
171                        else if (logger.isWarnEnabled()) {
172                                logger.warn("Could not connect to remote EJB [" + getJndiName() + "] - retrying");
173                        }
174                        return refreshAndRetry(invocation);
175                }
176                else {
177                        throw ex;
178                }
179        }
180
181        /**
182         * Refresh the EJB home object and retry the given invocation.
183         * Called by invoke on connect failure.
184         * @param invocation the AOP method invocation
185         * @return the invocation result, if any
186         * @throws Throwable in case of invocation failure
187         * @see #invoke
188         */
189        protected Object refreshAndRetry(MethodInvocation invocation) throws Throwable {
190                try {
191                        refreshHome();
192                }
193                catch (NamingException ex) {
194                        throw new RemoteLookupFailureException("Failed to locate remote EJB [" + getJndiName() + "]", ex);
195                }
196                return doInvoke(invocation);
197        }
198
199
200        /**
201         * Perform the given invocation on the current EJB home.
202         * Template method to be implemented by subclasses.
203         * @param invocation the AOP method invocation
204         * @return the invocation result, if any
205         * @throws Throwable in case of invocation failure
206         * @see #getHome
207         * @see #newSessionBeanInstance
208         */
209        protected abstract Object doInvoke(MethodInvocation invocation) throws Throwable;
210
211
212        /**
213         * Return a new instance of the stateless session bean.
214         * To be invoked by concrete remote SLSB invoker subclasses.
215         * <p>Can be overridden to change the algorithm.
216         * @throws NamingException if thrown by JNDI
217         * @throws InvocationTargetException if thrown by the create method
218         * @see #create
219         */
220        protected Object newSessionBeanInstance() throws NamingException, InvocationTargetException {
221                if (logger.isDebugEnabled()) {
222                        logger.debug("Trying to create reference to remote EJB");
223                }
224                Object ejbInstance = create();
225                if (logger.isDebugEnabled()) {
226                        logger.debug("Obtained reference to remote EJB: " + ejbInstance);
227                }
228                return ejbInstance;
229        }
230
231        /**
232         * Remove the given EJB instance.
233         * To be invoked by concrete remote SLSB invoker subclasses.
234         * @param ejb the EJB instance to remove
235         * @see javax.ejb.EJBObject#remove
236         */
237        protected void removeSessionBeanInstance(EJBObject ejb) {
238                if (ejb != null && !this.homeAsComponent) {
239                        try {
240                                ejb.remove();
241                        }
242                        catch (Throwable ex) {
243                                logger.warn("Could not invoke 'remove' on remote EJB proxy", ex);
244                        }
245                }
246        }
247
248}