| RemoteClientInvoker.java |
/***************************************
* *
* JBoss: The OpenSource J2EE WebOS *
* *
* Distributable under LGPL license. *
* See terms of license at gnu.org. *
* *
***************************************/
package org.jboss.remoting;
import org.jboss.remoting.marshal.MarshalFactory;
import org.jboss.remoting.marshal.Marshaller;
import org.jboss.remoting.marshal.UnMarshaller;
import org.jboss.remoting.marshal.InvalidMarshallingResource;
import org.jboss.remoting.transport.ClientInvoker;
import org.jboss.remoting.loading.ClassByteClassLoader;
import java.io.IOException;
import java.util.Map;
/**
* RemoteClientInvoker is an abstract client part handler that implements the bulk of the heavy
* lifting to process a remote method and dispatch it to a remote ServerInvoker and handle the result. <P>
* <p/>
* Specialized Client/Server Invokers might add additional functionality as part of the invocation - such as
* delivering queued notifcations from a remote server by adding the notification objects during each invocation
* to the invocation result payload and then having the client re-dispatch the notifications locally upon
* receiving the return invocation result.
*
* @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>
* @author <a href="mailto:telrod@e2technologies.net">Tom Elrod</a>
* @version $Revision: 1.2.8.2 $
*/
public abstract class RemoteClientInvoker extends AbstractInvoker implements ClientInvoker
{
protected boolean connected = false;
private Marshaller marshaller;
private UnMarshaller unmarshaller;
private String dataType;
public RemoteClientInvoker(InvokerLocator locator)
{
super(locator);
}
/**
* transport a request against a remote ServerInvoker
*
* @param invocationReq
* @return
* @throws Throwable
*/
public Object invoke(InvocationRequest invocationReq)
throws Throwable
{
Object returnValue = null;
int invokeCount = 0;
if (log.isDebugEnabled())
{
log.debug((++invokeCount) + ") invoking =>" + invocationReq + " with parameter: " + invocationReq.getParameter());
}
//TOOD: -TME Need to find a better way of setting this
// Need to set the local server locator so can make callbacks to this locator on server side.
invocationReq.setLocator(localServerLocator);
Marshaller marshaller = getMarshaller();
UnMarshaller unmarshaller = getUnMarshaller();
if (marshaller == null)
{
// try by locator (in case marshaller class name specified)
marshaller = MarshalFactory.getMarshaller(getLocator(), getClassLoader());
if(marshaller == null)
{
// need to have a marshaller, so create a default one
marshaller = MarshalFactory.getMarshaller(getDataType());
if(marshaller == null)
{
// went as far as possible to find a marshaller, will have to give up
throw new InvalidMarshallingResource("Can not find a valid marshaller for data type: " + getDataType());
}
}
}
if (unmarshaller == null)
{
// try by locator (in case unmarshaller class name specified)
unmarshaller = MarshalFactory.getUnMarshaller(getLocator(), getClassLoader());
if(unmarshaller == null)
{
unmarshaller = MarshalFactory.getUnMarshaller(getDataType());
if(unmarshaller == null)
{
// went as far as possible to find a unmarshaller, will have to give up
throw new InvalidMarshallingResource("Can not find a valid unmarshaller for data type: " + getDataType());
}
}
}
try
{
// if raw, then send only param of invocation request
Object payload = null;
Map metadata = invocationReq.getRequestPayload();
if(metadata != null && metadata.get(Client.RAW) != null)
{
payload = invocationReq.getParameter();
}
else
{
payload = invocationReq;
}
returnValue = transport(invocationReq.getSessionId(), payload, metadata, marshaller, unmarshaller);
if (log.isDebugEnabled())
{
log.debug("received result=>" + returnValue);
}
// Now check if is remoting response and process
if(returnValue instanceof InvocationResponse)
{
InvocationResponse response = (InvocationResponse)returnValue;
returnValue = response.getResult();
if(log.isDebugEnabled())
{
log.debug("received result was an InvocationResponse so going to return response's return value of " + returnValue);
log.debug("response is exception = " + response.isException());
}
// if is a server side exception, throw it
if(response.isException())
{
throw (Throwable)returnValue;
}
}
}
catch (ClassNotFoundException cnf)
{
//TODO: -TME For now, just throw cnf, later will add dynamic classloading here.
throw cnf;
}
return returnValue;
}
/**
* this method is called prior to making the remote invocation to allow the subclass the ability
* to provide additional data or modify the invocation
*
* @param sessionId
* @param param
* @param sendPayload
* @param receivedPayload
*/
protected void preProcess(String sessionId, Object param, Map sendPayload, Map receivedPayload)
{
}
/**
* this method is called prior to returning the result for the invocation to allow the subclass the ability
* to modify the result result
*
* @param sessionId
* @param param
* @param sendPayload
* @param receivedPayload
*/
protected void postProcess(String sessionId, Object param, Map sendPayload,
Map receivedPayload)
{
}
/**
*
* @param sessionId
* @param invocation
* @param marshaller
* @return
* @throws IOException
* @throws ConnectionFailedException
*/
protected abstract Object transport(String sessionId, Object invocation, Map metadata, Marshaller marshaller, UnMarshaller unmarshaller)
throws IOException, ConnectionFailedException, ClassNotFoundException;
/**
* subclasses must provide this method to return true if their remote connection is connected and
* false if disconnected. in some transports, such as SOAP, this method may always return true, since the
* remote connectivity is done on demand and not kept persistent like other transports (such as socket-based
* transport).
*
* @return boolean true if connected, false if not
*/
public boolean isConnected()
{
return connected;
}
/**
* connect to the remote invoker
*
* @throws ConnectionFailedException
*/
public synchronized void connect()
throws ConnectionFailedException
{
if (!connected)
{
if (log.isDebugEnabled())
{
log.debug("connect called for: " + this);
}
handleConnect();
connected = true;
}
}
/**
* subclasses must implement this method to provide a hook to connect to the remote server, if this applies
* to the specific transport. However, in some transport implementations, this may not make must difference since
* the connection is not persistent among invocations, such as SOAP. In these cases, the method should
* silently return without any processing.
*
* @throws ConnectionFailedException
*/
protected abstract void handleConnect()
throws ConnectionFailedException;
/**
* subclasses must implement this method to provide a hook to disconnect from the remote server, if this applies
* to the specific transport. However, in some transport implementations, this may not make must difference since
* the connection is not persistent among invocations, such as SOAP. In these cases, the method should
* silently return without any processing.
*/
protected abstract void handleDisconnect();
/**
* disconnect from the remote invokere
*/
public synchronized void disconnect()
{
if (connected)
{
if (log.isDebugEnabled())
{
log.debug("disconnect called for: " + this);
}
connected = false;
handleDisconnect();
ClassLoader classLoader = getClassLoader();
if(classLoader != null && classLoader instanceof ClassByteClassLoader)
{
((ClassByteClassLoader)classbyteloader).destroy();
}
}
}
public void setMarshaller(Marshaller marshaller)
{
this.marshaller = marshaller;
}
public Marshaller getMarshaller()
{
return this.marshaller;
}
public void setUnMarshaller(UnMarshaller unmarshaller)
{
this.unmarshaller = unmarshaller;
}
public UnMarshaller getUnMarshaller()
{
return this.unmarshaller;
}
/**
* Will get the data type for the marshaller factory so know which marshaller to
* get to marshal the data. Will first check the locator uri for a 'datatype'
* parameter and take that value if it exists. Otherwise, will use the
* default datatype for the client invoker, based on transport.
*
* @return
*/
private String getDataType()
{
if (dataType == null)
{
dataType = getDataType(getLocator());
if (dataType == null)
{
dataType = getDefaultDataType();
}
}
return dataType;
}
private String getDataType(InvokerLocator locator)
{
String type = null;
if (locator != null)
{
Map params = locator.getParameters();
if (params != null)
{
type = (String) params.get(InvokerLocator.DATATYPE);
if(type == null)
{
type = (String) params.get(InvokerLocator.DATATYPE_CASED);
}
}
}
return type;
}
/**
* Each implementation of the remote client invoker should have
* a default data type that is uses in the case it is not specified
* in the invoker locator uri.
*
* @return
*/
protected abstract String getDefaultDataType();
/**
* Called by the garbage collector on an object when garbage collection
* determines that there are no more references to the object.
* A subclass overrides the <code>finalize</code> method to dispose of
* system resources or to perform other cleanup.
* <p/>
* The general contract of <tt>finalize</tt> is that it is invoked
* if and when the Java<font size="-2"><sup>TM</sup></font> virtual
* machine has determined that there is no longer any
* means by which this object can be accessed by any thread that has
* not yet died, except as a result of an action taken by the
* finalization of some other object or class which is ready to be
* finalized. The <tt>finalize</tt> method may take any action, including
* making this object available again to other threads; the usual purpose
* of <tt>finalize</tt>, however, is to perform cleanup actions before
* the object is irrevocably discarded. For example, the finalize method
* for an object that represents an input/output connection might perform
* explicit I/O transactions to break the connection before the object is
* permanently discarded.
* <p/>
* The <tt>finalize</tt> method of class <tt>Object</tt> performs no
* special action; it simply returns normally. Subclasses of
* <tt>Object</tt> may override this definition.
* <p/>
* The Java programming language does not guarantee which thread will
* transport the <tt>finalize</tt> method for any given object. It is
* guaranteed, however, that the thread that invokes finalize will not
* be holding any user-visible synchronization locks when finalize is
* invoked. If an uncaught exception is thrown by the finalize method,
* the exception is ignored and finalization of that object terminates.
* <p/>
* After the <tt>finalize</tt> method has been invoked for an object, no
* further action is taken until the Java virtual machine has again
* determined that there is no longer any means by which this object can
* be accessed by any thread that has not yet died, including possible
* actions by other objects or classes which are ready to be finalized,
* at which point the object may be discarded.
* <p/>
* The <tt>finalize</tt> method is never invoked more than once by a Java
* virtual machine for any given object.
* <p/>
* Any exception thrown by the <code>finalize</code> method causes
* the finalization of this object to be halted, but is otherwise
* ignored.
*
* @throws Throwable the <code>Exception</code> raised by this method
*/
protected void finalize() throws Throwable
{
disconnect();
super.finalize();
}
}
| RemoteClientInvoker.java |