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}