KickJava   Java API By Example, From Geeks To Geeks.

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


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.lang.reflect.InvocationTargetException JavaDoc;
20 import java.rmi.Remote JavaDoc;
21 import java.rmi.RemoteException JavaDoc;
22
23 import javax.naming.NamingException JavaDoc;
24 import javax.rmi.PortableRemoteObject JavaDoc;
25
26 import org.aopalliance.intercept.MethodInterceptor;
27 import org.aopalliance.intercept.MethodInvocation;
28
29 import org.springframework.aop.support.AopUtils;
30 import org.springframework.beans.factory.InitializingBean;
31 import org.springframework.jndi.JndiObjectLocator;
32 import org.springframework.remoting.RemoteConnectFailureException;
33 import org.springframework.remoting.RemoteLookupFailureException;
34 import org.springframework.remoting.RemoteProxyFailureException;
35 import org.springframework.remoting.support.DefaultRemoteInvocationFactory;
36 import org.springframework.remoting.support.RemoteInvocation;
37 import org.springframework.remoting.support.RemoteInvocationFactory;
38
39 /**
40  * Interceptor for accessing RMI services from JNDI.
41  * Typically used for RMI-IIOP (CORBA), but can also be used for EJB home objects
42  * (for example, a Stateful Session Bean home). In contrast to a plain JNDI lookup,
43  * this accessor also performs narrowing through PortableRemoteObject.
44  *
45  * <p>With conventional RMI services, this invoker is typically used with the RMI
46  * service interface. Alternatively, this invoker can also proxy a remote RMI service
47  * with a matching non-RMI business interface, i.e. an interface that mirrors the RMI
48  * service methods but does not declare RemoteExceptions. In the latter case,
49  * RemoteExceptions thrown by the RMI stub will automatically get converted to
50  * Spring's unchecked RemoteAccessException.
51  *
52  * <p>The JNDI environment can be specified as "jndiEnvironment" property,
53  * or be configured in a <code>jndi.properties</code> file or as system properties.
54  * For example:
55  *
56  * <pre class="code">&lt;property name="jndiEnvironment"&gt;
57  * &lt;props>
58  * &lt;prop key="java.naming.factory.initial"&gt;com.sun.jndi.cosnaming.CNCtxFactory&lt;/prop&gt;
59  * &lt;prop key="java.naming.provider.url"&gt;iiop://localhost:1050&lt;/prop&gt;
60  * &lt;/props&gt;
61  * &lt;/property&gt;</pre>
62  *
63  * @author Juergen Hoeller
64  * @since 1.1
65  * @see #setJndiTemplate
66  * @see #setJndiEnvironment
67  * @see #setJndiName
68  * @see JndiRmiServiceExporter
69  * @see JndiRmiProxyFactoryBean
70  * @see org.springframework.remoting.RemoteAccessException
71  * @see java.rmi.RemoteException
72  * @see java.rmi.Remote
73  * @see javax.rmi.PortableRemoteObject#narrow
74  */

