1
package org.jboss.remoting;
3
import org.jboss.logging.Logger;
4
import org.jboss.remoting.loading.ClassByteClassLoader;
5
import org.jboss.remoting.loading.RemotingClassLoader;
6
import org.jboss.remoting.marshal.InvalidMarshallingResource;
7
import org.jboss.remoting.marshal.MarshalFactory;
8
import org.jboss.remoting.marshal.Marshaller;
9
import org.jboss.remoting.marshal.UnMarshaller;
10
import org.jboss.remoting.marshal.UpdateableClassloaderUnMarshaller;
11
import org.jboss.remoting.transport.ClientInvoker;
12
import org.jboss.remoting.util.SecurityUtility;
13
import org.jboss.util.id.GUID;
15
import java.io.IOException;
16
import java.security.AccessController;
17
import java.security.PrivilegedAction;
18
import java.security.PrivilegedActionException;
19
import java.security.PrivilegedExceptionAction;
20
import java.util.HashMap;
21
import java.util.List;
23
import java.util.WeakHashMap;
26
* MicroRemoteClientInvoker is an abstract client part handler that implements the bulk of the heavy
27
* lifting to process a remote method and dispatch it to a remote ServerInvoker and handle the result. <P>
29
* Specialized Client/Server Invokers might add additional functionality as part of the invocation - such as
30
* delivering queued notifcations from a remote server by adding the notification objects during each invocation
31
* to the invocation result payload and then having the client re-dispatch the notifications locally upon
32
* receiving the return invocation result.
34
* The reason for the name micro is that this class contains only api that can be run within a J2ME envrionment.
36
* @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>
37
* @author <a href="mailto:telrod@e2technologies.net">Tom Elrod</a>
38
* @version $Revision: 5265 $
40
public abstract class MicroRemoteClientInvoker extends AbstractInvoker implements ClientInvoker
42
private static final Logger log = Logger.getLogger(MicroRemoteClientInvoker.class);
43
private boolean trace = log.isTraceEnabled();
45
protected boolean connected = false;
46
private Marshaller marshaller;
47
private Map unmarshallers = new WeakHashMap();
48
private String dataType;
49
private final Object clientLeaseLock = new Object();
50
private LeasePinger leasePinger = null;
51
private String invokerSessionID = new GUID().toString();
52
protected boolean parentFirstClassLoading = true;
53
private boolean changeInvalidStateToCannotConnect = false;
55
protected List connectHomes;
57
public MicroRemoteClientInvoker(InvokerLocator locator)
63
public MicroRemoteClientInvoker(InvokerLocator locator, Map configuration)
65
super(locator, configuration);
70
* Transport a request against a remote ServerInvoker.
72
public Object invoke(InvocationRequest invocationReq) throws Throwable
74
Object returnValue = null;
77
if (trace) { log.trace(this + "(" + (++invokeCount) + ") invoking " + invocationReq); }
79
// Set up marshaller and unmarshaller.
80
Marshaller marshaller = null;
81
UnMarshaller unmarshaller = null;
82
RemotingClassLoader rcl = null;
83
synchronized (MicroRemoteClientInvoker.class)
85
marshaller = getMarshaller();
86
if (marshaller == null)
88
// try by locator (in case marshaller class name specified)
89
Map map = passConfigMapToMarshalFactory ? configuration : null;
90
marshaller = MarshalFactory.getMarshaller(getLocator(), getClassLoader(), map);
91
if (marshaller == null)
93
// need to have a marshaller, so create a default one
94
marshaller = MarshalFactory.getMarshaller(getDataType(), getSerializationType());
95
if (marshaller == null)
97
// went as far as possible to find a marshaller, will have to give up
98
throw new InvalidMarshallingResource(
99
"Can not find a valid marshaller for data type: " + getDataType());
102
setMarshaller(marshaller);
105
unmarshaller = getUnMarshaller();
106
if (unmarshaller == null)
108
// try by locator (in case unmarshaller class name specified)
109
Map map = passConfigMapToMarshalFactory ? configuration : null;
110
unmarshaller = MarshalFactory.getUnMarshaller(getLocator(), getClassLoader(), map);
111
if (unmarshaller == null)
113
unmarshaller = MarshalFactory.getUnMarshaller(getDataType(), getSerializationType());
114
if (unmarshaller == null)
116
// went as far as possible to find a unmarshaller, will have to give up
117
throw new InvalidMarshallingResource(
118
"Can not find a valid unmarshaller for data type: " + getDataType());
121
setUnMarshaller(unmarshaller);
124
// Each unmarshaller gets a RemotingClassloader classloader containing the
125
// remoting class loader (for remote classloading) and the current thread's
126
// class loader. This allows to load remoting classes as well as user's
127
// classes. If possible, will simply reset context classloader on existing
128
// RemotingClassLoader.
129
final ClassLoader contextClassLoader = getContextClassLoader(Thread.currentThread());
130
if (unmarshaller instanceof UpdateableClassloaderUnMarshaller)
132
UpdateableClassloaderUnMarshaller uclum = (UpdateableClassloaderUnMarshaller) unmarshaller;
133
ClassLoader cl = uclum.getClassLoader();
134
if (cl instanceof RemotingClassLoader)
136
rcl = (RemotingClassLoader) cl;
137
rcl.setUserClassLoader(contextClassLoader);
141
rcl = createRemotingClassLoader(getClassLoader(), contextClassLoader, parentFirstClassLoading);
142
unmarshaller.setClassLoader(rcl);
147
rcl = createRemotingClassLoader(getClassLoader(), contextClassLoader, parentFirstClassLoading);
148
unmarshaller.setClassLoader(rcl);
152
// if raw, then send only param of invocation request
153
Object payload = null;
154
Map metadata = invocationReq.getRequestPayload();
155
if (metadata != null && metadata.get(Client.RAW) != null)
157
payload = invocationReq.getParameter();
161
payload = invocationReq;
166
String sessionId = invocationReq.getSessionId();
167
returnValue = transport(sessionId, payload, metadata, marshaller, unmarshaller);
171
// Delete reference to current thread's context classloader.
172
rcl.unsetUserClassLoader();
175
// Now check if is remoting response and process
176
if (returnValue instanceof InvocationResponse)
178
InvocationResponse response = (InvocationResponse)returnValue;
179
returnValue = response.getResult();
181
// if is a server side exception, throw it
182
if (response.isException())
184
Throwable e = (Throwable)returnValue;
186
if (trace) { log.trace(this + " received a server-side exception as response to the invocation: " + e); }
188
StackTraceElement[] serverStackTrace;
189
if (e.getCause() != null)
191
serverStackTrace = e.getCause().getStackTrace();
192
if (serverStackTrace == null || serverStackTrace.length == 0)
194
serverStackTrace = e.getStackTrace();
199
serverStackTrace = e.getStackTrace();
202
// need to check that there is a server stack trace. If there is not, need to log
203
// warning here so caller knows that error happened on server side and to look there,
204
// as stack trace is just going to lead them to here, giving the impression that is
205
// a client side exception from this point within remoting client.
206
if (serverStackTrace == null || serverStackTrace.length == 0)
208
log.warn("An exception occurred on the server side when making remote invocation. " +
209
"The exception returned from server does not include a stack trace. " +
210
"Original server side exception message is " + e.getMessage(), e);
213
Exception clientException = new Exception();
214
StackTraceElement[] clientStackTrace = clientException.getStackTrace();
215
StackTraceElement[] completeStackTrace = new StackTraceElement[serverStackTrace.length + clientStackTrace.length];
216
System.arraycopy(serverStackTrace, 0, completeStackTrace, 0, serverStackTrace.length);
217
System.arraycopy(clientStackTrace, 0, completeStackTrace, serverStackTrace.length, clientStackTrace.length);
219
Throwable responseException = null;
220
if (e instanceof ServerInvoker.InvalidStateException && changeInvalidStateToCannotConnect)
222
responseException = new CannotConnectException(e.getMessage(), e.getCause());
226
responseException = e;
229
if (e.getCause() != null)
231
responseException.getCause().setStackTrace(completeStackTrace);
235
responseException.setStackTrace(completeStackTrace);
238
throw responseException;
241
if (trace) { log.trace(this + " received InvocationResponse so going to return response's return value of " + returnValue);}
249
* this method is called prior to making the remote invocation to allow the subclass the ability
250
* to provide additional data or modify the invocation
255
* @param receivedPayload
257
protected void preProcess(String sessionId, Object param, Map sendPayload, Map receivedPayload)
262
* this method is called prior to returning the result for the invocation to allow the subclass the ability
263
* to modify the result result
268
* @param receivedPayload
270
protected void postProcess(String sessionId, Object param, Map sendPayload,
276
protected abstract Object transport(String sessionId, Object invocation, Map metadata,
277
Marshaller marshaller, UnMarshaller unmarshaller)
278
throws IOException, ConnectionFailedException, ClassNotFoundException;
281
* Subclasses must provide this method to return true if their remote connection is connected and
282
* false if disconnected. in some transports, such as SOAP, this method may always return true,
283
* since the remote connectivity is done on demand and not kept persistent like other transports
284
* (such as socket-based transport).
286
* @return boolean true if connected, false if not
288
public boolean isConnected()
294
* Connect to the remote invoker.
296
public synchronized void connect() throws ConnectionFailedException
300
log.debug(this + " connecting");
302
// In single home case, locator's connect homes list consists
303
// locator's host:port. In multihome case, connect homes list
304
// is empty unless explicitly defined.
305
connectHomes = locator.getConnectHomeList();
306
if (locator.isMultihome() && connectHomes.isEmpty())
308
connectHomes = locator.getHomeList();
314
log.debug(this + " connected");
319
* Subclasses must implement this method to provide a hook to connect to the remote server, if
320
* this applies to the specific transport. However, in some transport implementations, this may
321
* not make must difference since the connection is not persistent among invocations, such as
322
* SOAP. In these cases, the method should silently return without any processing.
324
* @throws ConnectionFailedException
327
protected abstract void handleConnect() throws ConnectionFailedException;
330
* Subclasses must implement this method to provide a hook to disconnect from the remote server,
331
* if this applies to the specific transport. However, in some transport implementations, this
332
* may not make must difference since the connection is not persistent among invocations, such as
333
* SOAP. In these cases, the method should silently return without any processing.
335
protected abstract void handleDisconnect();
338
* disconnect from the remote invokere
340
public synchronized void disconnect()
342
if (trace) { log.trace(this + " disconnecting ..."); }
348
ClassLoader classLoader = getClassLoader();
349
if (classLoader != null && classLoader instanceof ClassByteClassLoader)
351
((ClassByteClassLoader) classbyteloader).destroy();
353
if (trace) { log.trace(this + " disconnected"); }
357
if (trace) { log.trace(this + " is not connected!"); }
361
public void setMarshaller(Marshaller marshaller)
363
this.marshaller = marshaller;
366
public Marshaller getMarshaller()
368
return this.marshaller;
371
public void setUnMarshaller(UnMarshaller unmarshaller)
373
ClassLoader classLoader = getContextClassLoader(Thread.currentThread());
374
unmarshallers.put(classLoader, unmarshaller);
377
public UnMarshaller getUnMarshaller()
379
ClassLoader classLoader = getContextClassLoader(Thread.currentThread());
380
return (UnMarshaller)unmarshallers.get(classLoader);
383
public String getSessionId()
385
return this.invokerSessionID;
388
public void terminateLease(String sessionId, int disconnectTimeout)
390
terminateLease(sessionId, disconnectTimeout, null);
393
public void terminateLease(String sessionId, int disconnectTimeout, LeasePinger passedLeasePinger)
395
synchronized(clientLeaseLock)
397
if (passedLeasePinger != null && passedLeasePinger != leasePinger)
399
if (trace) log.trace(this + ": " + passedLeasePinger + " != " + leasePinger);
402
if (trace) log.trace(this + " entering terminateLease() for " + leasePinger);
403
if(leasePinger != null)
405
leasePinger.setDisconnectTimeout(disconnectTimeout);
407
if (sessionId == null)
409
if (trace) log.trace(this + " shutting down LeasePinger: " + leasePinger);
410
// Independent of any particular Client - force LeasePinger shutdown.
411
// Should be called only if there is a reasonable belief that the lease
412
// has already stopped on the server side.
415
leasePinger.stopPing();
419
log.debug("error shutting down lease pinger" + e.getMessage());
420
log.trace("error shutting down lease pinger", e);
426
// Remove a particular Client.
427
if (trace) log.trace(this + " removing client " + sessionId + " from LeasePinger: " + leasePinger);
428
boolean isLastClientLease = leasePinger.removeClient(sessionId);
429
if(isLastClientLease)
431
if (trace) log.trace(this + " shutting down LeasePinger, " + sessionId + " was last client lease: " + leasePinger);
434
leasePinger.stopPing();
438
log.debug("error shutting down lease pinger");
446
if (trace) log.trace(this + " leasePinger is null: must have been shut down already");
448
if (trace) log.trace(this + " leaving terminateLease() for " + leasePinger);
452
public long getLeasePeriod(String sessionID)
454
synchronized(clientLeaseLock)
456
if(leasePinger == null)
461
return leasePinger.getLeasePeriod(sessionID);
465
public void establishLease(String clientSessionID, Map configuration, long leasePeriod)
468
Client client = (Client) configuration.get(Client.CLIENT);
469
ConnectionListener listener = (ConnectionListener) configuration.remove(Client.CONNECTION_LISTENER);
470
boolean useClientConnectionIdentity = false;
471
if (configuration != null)
473
Object o = configuration.get(Remoting.USE_CLIENT_CONNECTION_IDENTITY);
474
if (o instanceof String)
476
useClientConnectionIdentity = Boolean.valueOf((String) o).booleanValue();
480
log.warn("value of " + Remoting.USE_CLIENT_CONNECTION_IDENTITY + " must be a String: " + o);
484
synchronized (clientLeaseLock)
486
// if already have a lease pinger, then already have a client with an established
487
// lease and just need to update the lease pinger
488
if (leasePinger != null)
490
leasePinger.addClient(clientSessionID, configuration, leasePeriod);
491
if (trace) log.trace(this + " added client with session ID " + clientSessionID + " to " + leasePinger);
497
if(trace) { log.trace(this + " sending initial lease ping to server to determine if server has leasing enabled."); }
499
// configuration should NOT be passed as want ping to be specific to client invoker
500
// and NOT to the client.
502
String leasePingerId = new GUID().toString();
503
Map requestMap = new HashMap();
504
requestMap.put(LeasePinger.LEASE_PINGER_ID, leasePingerId);
505
requestMap.put(LeasePinger.TIME_STAMP, Long.toString(System.currentTimeMillis()));
506
if (trace) log.trace(this + " initiating lease for leasePingerId " + leasePingerId);
507
InvocationRequest ir = new InvocationRequest(invokerSessionID, null, "$PING$", requestMap, new HashMap(), null);
509
Object ret = invoke(ir);
511
if (ret instanceof InvocationResponse)
513
InvocationResponse resp = (InvocationResponse) ret;
514
Boolean shouldLease = (Boolean)resp.getResult();
516
if (shouldLease.booleanValue())
518
long defaultLeasePeriod = LeasePinger.DEFAULT_LEASE_PERIOD;
519
Map respMap = resp.getPayload();
523
Long leaseTimeoutValue = (Long)respMap.get("clientLeasePeriod");
524
long serverDefaultLeasePeriod = leaseTimeoutValue.longValue();
525
if(serverDefaultLeasePeriod > 0)
527
defaultLeasePeriod = serverDefaultLeasePeriod;
531
if(trace) { log.trace("server does have leasing enabled (with default lease period of " + defaultLeasePeriod + ") and will start a new lease pinger."); }
533
leasePinger = new LeasePinger(this, invokerSessionID, defaultLeasePeriod, configuration);
534
leasePinger.setLeasePingerId(leasePingerId);
535
leasePinger.setUseClientConnectionIdentity(useClientConnectionIdentity);
536
leasePinger.addClient(clientSessionID, configuration, leasePeriod);
537
leasePinger.startPing();
541
catch (Throwable throwable)
543
Exception e = new Exception("Error setting up client lease");
544
e.initCause(throwable);
549
if (trace) log.trace(this + ": client = " + client + ", listener = " + listener);
550
if (client != null && listener != null)
552
client.addConnectionListener(listener, configuration);
558
* Will get the data type for the marshaller factory so know which marshaller to
559
* get to marshal the data. Will first check the locator uri for a 'datatype'
560
* parameter and take that value if it exists. Otherwise, will use the
561
* default datatype for the client invoker, based on transport.
563
private String getDataType()
565
if (dataType == null)
567
String localDataType = getDataType(getLocator());
568
if (localDataType == null)
570
localDataType = getDefaultDataType();
572
dataType = localDataType;
577
private String getDataType(InvokerLocator locator)
583
Map params = locator.getParameters();
586
type = (String) params.get(InvokerLocator.DATATYPE);
589
type = (String) params.get(InvokerLocator.DATATYPE_CASED);
596
protected void init()
598
// Get the parent delegation order flag, default is parent first
599
Object flag = super.getConfiguration().get(Remoting.CLASSLOADING_PARENT_FIRST_DELEGATION);
602
// Fallback to the system property
603
flag = getSystemProperty(Remoting.CLASSLOADING_PARENT_FIRST_DELEGATION_PROP);
605
boolean parentFirst = true;
608
String sflag = flag.toString();
609
parentFirst = Boolean.valueOf(sflag).booleanValue();
611
parentFirstClassLoading = parentFirst;
613
flag = configuration.get(Remoting.CHANGE_INVALID_STATE_TO_CANNOT_CONNECT);
616
String sflag = flag.toString();
617
changeInvalidStateToCannotConnect = Boolean.valueOf(sflag).booleanValue();
622
* Each implementation of the remote client invoker should have
623
* a default data type that is uses in the case it is not specified
624
* in the invoker locator uri.
626
protected abstract String getDefaultDataType();
629
protected List getConnectHomes()
635
* Called by the garbage collector on an object when garbage collection
636
* determines that there are no more references to the object.
637
* A subclass overrides the <code>finalize</code> method to dispose of
638
* system resources or to perform other cleanup.
640
* The general contract of <tt>finalize</tt> is that it is invoked
641
* if and when the Java<font size="-2"><sup>TM</sup></font> virtual
642
* machine has determined that there is no longer any
643
* means by which this object can be accessed by any thread that has
644
* not yet died, except as a result of an action taken by the
645
* finalization of some other object or class which is ready to be
646
* finalized. The <tt>finalize</tt> method may take any action, including
647
* making this object available again to other threads; the usual purpose
648
* of <tt>finalize</tt>, however, is to perform cleanup actions before
649
* the object is irrevocably discarded. For example, the finalize method
650
* for an object that represents an input/output connection might perform
651
* explicit I/O transactions to break the connection before the object is
652
* permanently discarded.
654
* The <tt>finalize</tt> method of class <tt>Object</tt> performs no
655
* special action; it simply returns normally. Subclasses of
656
* <tt>Object</tt> may override this definition.
658
* The Java programming language does not guarantee which thread will
659
* transport the <tt>finalize</tt> method for any given object. It is
660
* guaranteed, however, that the thread that invokes finalize will not
661
* be holding any user-visible synchronization locks when finalize is
662
* invoked. If an uncaught exception is thrown by the finalize method,
663
* the exception is ignored and finalization of that object terminates.
665
* After the <tt>finalize</tt> method has been invoked for an object, no
666
* further action is taken until the Java virtual machine has again
667
* determined that there is no longer any means by which this object can
668
* be accessed by any thread that has not yet died, including possible
669
* actions by other objects or classes which are ready to be finalized,
670
* at which point the object may be discarded.
672
* The <tt>finalize</tt> method is never invoked more than once by a Java
673
* virtual machine for any given object.
675
* Any exception thrown by the <code>finalize</code> method causes
676
* the finalization of this object to be halted, but is otherwise
679
* @throws Throwable the <code>Exception</code> raised by this method
681
protected void finalize() throws Throwable
687
protected LeasePinger getLeasePinger()
689
synchronized(clientLeaseLock)
695
static private String getSystemProperty(final String name)
697
if (SecurityUtility.skipAccessControl())
698
return System.getProperty(name);
703
value = (String)AccessController.doPrivileged( new PrivilegedExceptionAction()
705
public Object run() throws Exception
707
return System.getProperty(name);
711
catch (PrivilegedActionException e)
713
throw (RuntimeException) e.getCause();
719
static private RemotingClassLoader createRemotingClassLoader(final ClassLoader remotingClassLoader,
720
final ClassLoader userClassLoader, final boolean parentFirstDelegation)
722
if (SecurityUtility.skipAccessControl())
724
return new RemotingClassLoader(remotingClassLoader, userClassLoader, parentFirstDelegation);
727
return (RemotingClassLoader)AccessController.doPrivileged( new PrivilegedAction()
731
return new RemotingClassLoader(remotingClassLoader, userClassLoader, parentFirstDelegation);
736
static private ClassLoader getContextClassLoader(final Thread thread)
738
if (SecurityUtility.skipAccessControl())
740
return thread.getContextClassLoader();
743
return (ClassLoader) AccessController.doPrivileged( new PrivilegedAction()
747
return thread.getContextClassLoader();