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