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
323
324
325
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
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
435
436
437
438
439
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
561 method = getMethod(object, request.getMethodID());
562 }
563 Object[] args = request.getArgs();
564 if (args == null) {
565
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 }