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: 5868 $
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
synchronized (MicroRemoteClientInvoker.class)
174
rcl.unsetUserClassLoader();
178
// Now check if is remoting response and process
179
if (returnValue instanceof InvocationResponse)
181
InvocationResponse response = (InvocationResponse)returnValue;
182
returnValue = response.getResult();
184
// if is a server side exception, throw it
185
if (response.isException())
187
Throwable e = (Throwable)returnValue;
189
if (trace) { log.trace(this + " received a server-side exception as response to the invocation: " + e); }
191
StackTraceElement[] serverStackTrace;
192
if (e.getCause() != null)
194
serverStackTrace = e.getCause().getStackTrace();
195
if (serverStackTrace == null || serverStackTrace.length == 0)
197
serverStackTrace = e.getStackTrace();
202
serverStackTrace = e.getStackTrace();
205
// need to check that there is a server stack trace. If there is not, need to log
206
// warning here so caller knows that error happened on server side and to look there,
207
// as stack trace is just going to lead them to here, giving the impression that is
208
// a client side exception from this point within remoting client.
209
if (serverStackTrace == null || serverStackTrace.length == 0)
211
log.warn("An exception occurred on the server side when making remote invocation. " +
212
"The exception returned from server does not include a stack trace. " +
213
"Original server side exception message is " + e.getMessage(), e);
216
Exception clientException = new Exception();
217
StackTraceElement[] clientStackTrace = clientException.getStackTrace();
218
StackTraceElement[] completeStackTrace = new StackTraceElement[serverStackTrace.length + clientStackTrace.length];
219
System.arraycopy(serverStackTrace, 0, completeStackTrace, 0, serverStackTrace.length);
220
System.arraycopy(clientStackTrace, 0, completeStackTrace, serverStackTrace.length, clientStackTrace.length);
222
Throwable responseException = null;
223
if (e instanceof ServerInvoker.InvalidStateException && changeInvalidStateToCannotConnect)
225
responseException = new CannotConnectException(e.getMessage(), e.getCause());
229
responseException = e;
232
if (e.getCause() != null)
234
responseException.getCause().setStackTrace(completeStackTrace);
238
responseException.setStackTrace(completeStackTrace);
241
throw responseException;
244
if (trace) { log.trace(this + " received InvocationResponse so going to return response's return value of " + returnValue);}
252
* this method is called prior to making the remote invocation to allow the subclass the ability
253
* to provide additional data or modify the invocation
258
* @param receivedPayload
260
protected void preProcess(String sessionId, Object param, Map sendPayload, Map receivedPayload)
265
* this method is called prior to returning the result for the invocation to allow the subclass the ability
266
* to modify the result result
271
* @param receivedPayload
273
protected void postProcess(String sessionId, Object param, Map sendPayload,
279
protected abstract Object transport(String sessionId, Object invocation, Map metadata,
280
Marshaller marshaller, UnMarshaller unmarshaller)
281
throws IOException, ConnectionFailedException, ClassNotFoundException;
284
* Subclasses must provide this method to return true if their remote connection is connected and
285
* false if disconnected. in some transports, such as SOAP, this method may always return true,
286
* since the remote connectivity is done on demand and not kept persistent like other transports
287
* (such as socket-based transport).
289
* @return boolean true if connected, false if not
291
public boolean isConnected()
297
* Connect to the remote invoker.
299
public synchronized void connect() throws ConnectionFailedException
303
log.debug(this + " connecting");
305
// In single home case, locator's connect homes list consists
306
// locator's host:port. In multihome case, connect homes list
307
// is empty unless explicitly defined.
308
connectHomes = locator.getConnectHomeList();
309
if (locator.isMultihome() && connectHomes.isEmpty())
311
connectHomes = locator.getHomeList();
317
log.debug(this + " connected");
322
* Subclasses must implement this method to provide a hook to connect to the remote server, if
323
* this applies to the specific transport. However, in some transport implementations, this may
324
* not make must difference since the connection is not persistent among invocations, such as
325
* SOAP. In these cases, the method should silently return without any processing.
327
* @throws ConnectionFailedException
330
protected abstract void handleConnect() throws ConnectionFailedException;
333
* Subclasses must implement this method to provide a hook to disconnect from the remote server,
334
* if this applies to the specific transport. However, in some transport implementations, this
335
* may not make must difference since the connection is not persistent among invocations, such as
336
* SOAP. In these cases, the method should silently return without any processing.
338
protected abstract void handleDisconnect();
341
* disconnect from the remote invokere
343
public synchronized void disconnect()
345
if (trace) { log.trace(this + " disconnecting ..."); }
351
ClassLoader classLoader = getClassLoader();
352
if (classLoader != null && classLoader instanceof ClassByteClassLoader)
354
((ClassByteClassLoader) classbyteloader).destroy();
356
if (trace) { log.trace(this + " disconnected"); }
360
if (trace) { log.trace(this + " is not connected!"); }
364
public void setMarshaller(Marshaller marshaller)
366
this.marshaller = marshaller;
369
public Marshaller getMarshaller()
371
return this.marshaller;
374
public void setUnMarshaller(UnMarshaller unmarshaller)
376
ClassLoader classLoader = getContextClassLoader(Thread.currentThread());
377
unmarshallers.put(classLoader, unmarshaller);
380
public UnMarshaller getUnMarshaller()
382
ClassLoader classLoader = getContextClassLoader(Thread.currentThread());
383
return (UnMarshaller)unmarshallers.get(classLoader);
386
public String getSessionId()
388
return this.invokerSessionID;
391
public void terminateLease(String sessionId, int disconnectTimeout)
393
terminateLease(sessionId, disconnectTimeout, null);
396
public void terminateLease(String sessionId, int disconnectTimeout, LeasePinger passedLeasePinger)
398
synchronized(clientLeaseLock)
400
if (passedLeasePinger != null && passedLeasePinger != leasePinger)
402
if (trace) log.trace(this + ": " + passedLeasePinger + " != " + leasePinger);
405
if (trace) log.trace(this + " entering terminateLease() for " + leasePinger);
406
if(leasePinger != null)
408
leasePinger.setDisconnectTimeout(disconnectTimeout);
410
if (sessionId == null)
412
if (trace) log.trace(this + " shutting down LeasePinger: " + leasePinger);
413
// Independent of any particular Client - force LeasePinger shutdown.
414
// Should be called only if there is a reasonable belief that the lease
415
// has already stopped on the server side.
418
leasePinger.stopPing();
422
log.debug("error shutting down lease pinger" + e.getMessage());
423
log.trace("error shutting down lease pinger", e);
429
// Remove a particular Client.
430
if (trace) log.trace(this + " removing client " + sessionId + " from LeasePinger: " + leasePinger);
431
boolean isLastClientLease = leasePinger.removeClient(sessionId);
432
if(isLastClientLease)
434
if (trace) log.trace(this + " shutting down LeasePinger, " + sessionId + " was last client lease: " + leasePinger);
437
leasePinger.stopPing();
441
log.debug("error shutting down lease pinger");
449
if (trace) log.trace(this + " leasePinger is null: must have been shut down already");
451
if (trace) log.trace(this + " leaving terminateLease() for " + leasePinger);
455
public long getLeasePeriod(String sessionID)
457
synchronized(clientLeaseLock)
459
if(leasePinger == null)
464
return leasePinger.getLeasePeriod(sessionID);
468
public void establishLease(String clientSessionID, Map configuration, long leasePeriod)
471
Client client = (Client) configuration.get(Client.CLIENT);
472
ConnectionListener listener = (ConnectionListener) configuration.remove(Client.CONNECTION_LISTENER);
473
boolean useClientConnectionIdentity = false;
474
if (configuration != null)
476
Object o = configuration.get(Remoting.USE_CLIENT_CONNECTION_IDENTITY);
477
if (o instanceof String)
479
useClientConnectionIdentity = Boolean.valueOf((String) o).booleanValue();
483
log.warn("value of " + Remoting.USE_CLIENT_CONNECTION_IDENTITY + " must be a String: " + o);
487
synchronized (clientLeaseLock)
489
// if already have a lease pinger, then already have a client with an established
490
// lease and just need to update the lease pinger
491
if (leasePinger != null)
493
leasePinger.addClient(clientSessionID, configuration, leasePeriod);
494
if (trace) log.trace(this + " added client with session ID " + clientSessionID + " to " + leasePinger);
500
if(trace) { log.trace(this + " sending initial lease ping to server to determine if server has leasing enabled."); }
502
// configuration should NOT be passed as want ping to be specific to client invoker
503
// and NOT to the client.
505
String leasePingerId = new GUID().toString();
506
Map requestMap = new HashMap();
507
requestMap.put(LeasePinger.LEASE_PINGER_ID, leasePingerId);
508
requestMap.put(LeasePinger.TIME_STAMP, Long.toString(System.currentTimeMillis()));
509
if (trace) log.trace(this + " initiating lease for leasePingerId " + leasePingerId);
510
InvocationRequest ir = new InvocationRequest(invokerSessionID, null, "$PING$", requestMap, new HashMap(), null);
512
Object ret = invoke(ir);
514
if (ret instanceof InvocationResponse)
516
InvocationResponse resp = (InvocationResponse) ret;
517
Boolean shouldLease = (Boolean)resp.getResult();
519
if (shouldLease.booleanValue())
521
long defaultLeasePeriod = LeasePinger.DEFAULT_LEASE_PERIOD;
522
Map respMap = resp.getPayload();
526
Long leaseTimeoutValue = (Long)respMap.get("clientLeasePeriod");
527
long serverDefaultLeasePeriod = leaseTimeoutValue.longValue();
528
if(serverDefaultLeasePeriod > 0)
530
defaultLeasePeriod = serverDefaultLeasePeriod;
534
if(trace) { log.trace("server does have leasing enabled (with default lease period of " + defaultLeasePeriod + ") and will start a new lease pinger."); }
536
leasePinger = new LeasePinger(this, invokerSessionID, defaultLeasePeriod, configuration);
537
leasePinger.setLeasePingerId(leasePingerId);
538
leasePinger.setUseClientConnectionIdentity(useClientConnectionIdentity);
539
leasePinger.addClient(clientSessionID, configuration, leasePeriod);
540
leasePinger.startPing();
544
catch (Throwable throwable)
546
Exception e = new Exception("Error setting up client lease");
547
e.initCause(throwable);
552
if (trace) log.trace(this + ": client = " + client + ", listener = " + listener);
553
if (client != null && listener != null)
555
client.addConnectionListener(listener, configuration);
561
* Will get the data type for the marshaller factory so know which marshaller to
562
* get to marshal the data. Will first check the locator uri for a 'datatype'
563
* parameter and take that value if it exists. Otherwise, will use the
564
* default datatype for the client invoker, based on transport.
566
private String getDataType()
568
if (dataType == null)
570
String localDataType = getDataType(getLocator());
571
if (localDataType == null)
573
localDataType = getDefaultDataType();
575
dataType = localDataType;
580
private String getDataType(InvokerLocator locator)
586
Map params = locator.getParameters();
589
type = (String) params.get(InvokerLocator.DATATYPE);
592
type = (String) params.get(InvokerLocator.DATATYPE_CASED);
599
protected void init()
601
// Get the parent delegation order flag, default is parent first
602
Object flag = super.getConfiguration().get(Remoting.CLASSLOADING_PARENT_FIRST_DELEGATION);
605
// Fallback to the system property
606
flag = getSystemProperty(Remoting.CLASSLOADING_PARENT_FIRST_DELEGATION_PROP);
608
boolean parentFirst = true;
611
String sflag = flag.toString();
612
parentFirst = Boolean.valueOf(sflag).booleanValue();
614
parentFirstClassLoading = parentFirst;
616
flag = configuration.get(Remoting.CHANGE_INVALID_STATE_TO_CANNOT_CONNECT);
619
String sflag = flag.toString();
620
changeInvalidStateToCannotConnect = Boolean.valueOf(sflag).booleanValue();
625
* Each implementation of the remote client invoker should have
626
* a default data type that is uses in the case it is not specified
627
* in the invoker locator uri.
629
protected abstract String getDefaultDataType();
632
protected List getConnectHomes()
638
* Called by the garbage collector on an object when garbage collection
639
* determines that there are no more references to the object.
640
* A subclass overrides the <code>finalize</code> method to dispose of
641
* system resources or to perform other cleanup.
643
* The general contract of <tt>finalize</tt> is that it is invoked
644
* if and when the Java<font size="-2"><sup>TM</sup></font> virtual
645
* machine has determined that there is no longer any
646
* means by which this object can be accessed by any thread that has
647
* not yet died, except as a result of an action taken by the
648
* finalization of some other object or class which is ready to be
649
* finalized. The <tt>finalize</tt> method may take any action, including
650
* making this object available again to other threads; the usual purpose
651
* of <tt>finalize</tt>, however, is to perform cleanup actions before
652
* the object is irrevocably discarded. For example, the finalize method
653
* for an object that represents an input/output connection might perform
654
* explicit I/O transactions to break the connection before the object is
655
* permanently discarded.
657
* The <tt>finalize</tt> method of class <tt>Object</tt> performs no
658
* special action; it simply returns normally. Subclasses of
659
* <tt>Object</tt> may override this definition.
661
* The Java programming language does not guarantee which thread will
662
* transport the <tt>finalize</tt> method for any given object. It is
663
* guaranteed, however, that the thread that invokes finalize will not
664
* be holding any user-visible synchronization locks when finalize is
665
* invoked. If an uncaught exception is thrown by the finalize method,
666
* the exception is ignored and finalization of that object terminates.
668
* After the <tt>finalize</tt> method has been invoked for an object, no
669
* further action is taken until the Java virtual machine has again
670
* determined that there is no longer any means by which this object can
671
* be accessed by any thread that has not yet died, including possible
672
* actions by other objects or classes which are ready to be finalized,
673
* at which point the object may be discarded.
675
* The <tt>finalize</tt> method is never invoked more than once by a Java
676
* virtual machine for any given object.
678
* Any exception thrown by the <code>finalize</code> method causes
679
* the finalization of this object to be halted, but is otherwise
682
* @throws Throwable the <code>Exception</code> raised by this method
684
protected void finalize() throws Throwable
690
protected LeasePinger getLeasePinger()
692
synchronized(clientLeaseLock)
698
static private String getSystemProperty(final String name)
700
if (SecurityUtility.skipAccessControl())
701
return System.getProperty(name);
706
value = (String)AccessController.doPrivileged( new PrivilegedExceptionAction()
708
public Object run() throws Exception
710
return System.getProperty(name);
714
catch (PrivilegedActionException e)
716
throw (RuntimeException) e.getCause();
722
static private RemotingClassLoader createRemotingClassLoader(final ClassLoader remotingClassLoader,
723
final ClassLoader userClassLoader, final boolean parentFirstDelegation)
725
if (SecurityUtility.skipAccessControl())
727
return new RemotingClassLoader(remotingClassLoader, userClassLoader, parentFirstDelegation);
730
return (RemotingClassLoader)AccessController.doPrivileged( new PrivilegedAction()
734
return new RemotingClassLoader(remotingClassLoader, userClassLoader, parentFirstDelegation);
739
static private ClassLoader getContextClassLoader(final Thread thread)
741
if (SecurityUtility.skipAccessControl())
743
return thread.getContextClassLoader();
746
return (ClassLoader) AccessController.doPrivileged( new PrivilegedAction()
750
return thread.getContextClassLoader();