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