View Javadoc

1   /***
2    * Redistribution and use of this software and associated documentation
3    * ("Software"), with or without modification, are permitted provided
4    * that the following conditions are met:
5    *
6    * 1. Redistributions of source code must retain copyright
7    *    statements and notices.  Redistributions must also contain a
8    *    copy of this document.
9    *
10   * 2. Redistributions in binary form must reproduce the
11   *    above copyright notice, this list of conditions and the
12   *    following disclaimer in the documentation and/or other
13   *    materials provided with the distribution.
14   *
15   * 3. The name "Exolab" must not be used to endorse or promote
16   *    products derived from this Software without prior written
17   *    permission of Exoffice Technologies.  For written permission,
18   *    please contact info@exolab.org.
19   *
20   * 4. Products derived from this Software may not be called "Exolab"
21   *    nor may "Exolab" appear in their names without prior written
22   *    permission of Exoffice Technologies. Exolab is a registered
23   *    trademark of Exoffice Technologies.
24   *
25   * 5. Due credit should be given to the Exolab Project
26   *    (http://www.exolab.org/).
27   *
28   * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
29   * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
32   * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39   * OF THE POSSIBILITY OF SUCH DAMAGE.
40   *
41   * Copyright 2003-2005 (C) Exoffice Technologies Inc. All Rights Reserved.
42   *
43   * $Id: RMIManagedConnection.java,v 1.8 2006/12/16 12:37:17 tanderson Exp $
44   */
45  package org.exolab.jms.net.rmi;
46  
47  import java.io.IOException;
48  import java.rmi.AccessException;
49  import java.rmi.MarshalException;
50  import java.rmi.MarshalledObject;
51  import java.rmi.NotBoundException;
52  import java.rmi.RemoteException;
53  import java.rmi.registry.LocateRegistry;
54  import java.rmi.registry.Registry;
55  import java.rmi.server.UnicastRemoteObject;
56  import java.security.Principal;
57  
58  import org.apache.commons.logging.Log;
59  import org.apache.commons.logging.LogFactory;
60  
61  import org.exolab.jms.common.uuid.UUIDGenerator;
62  import org.exolab.jms.net.connector.AbstractManagedConnection;
63  import org.exolab.jms.net.connector.Caller;
64  import org.exolab.jms.net.connector.CallerImpl;
65  import org.exolab.jms.net.connector.ConnectException;
66  import org.exolab.jms.net.connector.Connection;
67  import org.exolab.jms.net.connector.IllegalStateException;
68  import org.exolab.jms.net.connector.InvocationHandler;
69  import org.exolab.jms.net.connector.Request;
70  import org.exolab.jms.net.connector.ResourceException;
71  import org.exolab.jms.net.connector.Response;
72  import org.exolab.jms.net.connector.SecurityException;
73  import org.exolab.jms.net.connector.MarshalledInvocation;
74  import org.exolab.jms.net.connector.ManagedConnectionListener;
75  import org.exolab.jms.net.uri.InvalidURIException;
76  import org.exolab.jms.net.uri.URI;
77  import org.exolab.jms.net.uri.URIHelper;
78  
79  
80  /***
81   * <code>RMIManagedConnection</code> manages multiple <code>RMIConnection</code>
82   * instances.
83   *
84   * @author <a href="mailto:tma@netspace.net.au">Tim Anderson</a>
85   * @version $Revision: 1.8 $ $Date: 2006/12/16 12:37:17 $
86   */
87  class RMIManagedConnection extends AbstractManagedConnection {
88  
89      /***
90       * The invoker for serving invocations from the remote managed connection.
91       */
92      private RMIInvokerImpl _localInvoker;
93  
94      /***
95       * The invoker for delegating invocations to the remote managed connection.
96       */
97      private RMIInvoker _remoteInvoker;
98  
99      /***
100      * The invocation handler.
101      */
102     private InvocationHandler _invoker;
103 
104     /***
105      * The remote address to which this is connected.
106      */
107     private URI _remoteURI;
108 
109     /***
110      * The the local address that this connection is bound to.
111      */
112     private URI _localURI;
113 
114     /***
115      * The security principal.
116      */
117     private Principal _principal;
118 
119     /***
120      * Cached caller instance. Non-null if this is a server-side instance.
121      */
122     private Caller _caller;
123 
124     /***
125      * The logger.
126      */
127     private static final Log _log =
128             LogFactory.getLog(RMIManagedConnection.class);
129 
130 
131     /***
132      * Construct a new client <code>RMIManagedConnection</code>.
133      *
134      * @param principal the security principal
135      * @param info      the connection request info
136      * @throws ResourceException if a connection cannot be established
137      */
138     protected RMIManagedConnection(Principal principal, RMIRequestInfo info)
139             throws ResourceException {
140 
141         Registry registry;
142         _remoteURI = URIHelper.convertHostToAddress(info.getURI());
143         _localURI = generateLocalURI();
144 
145         try {
146             registry = LocateRegistry.getRegistry(info.getHost(),
147                                                   info.getPort());
148         } catch (RemoteException exception) {
149             throw new ConnectException("Failed to get registry"
150                                        + ", host=" + info.getHost()
151                                        + ", port=" + info.getPort(),
152                                        exception);
153         }
154 
155         String name = RegistryHelper.getName(_remoteURI);
156         RMIInvokerFactory factory;
157         try {
158             factory = (RMIInvokerFactory) registry.lookup(name);
159         } catch (RemoteException exception) {
160             throw new ConnectException("Failed to lookup connection proxy"
161                                        + ", host=" + info.getHost()
162                                        + ", port=" + info.getPort(),
163                                        exception);
164         } catch (NotBoundException exception) {
165             throw new ConnectException("Connection proxy=" + name
166                                        + " not bound in "
167                                        + "registry, host=" + info.getHost()
168                                        + ", port=" + info.getPort(),
169                                        exception);
170         }
171 
172         _localInvoker = new RMIInvokerImpl();
173         _localInvoker.setConnection(this);
174         try {
175             UnicastRemoteObject.exportObject(_localInvoker);
176         } catch (RemoteException exception) {
177             throw new ResourceException("Failed to export invocation handler",
178                                         exception);
179         }
180         try {
181             _remoteInvoker = factory.createInvoker(principal, _localInvoker,
182                                                    _localURI.toString());
183         } catch (AccessException exception) {
184             throw new SecurityException(exception.getMessage(), exception);
185         } catch (RemoteException exception) {
186             if (exception.detail instanceof AccessException) {
187                 throw new SecurityException(exception.getMessage(),
188                                             exception.detail);
189             }
190             throw new ResourceException("Failed to create invocation handler",
191                                         exception);
192         }
193         _principal = principal;
194     }
195 
196     /***
197      * Construct a new server <code>RMIManagedConnection</code>. This is
198      * responsible for exporting the supplied local invoker on the port
199      * specified by the URI.
200      *
201      * @param principal     the security principal
202      * @param localInvoker  the invoker which delegates invocations to this
203      * @param localURI      the URI to export the connection proxy on
204      * @param remoteInvoker the invoker which delegates invocations to the
205      *                      remote managed connection
206      * @param remoteURI     the URI representing the remote connection
207      * @throws RemoteException if the connection proxy can't be exported
208      */
209     protected RMIManagedConnection(Principal principal,
210                                    RMIInvokerImpl localInvoker,
211                                    URI localURI,
212                                    RMIInvoker remoteInvoker,
213                                    URI remoteURI)
214 
215             throws RemoteException {
216         localInvoker.setConnection(this);
217         UnicastRemoteObject.exportObject(localInvoker, localURI.getPort());
218         _localInvoker = localInvoker;
219         _localURI = localURI;
220         _remoteInvoker = remoteInvoker;
221         _remoteURI = remoteURI;
222         _principal = principal;
223         _caller = new CallerImpl(_remoteURI, _localURI);
224     }
225 
226     /***
227      * Creates a new connection handle for the underlying physical connection.
228      *
229      * @return a new connection handle
230      * @throws IllegalStateException if an invocation handler hasn't been
231      *                               registered
232      */
233     public synchronized Connection getConnection()
234             throws IllegalStateException {
235         if (_invoker == null) {
236             throw new IllegalStateException("No InvocationHandler registered");
237         }
238         return new RMIConnection(this);
239     }
240 
241     /***
242      * Registers a handler for handling invocations.
243      *
244      * @param handler the invocation handler
245      * @throws IllegalStateException if a handler is already registered
246      */
247     public synchronized void setInvocationHandler(InvocationHandler handler)
248             throws IllegalStateException {
249         if (_invoker != null) {
250             throw new IllegalStateException(
251                     "An invocation handler is already registered");
252         }
253         _invoker = handler;
254     }
255 
256     /***
257      * Ping the connection. The connection event listener will be notified
258      * if the ping succeeds.
259      * NOTE: the notification may occur prior to this call returning.
260      *
261      * @throws ResourceException for any error
262      */
263     public void ping() throws ResourceException {
264         RMIInvoker invoker;
265         synchronized (this) {
266             invoker = _remoteInvoker;
267         }
268         if (invoker != null) {
269             try {
270                 invoker.ping();
271                 ManagedConnectionListener listener
272                         = getConnectionEventListener();
273                 if (listener != null) {
274                     listener.pinged(this);
275                 }
276             } catch (RemoteException exception) {
277                 throw new ResourceException(exception);
278             }
279         } else {
280             throw new IllegalStateException("Connection not established");
281         }
282     }
283 
284     /***
285      * Destroys the physical connection.
286      *
287      * @throws ResourceException for any error
288      */
289     public void destroy() throws ResourceException {
290         RMIInvoker localInvoker;
291         RMIInvoker remoteInvoker;
292         synchronized (this) {
293             localInvoker = _localInvoker;
294             remoteInvoker = _remoteInvoker;
295         }
296         if (remoteInvoker != null) {
297             // notify peer of disconnection
298             try {
299                 remoteInvoker.disconnect();
300             } catch (RemoteException ignore) {
301                 // no-op
302             }
303         }
304         try {
305             if (localInvoker != null) {
306                 if (!UnicastRemoteObject.unexportObject(localInvoker, true)) {
307                     _log.warn("Failed to unexport invocation handler");
308                 }
309             }
310         } catch (RemoteException exception) {
311             throw new ResourceException(
312                     "Failed to unexport invocation handler", exception);
313         } finally {
314             synchronized (this) {
315                 _localInvoker = null;
316                 _remoteInvoker = null;
317             }
318         }
319     }
320 
321     /***
322      * Returns the remote address to which this is connected.
323      *
324      * @return the remote address to which this is connected
325      */
326     public URI getRemoteURI() {
327         return _remoteURI;
328     }
329 
330     /***
331      * Returns the local address that this connection is bound to.
332      *
333      * @return the local address that this connection is bound to
334      */
335     public URI getLocalURI() {
336         return _localURI;
337     }
338 
339     /***
340      * Returns the principal associated with this connection.
341      *
342      * @return the principal associated with this connection,
343      *         or <code>null<code> if none is set
344      */
345     public Principal getPrincipal() {
346         return _principal;
347     }
348 
349     /***
350      * Determines if the security principal that owns this connection is the
351      * same as that supplied.
352      *
353      * @param principal the principal to compare. May be <code>null</code>.
354      * @return <code>true</code> if the principal that owns this connection is
355      *         the same as <code>principal</code>
356      */
357     public boolean hasPrincipal(Principal principal) {
358         boolean result = false;
359         if ((_principal != null && _principal.equals(principal))
360                 || (_principal == null && principal == null)) {
361             result = true;
362         }
363         return result;
364     }
365 
366     /***
367      * Invoke a method on a remote object.
368      *
369      * @param connection the connection performing the invocation
370      * @param request    the request
371      * @return the result of the invocation
372      * @throws RemoteException if the distributed call cannot be made
373      */
374     protected Response invoke(Connection connection, Request request)
375             throws RemoteException {
376         Response response;
377         try {
378             MarshalledObject wrappedRequest = new MarshalledObject(request);
379             MarshalledObject wrappedResponse =
380                     _remoteInvoker.invoke(wrappedRequest);
381             response = (Response) wrappedResponse.get();
382         } catch (ClassNotFoundException exception) {
383             response = new Response(exception);
384         } catch (IOException exception) {
385             response = new Response(exception);
386         }
387         return response;
388     }
389 
390     /***
391      * Invoke a method on a local object.
392      *
393      * @param request the wrapped <code>Request</code>
394      * @return the wrapped <code>Response</code>
395      * @throws MarshalException if the response can't be marshalled
396      */
397     protected MarshalledObject invokeLocal(MarshalledObject request)
398             throws MarshalException {
399         MarshalledInvocation invocation
400                 = new MarshalledInvocation(request, _caller);
401         _invoker.invoke(invocation);
402 
403         MarshalledObject response;
404         try {
405             response = invocation.getMarshalledResponse();
406         } catch (Exception exception) {
407             throw new MarshalException("Failed to marshal response",
408                                        exception);
409         }
410         return response;
411     }
412 
413     /***
414      * Invoked when the remote peer disconnects.
415      */
416     protected void disconnect() {
417         synchronized (this) {
418             _remoteInvoker = null;
419         }
420         notifyClosed();
421     }
422 
423     /***
424      * Helper to generate a URI for a client RMIManagedConnection instance.
425      *
426      * @return a URI that uniquely identifies a client RMIManagedConnection
427      * @throws ResourceException if the URI cannot be generated
428      */
429     private URI generateLocalURI() throws ResourceException {
430         URI result;
431         String path = UUIDGenerator.create();
432         try {
433             result = URIHelper.create("rmi", null, -1, path);
434         } catch (InvalidURIException exception) {
435             throw new ResourceException("Failed to generate local URI",
436                                         exception);
437         }
438         return result;
439     }
440 }