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: DefaultORB.java,v 1.13 2006/02/23 11:17:40 tanderson Exp $
44   */
45  package org.exolab.jms.net.orb;
46  
47  import java.lang.reflect.InvocationTargetException;
48  import java.lang.reflect.Method;
49  import java.rmi.ConnectException;
50  import java.rmi.NoSuchObjectException;
51  import java.rmi.RemoteException;
52  import java.rmi.StubNotFoundException;
53  import java.rmi.server.ExportException;
54  import java.security.Principal;
55  import java.util.Map;
56  
57  import EDU.oswego.cs.dl.util.concurrent.PooledExecutor;
58  import org.apache.commons.logging.Log;
59  import org.apache.commons.logging.LogFactory;
60  import org.exolab.jms.common.security.BasicPrincipal;
61  import org.exolab.jms.common.threads.DefaultThreadPoolFactory;
62  import org.exolab.jms.net.connector.AbstractConnectionManager;
63  import org.exolab.jms.net.connector.Authenticator;
64  import org.exolab.jms.net.connector.Caller;
65  import org.exolab.jms.net.connector.CallerListener;
66  import org.exolab.jms.net.connector.Connection;
67  import org.exolab.jms.net.connector.Invocation;
68  import org.exolab.jms.net.connector.InvocationHandler;
69  import org.exolab.jms.net.connector.MulticastCallerListener;
70  import org.exolab.jms.net.connector.Request;
71  import org.exolab.jms.net.connector.ResourceException;
72  import org.exolab.jms.net.connector.Response;
73  import org.exolab.jms.net.proxy.Proxy;
74  import org.exolab.jms.net.registry.LocalRegistry;
75  import org.exolab.jms.net.registry.Registry;
76  import org.exolab.jms.net.uri.InvalidURIException;
77  import org.exolab.jms.net.uri.URI;
78  import org.exolab.jms.common.threads.ThreadPoolFactory;
79  import org.exolab.jms.net.util.MethodHelper;
80  import org.exolab.jms.net.util.Properties;
81  
82  
83  /***
84   * The <code>DefaultORB</code> class manages exported objects.
85   *
86   * @author <a href="mailto:tma@netspace.net.au">Tim Anderson</a>
87   * @version $Revision: 1.13 $ $Date: 2006/02/23 11:17:40 $
88   */
89  class DefaultORB extends AbstractORB {
90  
91      /***
92       * The registry.
93       */
94      private LocalRegistry _registry;
95  
96      /***
97       * The connection manager.
98       */
99      private AbstractConnectionManager _manager;
100 
101     /***
102      * The caller event listeners.
103      */
104     private MulticastCallerListener _listeners;
105 
106     /***
107      * The thread pool factory.
108      */
109     private ThreadPoolFactory _factory;
110 
111     /***
112      * The thread pool for scheduling invocation requests.
113      */
114     private PooledExecutor _pool;
115 
116     /***
117      * The maximum no. of threads to use in the thread pool.
118      */
119     private int _maxThreads;
120 
121     /***
122      * Synchronization helper for accessing _pool.
123      */
124     private final Object _poolLock = new Object();
125 
126     /***
127      * Current caller.
128      */
129     private final ThreadLocal _caller = new ThreadLocal();
130 
131     /***
132      * Constant that holds the name of the connection property for specifying
133      * the maximum no. of threads to use for servicing invocations.
134      */
135     private static final String MAX_THREADS_NAME
136             = "org.exolab.jms.net.orb.threads.max";
137 
138     /***
139      * Constant that holds the name of the connection property for specifying
140      * the thread pool factory.
141      */
142     private static final String THREAD_POOL_FACTORY
143             = "org.exolab.jms.net.orb.threads.factory";
144 
145     /***
146      * Default max no. of threads to service invocations, if none is specified.
147      */
148     private static final int MAX_THREADS = Integer.MAX_VALUE;
149 
150     /***
151      * The logger.
152      */
153     private static final Log _log = LogFactory.getLog(DefaultORB.class);
154 
155 
156     /***
157      * Construct a new <code>DefaultORB</code> with the connection
158      * authenticator. All proxies will be loaded using this instance's class
159      * loader.
160      *
161      * @param authenticator the connection authenticator
162      * @throws RemoteException for any error
163      */
164     public DefaultORB(Authenticator authenticator)
165             throws RemoteException {
166         this(authenticator, DefaultORB.class.getClassLoader(), null);
167     }
168 
169     /***
170      * Construct a new <code>DefaultORB</code> with the connection
171      * authenticator, and properties to configure the ORB. All proxies will be
172      * loaded using this instance's class loader.
173      *
174      * @param authenticator the connection authenticator
175      * @param properties    properties to configure the ORB. May be
176      *                      <code>null</code>
177      * @throws RemoteException for any error
178      */
179     public DefaultORB(Authenticator authenticator, Map properties)
180             throws RemoteException {
181         this(authenticator, DefaultORB.class.getClassLoader(), properties);
182     }
183 
184     /***
185      * Construct a new <code>DefaultORB</code> with properties to configure the
186      * ORB. Connections will be unauthenticated. All proxies will be loaded
187      * using this instance's class loader.
188      *
189      * @throws RemoteException for any error
190      */
191     public DefaultORB(Map properties) throws RemoteException {
192         this(new DummyAuthenticator(), DefaultORB.class.getClassLoader(),
193                 properties);
194     }
195 
196     /***
197      * Construct a new <code>DefaultORB</code>. Connections will be
198      * unauthenticated. All proxies will be loaded using this instance's class
199      * loader.
200      *
201      * @throws RemoteException for any error
202      */
203     public DefaultORB() throws RemoteException {
204         this(new DummyAuthenticator(), DefaultORB.class.getClassLoader(),
205                 null);
206     }
207 
208     /***
209      * Construct a new <code>DefaultORB</code> with the connection
210      * authenticator, the class loader used to load proxies, and properties to
211      * configure the ORB.
212      *
213      * @param authenticator the connection authenticator
214      * @param loader        the class loader to load proxies
215      * @param properties    properties to configure the ORB. May be
216      *                      <code>null</code>
217      * @throws RemoteException for any error
218      */
219     public DefaultORB(Authenticator authenticator, ClassLoader loader,
220                       Map properties)
221             throws RemoteException {
222         super(loader, properties);
223         if (authenticator == null) {
224             throw new IllegalArgumentException(
225                     "Argument 'authenticator' is null");
226         }
227 
228         Properties helper = new Properties(properties, null);
229         try {
230             _maxThreads = helper.getInt(MAX_THREADS_NAME, MAX_THREADS);
231         } catch (ResourceException exception) {
232             throw new RemoteException("Failed to construct thread pool",
233                     exception);
234         }
235 
236         _factory = (ThreadPoolFactory) helper.getProperties().get(
237                 THREAD_POOL_FACTORY);
238         if (_factory == null) {
239             _factory = new DefaultThreadPoolFactory(null);
240         }
241 
242         try {
243             _manager = createConnectionManager(new Handler(), authenticator);
244         } catch (ResourceException exception) {
245             throw new RemoteException("Failed to construct connection manager",
246                     exception);
247         }
248     }
249 
250     /***
251      * Returns a reference to the registry service.
252      *
253      * @return the registry service
254      * @throws RemoteException if the service cannot be exported
255      */
256     public synchronized LocalRegistry getRegistry() throws RemoteException {
257         if (_registry == null) {
258             _registry = new RegistryService(this);
259         }
260         return _registry;
261     }
262 
263     /***
264      * Returns a reference to a remote registry service.
265      *
266      * @param properties the connection properties.
267      * @return the registry service
268      * @throws RemoteException for any error
269      */
270     public Registry getRegistry(Map properties) throws RemoteException {
271         if (properties == null || properties.get(PROVIDER_URI) == null) {
272             throw new ConnectException(PROVIDER_URI + " not specified");
273         }
274         Registry registry;
275         String uri = (String) properties.get(PROVIDER_URI);
276         String principal = (String) properties.get(SECURITY_PRINCIPAL);
277         String credentials = (String) properties.get(SECURITY_CREDENTIALS);
278         Principal subject = null;
279 
280         if (principal != null) {
281             subject = new BasicPrincipal(principal, credentials);
282         }
283 
284         try {
285             registry = Locator.getRegistry(subject, uri, _manager,
286                     getProxyClassLoader(),
287                     properties);
288         } catch (InvalidURIException exception) {
289             throw new RemoteException("Invalid URI: " + uri, exception);
290         }
291         return registry;
292     }
293 
294     /***
295      * Export an object to the current remote caller. Only the remote caller may
296      * perform invocations.
297      *
298      * @param object the object to export
299      * @return a proxy which may be used to invoke methods on the object
300      * @throws ExportException       if the object cannot be exported
301      * @throws StubNotFoundException if the proxy class cannot be found
302      */
303     public Proxy exportObjectTo(Object object) throws ExportException,
304             StubNotFoundException {
305         Caller caller = (Caller) _caller.get();
306         if (caller == null) {
307             throw new ExportException("Cannot export - no current caller");
308         }
309         return doExportTo(object, caller.getLocalURI());
310     }
311 
312     /***
313      * Unexport an object.
314      *
315      * @param object the object to export
316      * @throws java.rmi.NoSuchObjectException if the object isn't exported
317      */
318     public synchronized void unexportObject(Object object)
319             throws NoSuchObjectException {
320         super.unexportObject(object);
321         if (getExported() == 0) {
322             // no more exported objects, so shutdown the thread pool.
323             // The other alternative is to reduce the keep alive time for
324             // unused threads however this is more likely to require
325             // more resources over time.
326             synchronized (_poolLock) {
327                 if (_pool != null) {
328                     _log.debug("Shutting down thread pool");
329                     _pool.shutdownNow();
330                     _pool = null;
331                 }
332             }
333         }
334     }
335 
336     /***
337      * Returns the current caller.
338      *
339      * @return the current caller, or <code>null</code> if no call is in
340      *         progress
341      * @throws RemoteException for any error
342      */
343     public Caller getCaller() throws RemoteException {
344         return (Caller) _caller.get();
345     }
346 
347     /***
348      * Register a caller event listener.
349      *
350      * @param uri      the remote URI to listen on
351      * @param listener the listener to notify
352      * @throws InvalidURIException if <code>uri</code> is invalid
353      */
354     public void addCallerListener(String uri, CallerListener listener)
355             throws InvalidURIException {
356         synchronized (this) {
357             if (_listeners == null) {
358                 _listeners = new MulticastCallerListener();
359                 _manager.setCallerListener(_listeners);
360             }
361         }
362         _listeners.addCallerListener(uri, listener);
363     }
364 
365     /***
366      * Deregister a caller event listener.
367      *
368      * @param uri      the remote URI the listener is listening for events on
369      * @param listener the listener to remove
370      * @throws InvalidURIException if <code>uri</code> is invalid
371      */
372     public void removeCallerListener(String uri, CallerListener listener)
373             throws InvalidURIException {
374         MulticastCallerListener listeners = null;
375         synchronized (this) {
376             listeners = _listeners;
377         }
378         if (listeners != null) {
379             listeners.removeCallerListener(uri, listener);
380         }
381     }
382 
383     /***
384      * Shuts down the ORB.
385      *
386      * @throws RemoteException for any error
387      */
388     public void shutdown() throws RemoteException {
389         try {
390             _manager.close();
391         } catch (ResourceException exception) {
392             throw new RemoteException("Failed to close connection manager",
393                     exception);
394         }
395         // super.close(); @todo
396     }
397 
398     /***
399      * Creates a new connection manager.
400      *
401      * @param handler       the invocation handler
402      * @param authenticator the connection authenticator
403      * @return a new connection manager
404      * @throws ResourceException for any error
405      */
406     protected AbstractConnectionManager createConnectionManager(
407             InvocationHandler handler, Authenticator authenticator)
408             throws ResourceException {
409         return new DefaultConnectionManager(handler, authenticator,
410                 getProperties());
411     }
412 
413     /***
414      * Connect to the specified URI.
415      *
416      * @param uri         the URI to establish a connection with
417      * @param principal   specifies the identity of the principal. If
418      *                    <code>null</code>, indicates to connect anonymously.
419      * @param credentials the credentials of the principal
420      * @return the local address that the connection is bound to
421      * @throws ExportException for any error
422      */
423     protected URI connect(URI uri, String principal, String credentials)
424             throws ExportException {
425         URI result;
426         try {
427             Principal subject = null;
428             if (principal != null) {
429                 subject = new BasicPrincipal(principal, credentials);
430             }
431             Connection connection = _manager.getConnection(subject, uri);
432             result = connection.getLocalURI();
433 
434             // @todo - closing the connection will work for now in that
435             // connection reference counts for OpenJMS will be correct. Won't
436             // support the case where a client exports an object to the server
437             // and then disposes its server proxies - the connection
438             // will be prematurely closed. The connection needs to be kept
439             // until the object is unexported.
440             connection.close();
441         } catch (ResourceException exception) {
442             throw new ExportException("Failed to connect to URI: " + uri,
443                     exception);
444         }
445         return result;
446     }
447 
448     /***
449      * Accept connections on the specified URI.
450      *
451      * @param uri the URI to accept connections on
452      * @throws ExportException for any error
453      */
454     protected void accept(URI uri) throws ExportException {
455         try {
456             _manager.accept(uri, getProperties());
457         } catch (ResourceException exception) {
458             throw new ExportException("Failed to accept connections on URI: "
459                     + uri, exception);
460         }
461     }
462 
463     /***
464      * Returns the thread pool, creating one if it doesn't exist.
465      *
466      * @return the thread pool
467      */
468     private PooledExecutor getThreadPool() {
469         synchronized (_poolLock) {
470             if (_pool == null) {
471 
472                 _pool = _factory.create("ORB", _maxThreads);
473                 _pool.abortWhenBlocked();
474             }
475             return _pool;
476         }
477     }
478 
479     /***
480      * Closes the thread pool.
481      */
482 
483     /***
484      * Returns the method corresponding to the supplied object and method
485      * identifier.
486      *
487      * @param object   the object to locate the method for
488      * @param methodID the method identifier
489      * @return the method
490      * @throws NoSuchMethodException if a corresponding method cannot be found
491      */
492     private Method getMethod(Object object, long methodID)
493             throws NoSuchMethodException {
494 
495         Method result = null;
496         Method[] methods = MethodHelper.getAllInterfaceMethods(
497                 object.getClass());
498         for (int i = 0; i < methods.length; ++i) {
499             Method method = methods[i];
500             if (MethodHelper.getMethodID(method) == methodID) {
501                 result = method;
502                 break;
503             }
504         }
505         if (result == null) {
506             throw new NoSuchMethodException(
507                     "Failed to resolve method for methodID=" + methodID);
508         }
509         return result;
510     }
511 
512     /***
513      * Invocation handler, that delegates invocations to objects managed by the
514      * DefaultORB.
515      */
516     private class Handler implements InvocationHandler {
517 
518         /***
519          * Perform an invocation.
520          *
521          * @param invocation the invocation
522          */
523         public void invoke(final Invocation invocation) {
524             Runnable invoker = new Runnable() {
525                 public void run() {
526                     Response response;
527                     try {
528                         Request request = invocation.getRequest();
529                         Caller caller = invocation.getCaller();
530                         response = invoke(request, caller);
531                     } catch (Throwable exception) {
532                         response = new Response(exception);
533                     }
534                     invocation.setResponse(response);
535                 }
536             };
537 
538             try {
539                 getThreadPool().execute(invoker);
540             } catch (Throwable exception) {
541                 _log.debug("Pool failed to execute invocation", exception);
542                 invocation.setResponse(new Response(exception));
543             }
544         }
545 
546         /***
547          * Handle a method invocation and return the result.
548          *
549          * @param request the request
550          * @param caller  the caller performing the invocation
551          * @return the result of the invocation
552          */
553         protected Response invoke(Request request, Caller caller) {
554             Response response;
555             try {
556                 Object object = getObject(request.getObjID(),
557                         request.getURI());
558                 Method method = request.getMethod();
559                 if (method == null) {
560                     // resolve the method using its id
561                     method = getMethod(object, request.getMethodID());
562                 }
563                 Object[] args = request.getArgs();
564                 if (args == null) {
565                     // deserialize the arguments
566                     args = request.readArgs(method);
567                 }
568                 if (_log.isDebugEnabled()) {
569                     _log.debug("Invoking " + method + " on " + object);
570                 }
571                 _caller.set(caller);
572                 Object result = method.invoke(object, args);
573                 response = new Response(result, method);
574             } catch (InvocationTargetException exception) {
575                 Throwable target = exception.getTargetException();
576                 if (target == null) {
577                     target = exception;
578                 }
579                 response = new Response(target);
580             } catch (Throwable exception) {
581                 response = new Response(exception);
582             } finally {
583                 _caller.set(null);
584             }
585             return response;
586         }
587 
588     }
589 
590     /***
591      * Dummy connection authenticator, which simply flags all principals as
592      * authenticated.
593      */
594     private static class DummyAuthenticator implements Authenticator {
595 
596         /***
597          * Determines if a principal has permissions to connect
598          *
599          * @param principal the principal to check
600          * @return <code>true</code> if the principal has permissions to
601          *         connect
602          * @throws ResourceException if an error occurs
603          */
604         public boolean authenticate(Principal principal)
605                 throws ResourceException {
606             return true;
607         }
608     }
609 
610 }