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}