001/*
002 * Copyright 2002-2013 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.remoting.rmi;
018
019import java.lang.reflect.InvocationTargetException;
020import java.lang.reflect.Method;
021import java.net.SocketException;
022import java.rmi.ConnectException;
023import java.rmi.ConnectIOException;
024import java.rmi.NoSuchObjectException;
025import java.rmi.RemoteException;
026import java.rmi.StubNotFoundException;
027import java.rmi.UnknownHostException;
028
029import org.aopalliance.intercept.MethodInvocation;
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032import org.omg.CORBA.COMM_FAILURE;
033import org.omg.CORBA.CompletionStatus;
034import org.omg.CORBA.NO_RESPONSE;
035import org.omg.CORBA.SystemException;
036
037import org.springframework.remoting.RemoteAccessException;
038import org.springframework.remoting.RemoteConnectFailureException;
039import org.springframework.remoting.RemoteProxyFailureException;
040import org.springframework.util.ReflectionUtils;
041
042/**
043 * Factored-out methods for performing invocations within an RMI client.
044 * Can handle both RMI and non-RMI service interfaces working on an RMI stub.
045 *
046 * <p>Note: This is an SPI class, not intended to be used by applications.
047 *
048 * @author Juergen Hoeller
049 * @since 1.1
050 */
051public abstract class RmiClientInterceptorUtils {
052
053        private static final Log logger = LogFactory.getLog(RmiClientInterceptorUtils.class);
054
055
056        /**
057         * Perform a raw method invocation on the given RMI stub,
058         * letting reflection exceptions through as-is.
059         * @param invocation the AOP MethodInvocation
060         * @param stub the RMI stub
061         * @return the invocation result, if any
062         * @throws InvocationTargetException if thrown by reflection
063         */
064        public static Object invokeRemoteMethod(MethodInvocation invocation, Object stub)
065                        throws InvocationTargetException {
066
067                Method method = invocation.getMethod();
068                try {
069                        if (method.getDeclaringClass().isInstance(stub)) {
070                                // directly implemented
071                                return method.invoke(stub, invocation.getArguments());
072                        }
073                        else {
074                                // not directly implemented
075                                Method stubMethod = stub.getClass().getMethod(method.getName(), method.getParameterTypes());
076                                return stubMethod.invoke(stub, invocation.getArguments());
077                        }
078                }
079                catch (InvocationTargetException ex) {
080                        throw ex;
081                }
082                catch (NoSuchMethodException ex) {
083                        throw new RemoteProxyFailureException("No matching RMI stub method found for: " + method, ex);
084                }
085                catch (Throwable ex) {
086                        throw new RemoteProxyFailureException("Invocation of RMI stub method failed: " + method, ex);
087                }
088        }
089
090        /**
091         * Wrap the given arbitrary exception that happened during remote access
092         * in either a RemoteException or a Spring RemoteAccessException (if the
093         * method signature does not support RemoteException).
094         * <p>Only call this for remote access exceptions, not for exceptions
095         * thrown by the target service itself!
096         * @param method the invoked method
097         * @param ex the exception that happened, to be used as cause for the
098         * RemoteAccessException or RemoteException
099         * @param message the message for the RemoteAccessException respectively
100         * RemoteException
101         * @return the exception to be thrown to the caller
102         */
103        public static Exception convertRmiAccessException(Method method, Throwable ex, String message) {
104                if (logger.isDebugEnabled()) {
105                        logger.debug(message, ex);
106                }
107                if (ReflectionUtils.declaresException(method, RemoteException.class)) {
108                        return new RemoteException(message, ex);
109                }
110                else {
111                        return new RemoteAccessException(message, ex);
112                }
113        }
114
115        /**
116         * Convert the given RemoteException that happened during remote access
117         * to Spring's RemoteAccessException if the method signature does not
118         * support RemoteException. Else, return the original RemoteException.
119         * @param method the invoked method
120         * @param ex the RemoteException that happened
121         * @param serviceName the name of the service (for debugging purposes)
122         * @return the exception to be thrown to the caller
123         */
124        public static Exception convertRmiAccessException(Method method, RemoteException ex, String serviceName) {
125                return convertRmiAccessException(method, ex, isConnectFailure(ex), serviceName);
126        }
127
128        /**
129         * Convert the given RemoteException that happened during remote access
130         * to Spring's RemoteAccessException if the method signature does not
131         * support RemoteException. Else, return the original RemoteException.
132         * @param method the invoked method
133         * @param ex the RemoteException that happened
134         * @param isConnectFailure whether the given exception should be considered
135         * a connect failure
136         * @param serviceName the name of the service (for debugging purposes)
137         * @return the exception to be thrown to the caller
138         */
139        public static Exception convertRmiAccessException(
140                        Method method, RemoteException ex, boolean isConnectFailure, String serviceName) {
141
142                if (logger.isDebugEnabled()) {
143                        logger.debug("Remote service [" + serviceName + "] threw exception", ex);
144                }
145                if (ReflectionUtils.declaresException(method, ex.getClass())) {
146                        return ex;
147                }
148                else {
149                        if (isConnectFailure) {
150                                return new RemoteConnectFailureException("Could not connect to remote service [" + serviceName + "]", ex);
151                        }
152                        else {
153                                return new RemoteAccessException("Could not access remote service [" + serviceName + "]", ex);
154                        }
155                }
156        }
157
158        /**
159         * Determine whether the given RMI exception indicates a connect failure.
160         * <p>Treats RMI's ConnectException, ConnectIOException, UnknownHostException,
161         * NoSuchObjectException and StubNotFoundException as connect failure.
162         * @param ex the RMI exception to check
163         * @return whether the exception should be treated as connect failure
164         * @see java.rmi.ConnectException
165         * @see java.rmi.ConnectIOException
166         * @see java.rmi.UnknownHostException
167         * @see java.rmi.NoSuchObjectException
168         * @see java.rmi.StubNotFoundException
169         */
170        public static boolean isConnectFailure(RemoteException ex) {
171                return (ex instanceof ConnectException || ex instanceof ConnectIOException ||
172                                ex instanceof UnknownHostException || ex instanceof NoSuchObjectException ||
173                                ex instanceof StubNotFoundException || ex.getCause() instanceof SocketException ||
174                                isCorbaConnectFailure(ex.getCause()));
175        }
176
177        /**
178         * Check whether the given RMI exception root cause indicates a CORBA
179         * connection failure.
180         * <p>This is relevant on the IBM JVM, in particular for WebSphere EJB clients.
181         * <p>See the
182         * <a href="https://www.redbooks.ibm.com/Redbooks.nsf/RedbookAbstracts/tips0243.html">IBM website</code>
183         * for details.
184         * @param ex the RMI exception to check
185         */
186        private static boolean isCorbaConnectFailure(Throwable ex) {
187                return ((ex instanceof COMM_FAILURE || ex instanceof NO_RESPONSE) &&
188                                ((SystemException) ex).completed == CompletionStatus.COMPLETED_NO);
189        }
190
191}