KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > remoting > rmi > RmiClientInterceptor


1 /*
2  * Copyright 2002-2007 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.remoting.rmi;
18
19 import java.io.IOException JavaDoc;
20 import java.lang.reflect.InvocationTargetException JavaDoc;
21 import java.net.MalformedURLException JavaDoc;
22 import java.net.URL JavaDoc;
23 import java.net.URLConnection JavaDoc;
24 import java.net.URLStreamHandler JavaDoc;
25 import java.rmi.Naming JavaDoc;
26 import java.rmi.NotBoundException JavaDoc;
27 import java.rmi.Remote JavaDoc;
28 import java.rmi.RemoteException JavaDoc;
29 import java.rmi.registry.LocateRegistry JavaDoc;
30 import java.rmi.registry.Registry JavaDoc;
31 import java.rmi.server.RMIClientSocketFactory JavaDoc;
32
33 import org.aopalliance.intercept.MethodInterceptor;
34 import org.aopalliance.intercept.MethodInvocation;
35
36 import org.springframework.aop.support.AopUtils;
37 import org.springframework.remoting.RemoteConnectFailureException;
38 import org.springframework.remoting.RemoteLookupFailureException;
39 import org.springframework.remoting.RemoteProxyFailureException;
40 import org.springframework.remoting.support.RemoteInvocationBasedAccessor;
41 import org.springframework.remoting.support.RemoteInvocationUtils;
42
43 /**
44  * Interceptor for accessing conventional RMI services or RMI invokers.
45  * The service URL must be a valid RMI URL like "rmi://localhost:1099/myservice".
46  *
47  * <p>RMI invokers work at the RmiInvocationHandler level, needing only one stub for
48  * any service. Service interfaces do not have to extend <code>java.rmi.Remote</code>
49  * or throw <code>java.rmi.RemoteException</code>. Spring's unchecked
50  * RemoteAccessException will be thrown on remote invocation failure.
51  * Of course, in and out parameters have to be serializable.
52  *
53  * <p>With conventional RMI services, this invoker is typically used with the RMI
54  * service interface. Alternatively, this invoker can also proxy a remote RMI service
55  * with a matching non-RMI business interface, i.e. an interface that mirrors the RMI
56  * service methods but does not declare RemoteExceptions. In the latter case,
57  * RemoteExceptions thrown by the RMI stub will automatically get converted to
58  * Spring's unchecked RemoteAccessException.
59  *
60  * @author Juergen Hoeller
61  * @since 29.09.2003
62  * @see RmiServiceExporter
63  * @see RmiProxyFactoryBean
64  * @see RmiInvocationHandler
65  * @see org.springframework.remoting.RemoteAccessException
66  * @see java.rmi.RemoteException
67  * @see java.rmi.Remote
68  */

