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
298 try {
299 remoteInvoker.disconnect();
300 } catch (RemoteException ignore) {
301
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 }