001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.remoting.rmi;
018:
019: import java.lang.reflect.InvocationTargetException;
020: import java.lang.reflect.Method;
021: import java.rmi.ConnectException;
022: import java.rmi.ConnectIOException;
023: import java.rmi.MarshalException;
024: import java.rmi.NoSuchObjectException;
025: import java.rmi.Remote;
026: import java.rmi.RemoteException;
027: import java.rmi.StubNotFoundException;
028: import java.rmi.UnknownHostException;
029: import java.rmi.UnmarshalException;
030: import java.util.Arrays;
031:
032: import org.aopalliance.intercept.MethodInvocation;
033: import org.apache.commons.logging.Log;
034: import org.apache.commons.logging.LogFactory;
035:
036: import org.springframework.remoting.RemoteAccessException;
037: import org.springframework.remoting.RemoteConnectFailureException;
038: import org.springframework.remoting.RemoteProxyFailureException;
039: import org.springframework.util.ReflectionUtils;
040:
041: /**
042: * Factored-out methods for performing invocations within an RMI client.
043: * Can handle both RMI and non-RMI service interfaces working on an RMI stub.
044: *
045: * <p>Note: This is an SPI class, not intended to be used by applications.
046: *
047: * @author Juergen Hoeller
048: * @since 1.1
049: */
050: public abstract class RmiClientInterceptorUtils {
051:
052: private static final String ORACLE_CONNECTION_EXCEPTION = "com.evermind.server.rmi.RMIConnectionException";
053:
054: private static final Log logger = LogFactory
055: .getLog(RmiClientInterceptorUtils.class);
056:
057: /**
058: * Apply the given method invocation to the given RMI stub.
059: * <p>Delegate to the corresponding method if the RMI stub does not directly
060: * implemented the invoked method. This typically happens when a non-RMI service
061: * interface is used for an RMI service. The methods of such a service interface
062: * have to match the RMI stub methods, but they typically don't declare
063: * <code>java.rmi.RemoteException</code>: A RemoteException thrown by the RMI stub
064: * will be automatically converted to Spring's RemoteAccessException.
065: * @param invocation the AOP MethodInvocation
066: * @param stub the RMI stub
067: * @param serviceName the name of the service (for debugging purposes)
068: * @return the invocation result, if any
069: * @throws Throwable exception to be thrown to the caller
070: * @see java.rmi.RemoteException
071: * @see org.springframework.remoting.RemoteAccessException
072: */
073: public static Object invoke(MethodInvocation invocation,
074: Remote stub, String serviceName) throws Throwable {
075: try {
076: return doInvoke(invocation, stub);
077: } catch (InvocationTargetException ex) {
078: Throwable targetEx = ex.getTargetException();
079: if (targetEx instanceof RemoteException) {
080: RemoteException rex = (RemoteException) targetEx;
081: throw convertRmiAccessException(invocation.getMethod(),
082: rex, serviceName);
083: } else {
084: throw targetEx;
085: }
086: }
087: }
088:
089: /**
090: * Perform a raw method invocation on the given RMI stub,
091: * letting reflection exceptions through as-is.
092: * @param invocation the AOP MethodInvocation
093: * @param stub the RMI stub
094: * @return the invocation result, if any
095: * @throws InvocationTargetException if thrown by reflection
096: */
097: public static Object doInvoke(MethodInvocation invocation,
098: Remote stub) throws InvocationTargetException {
099: Method method = invocation.getMethod();
100: try {
101: if (method.getDeclaringClass().isInstance(stub)) {
102: // directly implemented
103: return method.invoke(stub, invocation.getArguments());
104: } else {
105: // not directly implemented
106: Method stubMethod = stub.getClass().getMethod(
107: method.getName(), method.getParameterTypes());
108: return stubMethod.invoke(stub, invocation
109: .getArguments());
110: }
111: } catch (InvocationTargetException ex) {
112: throw ex;
113: } catch (NoSuchMethodException ex) {
114: throw new RemoteProxyFailureException(
115: "No matching RMI stub method found for: " + method,
116: ex);
117: } catch (Throwable ex) {
118: throw new RemoteProxyFailureException(
119: "Invocation of RMI stub method failed: " + method,
120: ex);
121: }
122: }
123:
124: /**
125: * Wrap the given arbitrary exception that happened during remote access
126: * in either a RemoteException or a Spring RemoteAccessException (if the
127: * method signature does not support RemoteException).
128: * <p>Only call this for remote access exceptions, not for exceptions
129: * thrown by the target service itself!
130: * @param method the invoked method
131: * @param ex the exception that happened, to be used as cause for the
132: * RemoteAccessException or RemoteException
133: * @param message the message for the RemoteAccessException respectively
134: * RemoteException
135: * @return the exception to be thrown to the caller
136: */
137: public static Exception convertRmiAccessException(Method method,
138: Throwable ex, String message) {
139: if (logger.isDebugEnabled()) {
140: logger.debug(message, ex);
141: }
142: if (Arrays.asList(method.getExceptionTypes()).contains(
143: RemoteException.class)) {
144: return new RemoteException(message, ex);
145: } else {
146: return new RemoteAccessException(message, ex);
147: }
148: }
149:
150: /**
151: * Convert the given RemoteException that happened during remote access
152: * to Spring's RemoteAccessException if the method signature does not
153: * support RemoteException. Else, return the original RemoteException.
154: * @param method the invoked method
155: * @param ex the RemoteException that happened
156: * @param serviceName the name of the service (for debugging purposes)
157: * @return the exception to be thrown to the caller
158: */
159: public static Exception convertRmiAccessException(Method method,
160: RemoteException ex, String serviceName) {
161: return convertRmiAccessException(method, ex,
162: isConnectFailure(ex), serviceName);
163: }
164:
165: /**
166: * Convert the given RemoteException that happened during remote access
167: * to Spring's RemoteAccessException if the method signature does not
168: * support RemoteException. Else, return the original RemoteException.
169: * @param method the invoked method
170: * @param ex the RemoteException that happened
171: * @param isConnectFailure whether the given exception should be considered
172: * a connect failure
173: * @param serviceName the name of the service (for debugging purposes)
174: * @return the exception to be thrown to the caller
175: */
176: public static Exception convertRmiAccessException(Method method,
177: RemoteException ex, boolean isConnectFailure,
178: String serviceName) {
179:
180: if (logger.isDebugEnabled()) {
181: logger.debug("Remote service [" + serviceName
182: + "] threw exception", ex);
183: }
184: if (ReflectionUtils.declaresException(method, ex.getClass())) {
185: return ex;
186: } else {
187: if (isConnectFailure) {
188: return new RemoteConnectFailureException(
189: "Could not connect to remote service ["
190: + serviceName + "]", ex);
191: } else {
192: return new RemoteAccessException(
193: "Could not access remote service ["
194: + serviceName + "]", ex);
195: }
196: }
197: }
198:
199: /**
200: * Determine whether the given RMI exception indicates a connect failure.
201: * <p>Treats RMI's ConnectException, ConnectIOException, UnknownHostException,
202: * NoSuchObjectException, StubNotFoundException, MarshalException and
203: * UnmarshalException as connect failure, as well as Oracle's OC4J
204: * <code>com.evermind.server.rmi.RMIConnectionException</code>
205: * (which doesn't derive from from any well-known RMI connect exception).
206: * @param ex the RMI exception to check
207: * @return whether the exception should be treated as connect failure
208: * @see java.rmi.ConnectException
209: * @see java.rmi.ConnectIOException
210: * @see java.rmi.UnknownHostException
211: * @see java.rmi.NoSuchObjectException
212: * @see java.rmi.StubNotFoundException
213: * @see java.rmi.MarshalException
214: * @see java.rmi.UnmarshalException
215: */
216: public static boolean isConnectFailure(RemoteException ex) {
217: return (ex instanceof ConnectException
218: || ex instanceof ConnectIOException
219: || ex instanceof UnknownHostException
220: || ex instanceof NoSuchObjectException
221: || ex instanceof StubNotFoundException
222: || ex instanceof MarshalException
223: || ex instanceof UnmarshalException || ORACLE_CONNECTION_EXCEPTION
224: .equals(ex.getClass().getName()));
225: }
226:
227: }
|