69 public class RmiClientInterceptor extends RemoteInvocationBasedAccessor
70         implements MethodInterceptor {
71
72     private boolean lookupStubOnStartup = true;
73
74     private boolean cacheStub = true;
75
76     private boolean refreshStubOnConnectFailure = false;
77
78     private RMIClientSocketFactory JavaDoc registryClientSocketFactory;
79
80     private Remote JavaDoc cachedStub;
81
82     private final Object JavaDoc stubMonitor = new Object JavaDoc();
83
84
85     /**
86      * Set whether to look up the RMI stub on startup. Default is "true".
87      * <p>Can be turned off to allow for late start of the RMI server.
88      * In this case, the RMI stub will be fetched on first access.
89      * @see #setCacheStub
90      */

91     public void setLookupStubOnStartup(boolean lookupStubOnStartup) {
92         this.lookupStubOnStartup = lookupStubOnStartup;
93     }
94
95     /**
96      * Set whether to cache the RMI stub once it has been located.
97      * Default is "true".
98      * <p>Can be turned off to allow for hot restart of the RMI server.
99      * In this case, the RMI stub will be fetched for each invocation.
100      * @see #setLookupStubOnStartup
101      */

102     public void setCacheStub(boolean cacheStub) {
103         this.cacheStub = cacheStub;
104     }
105
106     /**
107      * Set whether to refresh the RMI stub on connect failure.
108      * Default is "false".
109      * <p>Can be turned on to allow for hot restart of the RMI server.
110      * If a cached RMI stub throws an RMI exception that indicates a
111      * remote connect failure, a fresh proxy will be fetched and the
112      * invocation will be retried.
113      * @see java.rmi.ConnectException
114      * @see java.rmi.ConnectIOException
115      * @see java.rmi.NoSuchObjectException
116      */

117     public void setRefreshStubOnConnectFailure(boolean refreshStubOnConnectFailure) {
118         this.refreshStubOnConnectFailure = refreshStubOnConnectFailure;
119     }
120
121     /**
122      * Set a custom RMI client socket factory to use for accessing the RMI registry.
123      * @see java.rmi.server.RMIClientSocketFactory
124      * @see java.rmi.registry.LocateRegistry#getRegistry(String, int, RMIClientSocketFactory)
125      */

126     public void setRegistryClientSocketFactory(RMIClientSocketFactory JavaDoc registryClientSocketFactory) {
127         this.registryClientSocketFactory = registryClientSocketFactory;
128     }
129
130
131     public void afterPropertiesSet() {
132         super.afterPropertiesSet();
133         prepare();
134     }
135
136     /**
137      * Fetches RMI stub on startup, if necessary.
138      * @throws RemoteLookupFailureException if RMI stub creation failed
139      * @see #setLookupStubOnStartup
140      * @see #lookupStub
141      */

142     public void prepare() throws RemoteLookupFailureException {
143         // Cache RMI stub on initialization?
144
if (this.lookupStubOnStartup) {
145             Remote JavaDoc remoteObj = lookupStub();
146             if (logger.isDebugEnabled()) {
147                 if (remoteObj instanceof RmiInvocationHandler) {
148                     logger.debug("RMI stub [" + getServiceUrl() + "] is an RMI invoker");
149                 }
150                 else if (getServiceInterface() != null) {
151                     boolean isImpl = getServiceInterface().isInstance(remoteObj);
152                     logger.debug("Using service interface [" + getServiceInterface().getName() +
153                         "] for RMI stub [" + getServiceUrl() + "] - " +
154                         (!isImpl ? "not " : "") + "directly implemented");
155                 }
156             }
157             if (this.cacheStub) {
158                 this.cachedStub = remoteObj;
159             }
160         }
161     }
162
163     /**
164      * Create the RMI stub, typically by looking it up.
165      * <p>Called on interceptor initialization if "cacheStub" is "true";
166      * else called for each invocation by {@link #getStub()}.
167      * <p>The default implementation looks up the service URL via
168      * <code>java.rmi.Naming</code>. This can be overridden in subclasses.
169      * @return the RMI stub to store in this interceptor
170      * @throws RemoteLookupFailureException if RMI stub creation failed
171      * @see #setCacheStub
172      * @see java.rmi.Naming#lookup
173      */

174     protected Remote JavaDoc lookupStub() throws RemoteLookupFailureException {
175         try {
176             Remote JavaDoc stub = null;
177             if (this.registryClientSocketFactory != null) {
178                 // RMIClientSocketFactory specified for registry access.
179
// Unfortunately, due to RMI API limitations, this means
180
// that we need to parse the RMI URL ourselves and perform
181
// straight LocateRegistry.getRegistry/Registry.lookup calls.
182
URL JavaDoc url = new URL JavaDoc(null, getServiceUrl(), new DummyURLStreamHandler());
183                 String JavaDoc protocol = url.getProtocol();
184                 if (protocol != null && !"rmi".equals(protocol)) {
185                     throw new MalformedURLException JavaDoc("Invalid URL scheme '" + protocol + "'");
186                 }
187                 String JavaDoc host = url.getHost();
188                 int port = url.getPort();
189                 String JavaDoc name = url.getPath();
190                 if (name != null && name.startsWith("/")) {
191                     name = name.substring(1);
192                 }
193                 Registry JavaDoc registry = LocateRegistry.getRegistry(host, port, this.registryClientSocketFactory);
194                 stub = registry.lookup(name);
195             }
196             else {
197                 // Can proceed with standard RMI lookup API...
198
stub = Naming.lookup(getServiceUrl());
199             }
200             if (logger.isDebugEnabled()) {
201                 logger.debug("Located RMI stub with URL [" + getServiceUrl() + "]");
202             }
203             return stub;
204         }
205         catch (MalformedURLException JavaDoc ex) {
206             throw new RemoteLookupFailureException("Service URL [" + getServiceUrl() + "] is invalid", ex);
207         }
208         catch (NotBoundException JavaDoc ex) {
209             throw new RemoteLookupFailureException(
210                     "Could not find RMI service [" + getServiceUrl() + "] in RMI registry", ex);
211         }
212         catch (RemoteException JavaDoc ex) {
213             throw new RemoteLookupFailureException("Lookup of RMI stub failed", ex);
214         }
215     }
216
217     /**
218      * Return the RMI stub to use. Called for each invocation.
219      * <p>The default implementation returns the stub created on initialization,
220      * if any. Else, it invokes {@link #lookupStub} to get a new stub for
221      * each invocation. This can be overridden in subclasses, for example in
222      * order to cache a stub for a given amount of time before recreating it,
223      * or to test the stub whether it is still alive.
224      * @return the RMI stub to use for an invocation
225      * @throws RemoteLookupFailureException if RMI stub creation failed
226      * @see #lookupStub
227      */

228     protected Remote JavaDoc getStub() throws RemoteLookupFailureException {
229         if (!this.cacheStub || (this.lookupStubOnStartup && !this.refreshStubOnConnectFailure)) {
230             return (this.cachedStub != null ? this.cachedStub : lookupStub());
231         }
232         else {
233             synchronized (this.stubMonitor) {
234                 if (this.cachedStub == null) {
235                     this.cachedStub = lookupStub();
236                 }
237                 return this.cachedStub;
238             }
239         }
240     }
241
242
243     /**
244      * Fetches an RMI stub and delegates to <code>doInvoke</code>.
245      * If configured to refresh on connect failure, it will call
246      * {@link #refreshAndRetry} on corresponding RMI exceptions.
247      * @see #getStub
248      * @see #doInvoke(MethodInvocation, Remote)
249      * @see #refreshAndRetry
250      * @see java.rmi.ConnectException
251      * @see java.rmi.ConnectIOException
252      * @see java.rmi.NoSuchObjectException
253      */

254     public Object JavaDoc invoke(MethodInvocation invocation) throws Throwable JavaDoc {
255         Remote JavaDoc stub = getStub();
256         try {
257             return doInvoke(invocation, stub);
258         }
259         catch (RemoteConnectFailureException ex) {
260             return handleRemoteConnectFailure(invocation, ex);
261         }
262         catch (RemoteException JavaDoc ex) {
263             if (isConnectFailure(ex)) {
264                 return handleRemoteConnectFailure(invocation, ex);
265             }
266             else {
267                 throw ex;
268             }
269         }
270     }
271
272     /**
273      * Determine whether the given RMI exception indicates a connect failure.
274      * <p>The default implementation delegates to
275      * {@link RmiClientInterceptorUtils#isConnectFailure}.
276      * @param ex the RMI exception to check
277      * @return whether the exception should be treated as connect failure
278      */

279     protected boolean isConnectFailure(RemoteException JavaDoc ex) {
280         return RmiClientInterceptorUtils.isConnectFailure(ex);
281     }
282
283     /**
284      * Refresh the stub and retry the remote invocation if necessary.
285      * <p>If not configured to refresh on connect failure, this method
286      * simply rethrows the original exception.
287      * @param invocation the invocation that failed
288      * @param ex the exception raised on remote invocation
289      * @return the result value of the new invocation, if succeeded
290      * @throws Throwable an exception raised by the new invocation, if failed too.
291      */

292     private Object JavaDoc handleRemoteConnectFailure(MethodInvocation invocation, Exception JavaDoc ex) throws Throwable JavaDoc {
293         if (this.refreshStubOnConnectFailure) {
294             if (logger.isDebugEnabled()) {
295                 logger.debug("Could not connect to RMI service [" + getServiceUrl() + "] - retrying", ex);
296             }
297             else if (logger.isWarnEnabled()) {
298                 logger.warn("Could not connect to RMI service [" + getServiceUrl() + "] - retrying");
299             }
300             return refreshAndRetry(invocation);
301         }
302         else {
303             throw ex;
304         }
305     }
306
307     /**
308      * Refresh the RMI stub and retry the given invocation.
309      * Called by invoke on connect failure.
310      * @param invocation the AOP method invocation
311      * @return the invocation result, if any
312      * @throws Throwable in case of invocation failure
313      * @see #invoke
314      */

315     protected Object JavaDoc refreshAndRetry(MethodInvocation invocation) throws Throwable JavaDoc {
316         Remote JavaDoc freshStub = null;
317         synchronized (this.stubMonitor) {
318             freshStub = lookupStub();
319             if (this.cacheStub) {
320                 this.cachedStub = freshStub;
321             }
322         }
323         return doInvoke(invocation, freshStub);
324     }
325
326     /**
327      * Perform the given invocation on the given RMI stub.
328      * @param invocation the AOP method invocation
329      * @param stub the RMI stub to invoke
330      * @return the invocation result, if any
331      * @throws Throwable in case of invocation failure
332      */

333     protected Object JavaDoc doInvoke(MethodInvocation invocation, Remote JavaDoc stub) throws Throwable JavaDoc {
334         if (stub instanceof RmiInvocationHandler) {
335             // RMI invoker
336
try {
337                 return doInvoke(invocation, (RmiInvocationHandler) stub);
338             }
339             catch (RemoteException JavaDoc ex) {
340                 throw RmiClientInterceptorUtils.convertRmiAccessException(
341                     invocation.getMethod(), ex, isConnectFailure(ex), getServiceUrl());
342             }
343             catch (InvocationTargetException JavaDoc ex) {
344                 Throwable JavaDoc exToThrow = ex.getTargetException();
345                 RemoteInvocationUtils.fillInClientStackTraceIfPossible(exToThrow);
346                 throw exToThrow;
347             }
348             catch (Throwable JavaDoc ex) {
349                 throw new RemoteProxyFailureException(
350                         "Failed to invoke RMI stub for remote service [" + getServiceUrl() + "]", ex);
351             }
352         }
353         else {
354             // traditional RMI stub
355
try {
356                 return RmiClientInterceptorUtils.doInvoke(invocation, stub);
357             }
358             catch (InvocationTargetException JavaDoc ex) {
359                 Throwable JavaDoc targetEx = ex.getTargetException();
360                 if (targetEx instanceof RemoteException JavaDoc) {
361                     RemoteException JavaDoc rex = (RemoteException JavaDoc) targetEx;
362                     throw RmiClientInterceptorUtils.convertRmiAccessException(
363                             invocation.getMethod(), rex, isConnectFailure(rex), getServiceUrl());
364                 }
365                 else {
366                     throw targetEx;
367                 }
368             }
369         }
370     }
371
372     /**
373      * Apply the given AOP method invocation to the given {@link RmiInvocationHandler}.
374      * <p>The default implementation delegates to {@link #createRemoteInvocation}.
375      * @param methodInvocation the current AOP method invocation
376      * @param invocationHandler the RmiInvocationHandler to apply the invocation to
377      * @return the invocation result
378      * @throws RemoteException in case of communication errors
379      * @throws NoSuchMethodException if the method name could not be resolved
380      * @throws IllegalAccessException if the method could not be accessed
381      * @throws InvocationTargetException if the method invocation resulted in an exception
382      * @see org.springframework.remoting.support.RemoteInvocation
383      */

384     protected Object JavaDoc doInvoke(MethodInvocation methodInvocation, RmiInvocationHandler invocationHandler)
385         throws RemoteException JavaDoc, NoSuchMethodException JavaDoc, IllegalAccessException JavaDoc, InvocationTargetException JavaDoc {
386
387         if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
388             return "RMI invoker proxy for service URL [" + getServiceUrl() + "]";
389         }
390
391         return invocationHandler.invoke(createRemoteInvocation(methodInvocation));
392     }
393
394
395     /**
396      * Dummy URLStreamHandler that's just specified to suppress the standard
397      * <code>java.net.URL</code> URLStreamHandler lookup, to be able to
398      * use the standard URL class for parsing "rmi:..." URLs.
399      */

400     private static class DummyURLStreamHandler extends URLStreamHandler JavaDoc {
401
402         protected URLConnection JavaDoc openConnection(URL JavaDoc url) throws IOException JavaDoc {
403             throw new UnsupportedOperationException JavaDoc();
404         }
405     }
406
407 }
408
Popular Tags