75 public class JndiRmiClientInterceptor extends JndiObjectLocator
76     implements MethodInterceptor, InitializingBean {
77
78     private Class JavaDoc serviceInterface;
79
80     private RemoteInvocationFactory remoteInvocationFactory = new DefaultRemoteInvocationFactory();
81
82     private boolean lookupStubOnStartup = true;
83
84     private boolean cacheStub = true;
85
86     private boolean refreshStubOnConnectFailure = false;
87
88     private Remote JavaDoc cachedStub;
89
90     private final Object JavaDoc stubMonitor = new Object JavaDoc();
91
92
93     /**
94      * Set the interface of the service to access.
95      * The interface must be suitable for the particular service and remoting tool.
96      * <p>Typically required to be able to create a suitable service proxy,
97      * but can also be optional if the lookup returns a typed stub.
98      */

99     public void setServiceInterface(Class JavaDoc serviceInterface) {
100         if (serviceInterface != null && !serviceInterface.isInterface()) {
101             throw new IllegalArgumentException JavaDoc("'serviceInterface' must be an interface");
102         }
103         this.serviceInterface = serviceInterface;
104     }
105
106     /**
107      * Return the interface of the service to access.
108      */

109     public Class JavaDoc getServiceInterface() {
110         return this.serviceInterface;
111     }
112
113     /**
114      * Set the RemoteInvocationFactory to use for this accessor.
115      * Default is a {@link DefaultRemoteInvocationFactory}.
116      * <p>A custom invocation factory can add further context information
117      * to the invocation, for example user credentials.
118      */

119     public void setRemoteInvocationFactory(RemoteInvocationFactory remoteInvocationFactory) {
120         this.remoteInvocationFactory = remoteInvocationFactory;
121     }
122
123     /**
124      * Return the RemoteInvocationFactory used by this accessor.
125      */

126     public RemoteInvocationFactory getRemoteInvocationFactory() {
127         return this.remoteInvocationFactory;
128     }
129
130     /**
131      * Set whether to look up the RMI stub on startup. Default is "true".
132      * <p>Can be turned off to allow for late start of the RMI server.
133      * In this case, the RMI stub will be fetched on first access.
134      * @see #setCacheStub
135      */

136     public void setLookupStubOnStartup(boolean lookupStubOnStartup) {
137         this.lookupStubOnStartup = lookupStubOnStartup;
138     }
139
140     /**
141      * Set whether to cache the RMI stub once it has been located.
142      * Default is "true".
143      * <p>Can be turned off to allow for hot restart of the RMI server.
144      * In this case, the RMI stub will be fetched for each invocation.
145      * @see #setLookupStubOnStartup
146      */

147     public void setCacheStub(boolean cacheStub) {
148         this.cacheStub = cacheStub;
149     }
150
151     /**
152      * Set whether to refresh the RMI stub on connect failure.
153      * Default is "false".
154      * <p>Can be turned on to allow for hot restart of the RMI server.
155      * If a cached RMI stub throws an RMI exception that indicates a
156      * remote connect failure, a fresh proxy will be fetched and the
157      * invocation will be retried.
158      * @see java.rmi.ConnectException
159      * @see java.rmi.ConnectIOException
160      * @see java.rmi.NoSuchObjectException
161      */

162     public void setRefreshStubOnConnectFailure(boolean refreshStubOnConnectFailure) {
163         this.refreshStubOnConnectFailure = refreshStubOnConnectFailure;
164     }
165
166
167     public void afterPropertiesSet() throws NamingException JavaDoc {
168         super.afterPropertiesSet();
169         prepare();
170     }
171
172     /**
173      * Fetches the RMI stub on startup, if necessary.
174      * <p>Note: As of Spring 2.1, this method will always throw
175      * RemoteLookupFailureException and not declare NamingException anymore.
176      * @throws NamingException if the JNDI lookup failed
177      * @throws RemoteLookupFailureException if RMI stub creation failed
178      * @see #setLookupStubOnStartup
179      * @see #lookupStub
180      */

181     public void prepare() throws NamingException JavaDoc, RemoteLookupFailureException {
182         // Cache RMI stub on initialization?
183
if (this.lookupStubOnStartup) {
184             Remote JavaDoc remoteObj = lookupStub();
185             if (logger.isDebugEnabled()) {
186                 if (remoteObj instanceof RmiInvocationHandler) {
187                     logger.debug("JNDI RMI object [" + getJndiName() + "] is an RMI invoker");
188                 }
189                 else if (getServiceInterface() != null) {
190                     boolean isImpl = getServiceInterface().isInstance(remoteObj);
191                     logger.debug("Using service interface [" + getServiceInterface().getName() +
192                         "] for JNDI RMI object [" + getJndiName() + "] - " +
193                         (!isImpl ? "not " : "") + "directly implemented");
194                 }
195             }
196             if (this.cacheStub) {
197                 this.cachedStub = remoteObj;
198             }
199         }
200     }
201
202     /**
203      * Create the RMI stub, typically by looking it up.
204      * <p>Called on interceptor initialization if "cacheStub" is "true";
205      * else called for each invocation by {@link #getStub()}.
206      * <p>The default implementation retrieves the service from the
207      * JNDI environment. This can be overridden in subclasses.
208      * @return the RMI stub to store in this interceptor
209      * @throws NamingException if the JNDI lookup failed
210      * @throws RemoteLookupFailureException if RMI stub creation failed
211      * @see #setCacheStub
212      * @see #lookup
213      */

214     protected Remote JavaDoc lookupStub() throws NamingException JavaDoc, RemoteLookupFailureException {
215         Object JavaDoc stub = lookup();
216         if (getServiceInterface() != null && Remote JavaDoc.class.isAssignableFrom(getServiceInterface())) {
217             try {
218                 stub = PortableRemoteObject.narrow(stub, getServiceInterface());
219             }
220             catch (ClassCastException JavaDoc ex) {
221                 throw new RemoteLookupFailureException(
222                         "Could not narrow RMI stub to service interface [" + getServiceInterface().getName() + "]", ex);
223             }
224         }
225         if (!(stub instanceof Remote JavaDoc)) {
226             throw new RemoteLookupFailureException("Located RMI stub of class [" + stub.getClass().getName() +
227                     "], with JNDI name [" + getJndiName() + "], does not implement interface [java.rmi.Remote]");
228         }
229         return (Remote JavaDoc) stub;
230     }
231
232     /**
233      * Return the RMI stub to use. Called for each invocation.
234      * <p>The default implementation returns the stub created on initialization,
235      * if any. Else, it invokes {@link #lookupStub} to get a new stub for
236      * each invocation. This can be overridden in subclasses, for example in
237      * order to cache a stub for a given amount of time before recreating it,
238      * or to test the stub whether it is still alive.
239      * @return the RMI stub to use for an invocation
240      * @throws NamingException if stub creation failed
241      * @throws RemoteLookupFailureException if RMI stub creation failed
242      */

243     protected Remote JavaDoc getStub() throws NamingException JavaDoc, RemoteLookupFailureException {
244         if (!this.cacheStub || (this.lookupStubOnStartup && !this.refreshStubOnConnectFailure)) {
245             return (this.cachedStub != null ? this.cachedStub : lookupStub());
246         }
247         else {
248             synchronized (this.stubMonitor) {
249                 if (this.cachedStub == null) {
250                     this.cachedStub = lookupStub();
251                 }
252                 return this.cachedStub;
253             }
254         }
255     }
256
257
258     /**
259      * Fetches an RMI stub and delegates to {@link #doInvoke}.
260      * If configured to refresh on connect failure, it will call
261      * {@link #refreshAndRetry} on corresponding RMI exceptions.
262      * @see #getStub
263      * @see #doInvoke
264      * @see #refreshAndRetry
265      * @see java.rmi.ConnectException
266      * @see java.rmi.ConnectIOException
267      * @see java.rmi.NoSuchObjectException
268      */

269     public Object JavaDoc invoke(MethodInvocation invocation) throws Throwable JavaDoc {
270         Remote JavaDoc stub = null;
271         try {
272             stub = getStub();
273         }
274         catch (NamingException JavaDoc ex) {
275             throw new RemoteLookupFailureException("JNDI lookup for RMI service [" + getJndiName() + "] failed", ex);
276         }
277         try {
278             return doInvoke(invocation, stub);
279         }
280         catch (RemoteConnectFailureException ex) {
281             return handleRemoteConnectFailure(invocation, ex);
282         }
283         catch (RemoteException JavaDoc ex) {
284             if (isConnectFailure(ex)) {
285                 return handleRemoteConnectFailure(invocation, ex);
286             }
287             else {
288                 throw ex;
289             }
290         }
291     }
292
293     /**
294      * Determine whether the given RMI exception indicates a connect failure.
295      * <p>The default implementation delegates to
296      * {@link RmiClientInterceptorUtils#isConnectFailure}.
297      * @param ex the RMI exception to check
298      * @return whether the exception should be treated as connect failure
299      */

300     protected boolean isConnectFailure(RemoteException JavaDoc ex) {
301         return RmiClientInterceptorUtils.isConnectFailure(ex);
302     }
303
304     /**
305      * Refresh the stub and retry the remote invocation if necessary.
306      * <p>If not configured to refresh on connect failure, this method
307      * simply rethrows the original exception.
308      * @param invocation the invocation that failed
309      * @param ex the exception raised on remote invocation
310      * @return the result value of the new invocation, if succeeded
311      * @throws Throwable an exception raised by the new invocation, if failed too.
312      */

313     private Object JavaDoc handleRemoteConnectFailure(MethodInvocation invocation, Exception JavaDoc ex) throws Throwable JavaDoc {
314         if (this.refreshStubOnConnectFailure) {
315             if (logger.isDebugEnabled()) {
316                 logger.debug("Could not connect to RMI service [" + getJndiName() + "] - retrying", ex);
317             }
318             else if (logger.isWarnEnabled()) {
319                 logger.warn("Could not connect to RMI service [" + getJndiName() + "] - retrying");
320             }
321             return refreshAndRetry(invocation);
322         }
323         else {
324             throw ex;
325         }
326     }
327
328     /**
329      * Refresh the RMI stub and retry the given invocation.
330      * Called by invoke on connect failure.
331      * @param invocation the AOP method invocation
332      * @return the invocation result, if any
333      * @throws Throwable in case of invocation failure
334      * @see #invoke
335      */

336     protected Object JavaDoc refreshAndRetry(MethodInvocation invocation) throws Throwable JavaDoc {
337         Remote JavaDoc freshStub = null;
338         synchronized (this.stubMonitor) {
339             try {
340                 freshStub = lookupStub();
341             }
342             catch (NamingException JavaDoc ex) {
343                 throw new RemoteLookupFailureException("JNDI lookup for RMI service [" + getJndiName() + "] failed", ex);
344             }
345             if (this.cacheStub) {
346                 this.cachedStub = freshStub;
347             }
348         }
349         return doInvoke(invocation, freshStub);
350     }
351
352     /**
353      * Perform the given invocation on the given RMI stub.
354      * @param invocation the AOP method invocation
355      * @param stub the RMI stub to invoke
356      * @return the invocation result, if any
357      * @throws Throwable in case of invocation failure
358      */

359     protected Object JavaDoc doInvoke(MethodInvocation invocation, Remote JavaDoc stub) throws Throwable JavaDoc {
360         if (stub instanceof RmiInvocationHandler) {
361             // RMI invoker
362
try {
363                 return doInvoke(invocation, (RmiInvocationHandler) stub);
364             }
365             catch (RemoteException JavaDoc ex) {
366                 throw RmiClientInterceptorUtils.convertRmiAccessException(
367                     invocation.getMethod(), ex, isConnectFailure(ex), getJndiName());
368             }
369             catch (InvocationTargetException JavaDoc ex) {
370                 throw ex.getTargetException();
371             }
372             catch (Throwable JavaDoc ex) {
373                 throw new RemoteProxyFailureException(
374                         "Failed to invoke RMI stub for remote service [" + getJndiName() + "]", ex);
375             }
376         }
377         else {
378             // traditional RMI stub
379
try {
380                 return RmiClientInterceptorUtils.doInvoke(invocation, stub);
381             }
382             catch (InvocationTargetException JavaDoc ex) {
383                 Throwable JavaDoc targetEx = ex.getTargetException();
384                 if (targetEx instanceof RemoteException JavaDoc) {
385                     RemoteException JavaDoc rex = (RemoteException JavaDoc) targetEx;
386                     throw RmiClientInterceptorUtils.convertRmiAccessException(
387                             invocation.getMethod(), rex, isConnectFailure(rex), getJndiName());
388                 }
389                 else {
390                     throw targetEx;
391                 }
392             }
393         }
394     }
395
396     /**
397      * Apply the given AOP method invocation to the given {@link RmiInvocationHandler}.
398      * <p>The default implementation delegates to {@link #createRemoteInvocation}.
399      * @param methodInvocation the current AOP method invocation
400      * @param invocationHandler the RmiInvocationHandler to apply the invocation to
401      * @return the invocation result
402      * @throws RemoteException in case of communication errors
403      * @throws NoSuchMethodException if the method name could not be resolved
404      * @throws IllegalAccessException if the method could not be accessed
405      * @throws InvocationTargetException if the method invocation resulted in an exception
406      * @see org.springframework.remoting.support.RemoteInvocation
407      */

408     protected Object JavaDoc doInvoke(MethodInvocation methodInvocation, RmiInvocationHandler invocationHandler)
409         throws RemoteException JavaDoc, NoSuchMethodException JavaDoc, IllegalAccessException JavaDoc, InvocationTargetException JavaDoc {
410
411         if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
412             return "RMI invoker proxy for service URL [" + getJndiName() + "]";
413         }
414
415         return invocationHandler.invoke(createRemoteInvocation(methodInvocation));
416     }
417
418     /**
419      * Create a new RemoteInvocation object for the given AOP method invocation.
420      * <p>The default implementation delegates to the configured
421      * {@link #setRemoteInvocationFactory RemoteInvocationFactory}.
422      * This can be overridden in subclasses in order to provide custom RemoteInvocation
423      * subclasses, containing additional invocation parameters (e.g. user credentials).
424      * <p>Note that it is preferable to build a custom RemoteInvocationFactory
425      * as a reusable strategy, instead of overriding this method.
426      * @param methodInvocation the current AOP method invocation
427      * @return the RemoteInvocation object
428      * @see RemoteInvocationFactory#createRemoteInvocation
429      */

430     protected RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) {
431         return getRemoteInvocationFactory().createRemoteInvocation(methodInvocation);
432     }
433
434 }
435
Popular Tags