/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.remoting;
import org.jboss.logging.Logger;
import org.jboss.remoting.callback.CallbackPoller;
import org.jboss.remoting.callback.InvokerCallbackHandler;
import org.jboss.remoting.invocation.InternalInvocation;
import org.jboss.remoting.invocation.OnewayInvocation;
import org.jboss.remoting.marshal.Marshaller;
import org.jboss.remoting.marshal.UnMarshaller;
import org.jboss.remoting.stream.StreamServer;
import org.jboss.remoting.transport.BidirectionalClientInvoker;
import org.jboss.remoting.transport.ClientInvoker;
import org.jboss.remoting.transport.Connector;
import org.jboss.remoting.transport.PortUtil;
import org.jboss.remoting.transport.local.LocalClientInvoker;
import org.jboss.util.id.GUID;
import org.jboss.util.threadpool.BasicThreadPool;
import org.jboss.util.threadpool.BlockingMode;
import org.jboss.util.threadpool.ThreadPool;
import javax.net.SocketFactory;
import java.io.Externalizable;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.StreamCorruptedException;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Client is a convience class for invoking remote methods for a given subsystem.
* It is intended to be the main user interface for making remote invocation
* on the client side.
*
* @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>
* @author <a href="mailto:telrod@e2technologies.net">Tom Elrod</a>
* @version $Revision: 1.48 $
*/
public class Client implements Externalizable
{
/**
* Key to be used when tracking callback listeners.
*/
public static final String LISTENER_ID_KEY = "listenerId";
/**
* Specifies the default number of work threads in the pool for
* executing one way invocations on the client.
* Value is 10.
*/
public static final int MAX_NUM_ONEWAY_THREADS = 10;
/**
* The key to use for the metadata Map passed when making a invoke() call
* and wish for the invocation payload to be sent as is and not wrapped
* within a remoting invocation request object. This should be used
* when want to make direct calls on systems outside of remoting
* (e.g. making a http POST request to a web service).
*/
public static final String RAW = "rawPayload";
/**
* Key for the configuration map passed to the Client constructor
* to indicate that client should make initial request to establish
* lease with server. The value for this should be
* either a String that java.lang.Boolean can evaluate or a java.lang.Boolean.
* Client leasing is turned off by default, so would need to use this property
* to turn client leasing on.
*/
public static final String ENABLE_LEASE = "enableLease";
/**
* Key for the configuration map passed to the Client constructor providing a
* ssl javax.net.ssl.HandshakeCompletedListener implementation, which will
* be called on when ssl handshake completed with server.
*/
public static final String HANDSHAKE_COMPLETED_LISTENER = "handshakeCompletedListener";
/**
* Key for the configuration when adding a callback handler and internal callback server
* connector is created. The value should be the transport protocol to be used. By default
* will use the same protocol as being used by this client (e.g. http, socket, rmi, multiplex, etc.)
*/
public static final String CALLBACK_SERVER_PROTOCOL = "callbackServerProtocol";
/**
* Key for the configuration when adding a callback handler and internal callback server
* connector is created. The value should be the host name to be used. By default
* will use the result of calling InetAddress.getLocalHost().getHostAddress().
*/
public static final String CALLBACK_SERVER_HOST = "callbackServerHost";
/**
* Key for the configuration when adding a callback handler and internal callback server
* connector is created. The value should be the port to be used. By default
* will find a random unused port.
*/
public static final String CALLBACK_SERVER_PORT = "callbackServerPort";
/**
* Indicated the max number of threads used within oneway thread pool.
*/
private int maxNumberThreads = MAX_NUM_ONEWAY_THREADS;
private static final Logger log = Logger.getLogger(Client.class);
private ClientInvoker invoker;
private ClassLoader classloader;
private String subsystem;
private String sessionId = new GUID().toString();
private ThreadPool onewayThreadPool;
private InvokerLocator locator;
private ConnectionValidator connectionValidator = null;
private LeasePinger leasePinger = null;
private Map configuration = null;
private boolean enableLease = false;
private long leasePeriod = -1;
private Map callbackConnectors = new HashMap();
private Map callbackPollers = new HashMap();
private SocketFactory socketFactory;
private static final long serialVersionUID = 5679279425009837934L;
/**
* Constructs a remoting client with intended target server specified via the lcoator,
* without specifing a remote subsystem or including any metadata.
* Same as calling Client(locator, null, null);
*
* @param locator
* @throws Exception
*/
public Client(InvokerLocator locator) throws Exception
{
this(locator, null, null);
}
/**
* Constructs a remoting client with intended target server specified via the locator
* and configuration metadata. The metadata supplied will be used when creating client
* invoker (in the case specific data is required) and also for passing along additional
* data to connection listeners on the server side in the case that the client fails, will
* be able to use this extra information when notified.
*
* @param locator
* @param configuration
* @throws Exception
*/
public Client(InvokerLocator locator, Map configuration) throws Exception
{
this(locator, null, configuration);
}
/**
* Constructs a remoting client with intended target server specified via the locator
* and intended subsystem on server for invocations to be routed to.
*
* @param locator
* @param subsystem
* @throws Exception
*/
public Client(InvokerLocator locator, String subsystem)
throws Exception
{
this(locator, subsystem, null);
}
/**
* Constructs a remoting client with intended target server specified via the locator, intended subsystem
* on the server for invocations to be routed to, and configuration metadata.
* The metadata supplied will be used when creating client
* invoker (in the case specific data is required) and also for passing along additional
* data to connection listeners on the server side in the case that the client fails, will
* be able to use this extra information when notified.
*
* @param locator
* @param subsystem
* @param configuration
* @throws Exception
*/
public Client(InvokerLocator locator, String subsystem, Map configuration)
throws Exception
{
this(Thread.currentThread().getContextClassLoader(), locator, subsystem, configuration);
}
/**
* Constructs a remoting client with intended target server specified via the locator, intended subsystem
* on the server for invocations to be routed to, and configuration metadata.
* The metadata supplied will be used when creating client
* invoker (in the case specific data is required) and also for passing along additional
* data to connection listeners on the server side in the case that the client fails, will
* be able to use this extra information when notified (which will happen when connect() method
* is called.
*
* @param cl - the classloader that should be used by remoting
* @param locator
* @param subsystem
* @param configuration
* @throws Exception
* @deprecated This constructor should not be used any more as will no longer take into
* account the classloader specified as a parameter.
*/
public Client(ClassLoader cl, InvokerLocator locator, String subsystem, Map configuration)
throws Exception
{
this.classloader = cl;
this.locator = locator;
this.subsystem = subsystem == null ? null : subsystem.toUpperCase();
this.configuration = configuration;
}
/**
* Constructs a remoting client with intended target server specified via the locator
* and intended subsystem on server for invocations to be routed to.
*
* @param cl
* @param invoker
* @param subsystem
* @throws Exception
* @deprecated This constructor should not be used any more as will no longer take into
* account the classloader specified as a parameter.
*/
public Client(ClassLoader cl, ClientInvoker invoker, String subsystem)
throws Exception
{
this.classloader = cl;
this.subsystem = subsystem == null ? null : subsystem.toUpperCase();
this.invoker = invoker;
}
/**
* Adds a connection listener that will be notified if/when the connection
* to the server fails while the client is idle (no calls being made).
* The default behavior is to ping for connection every two seconds.
*
* @param listener
*/
public void addConnectionListener(ConnectionListener listener)
{
addConnectionListener(listener, (int) ConnectionValidator.DEFAULT_PING_PERIOD);
}
/**
* Adds a connection listener that will be notified if/when the connection
* to the server fails while the client is idle (no calls being made).
* The current behavior is to ping the server periodically. The time period
* is defined by the pingPeriod (which should be in milliseconds).
*
* @param listener
*/
public void addConnectionListener(ConnectionListener listener, int pingPeriod)
{
if (invoker == null)
{
throw new RuntimeException("Can not add connection listener to remoting client until client has been connected.");
}
else
{
// if local, then no point in having connection listener
if (invoker instanceof LocalClientInvoker)
{
return;
}
}
if (connectionValidator == null)
{
connectionValidator = new ConnectionValidator(this, pingPeriod);
}
connectionValidator.addConnectionListener(listener);
}
/**
* Removes specified connection listener. Will return true if it has
* already been registered, false otherwise.
*
* @param listener
* @return
*/
public boolean removeConnectionListener(ConnectionListener listener)
{
if (connectionValidator == null)
{
return false;
}
return connectionValidator.removeConnectionListener(listener);
}
/**
* This will set the session id used when making invocations on
* server invokers. There is a default unique id automatically
* generated for each Client instance, so unless you have a good reason to set
* this, do not set this.
*
* @param sessionId
*/
public void setSessionId(String sessionId)
{
this.sessionId = sessionId;
}
/**
* Gets the configuration map passed when constructing
* this object.
*
* @return
*/
public Map getConfiguration()
{
return configuration;
}
/**
* Gets the session id used when making invocations on server invokers.
* This is the id that will be used for tracking client connections on
* the server side, to include client failures that are sent to
* connection listeners on the server side.
*
* @return
*/
public String getSessionId()
{
return this.sessionId;
}
/**
* Indicates if the underlying transport has been connected to
* the target server.
*
* @return
*/
public boolean isConnected()
{
return (this.invoker != null && this.invoker.isConnected());
}
/**
* Will cause the underlying transport to make connection to
* the target server. This is important for any stateful transports, like socket or multiplex.
* This is also when a client lease with the server is started.
*
* @throws Exception
*/
public void connect() throws Exception
{
if (invoker == null && locator != null)
{
if (socketFactory != null)
{
configuration.put(Remoting.CUSTOM_SOCKET_FACTORY, socketFactory);
this.socketFactory = null;
}
invoker = InvokerRegistry.createClientInvoker(locator, configuration);
}
if (!isConnected())
{
connect(invoker);
}
}
private void connect(ClientInvoker invoker)
{
if (invoker != null)
{
invoker.connect();
setupClientLease(invoker);
}
else
{
throw new RuntimeException("Client invoker is null (may have used void constructor for Client, which should only be used for Externalization.");
}
}
private void setupClientLease(ClientInvoker invoker)
{
// start with checking the locator url for hint as to if should do initial lease ping
if (invoker != null)
{
if (invoker instanceof LocalClientInvoker)
{
// no need to continue as won't do client lease when is local
// JBREM-382
return;
}
InvokerLocator locator = invoker.getLocator();
Map locatorParams = locator.getParameters();
if (locatorParams != null)
{
String leaseValue = (String) locatorParams.get(InvokerLocator.CLIENT_LEASE);
if (leaseValue != null && leaseValue.length() > 0)
{
enableLease = Boolean.valueOf(leaseValue).booleanValue();
}
String leasePeriodValue = (String) locatorParams.get(InvokerLocator.CLIENT_LEASE_PERIOD);
if (leasePeriodValue != null && leasePeriodValue.length() > 0)
{
try
{
leasePeriod = Long.parseLong(leasePeriodValue);
}
catch (NumberFormatException e)
{
log.warn("Could not convert client lease period value (" + leasePeriodValue + ") to a number.");
}
}
}
}
else
{
throw new RuntimeException("Can not set up client lease as client invoker is null.");
}
if (configuration != null)
{
Object val = configuration.get(ENABLE_LEASE);
if (val != null)
{
if (val instanceof Boolean)
{
enableLease = ((Boolean) val).booleanValue();
}
else if (val instanceof String)
{
enableLease = Boolean.valueOf((String) val).booleanValue();
}
else
{
log.warn("Can not evaluate " + ENABLE_LEASE + " value (" + val + ") as a boolean type.");
}
}
}
if (enableLease)
{
Object ret = null;
try
{
ret = invoker.invoke(new InvocationRequest(sessionId, subsystem, "$PING$", configuration, new HashMap(), null));
if (ret instanceof InvocationResponse)
{
InvocationResponse resp = (InvocationResponse) ret;
Boolean shouldLease = (Boolean) resp.getResult();
if (shouldLease.booleanValue())
{
// if lease period not set via locator param, check value returned by server
if (leasePeriod < 0)
{
Map respMap = resp.getPayload();
if (respMap != null)
{
Long leaseTimeoutValue = (Long) respMap.get("clientLeasePeriod");
leasePeriod = leaseTimeoutValue.longValue();
}
}
if (leasePeriod > 0)
{
if (leasePinger == null)
{
leasePinger = new LeasePinger(this);
leasePinger.startPing(leasePeriod);
}
}
}
}
}
catch (Throwable throwable)
{
log.error("Error setting up client lease.", throwable);
}
}
}
/**
* Disconnects the underlying transport from the target server.
* Also notifies the target server to terminate client lease. Is important
* that this method is called when no longer using the remoting client. Otherwise
* resource will not be cleaned up and if the target server requires a lease, it
* will be maintained in the background.
*/
public void disconnect()
{
if (leasePinger != null)
{
try
{
invoker.invoke(new InvocationRequest(sessionId, subsystem, "$DISCONNECT$", null, null, null));
}
catch (Throwable throwable)
{
log.error("Error sending disconnect to server to end client lease.", throwable);
}
leasePinger.stopPing();
}
if (invoker != null)
{
/**
* Need to remove myself from registry so will not keep
* reference to me since I am of no use now. Will have to create
* a new one.
*/
InvokerRegistry.destroyClientInvoker(invoker.getLocator(), configuration);
invoker = null;
}
}
/**
* Get the client invoker (transport implementation).
*
* @return
*/
public ClientInvoker getInvoker()
{
return invoker;
}
/**
* Set the client invoker (transport implementation)
*
* @param invoker
*/
public void setInvoker(ClientInvoker invoker)
{
this.invoker = invoker;
}
/**
* Gets the subsystem being used when routing
* invocation request on the server side.
*
* @return
*/
public String getSubsystem()
{
return subsystem;
}
/**
* Sets the subsystem being used when routing invocation requests
* on the server side. Specifing a subsystem is only needed when
* server has multiple handlers registered (which will each have their
* own associated subsystem).
*
* @param subsystem
*/
public void setSubsystem(String subsystem)
{
this.subsystem = subsystem;
}
/**
* Invokes the server invoker handler with the payload parameter passed.
* Same as calling invoke(param, null);
*
* @param param
* @return
* @throws Throwable
*/
public Object invoke(Object param) throws Throwable
{
return invoke(param, null);
}
/**
* invoke the method remotely
*
* @param param - payload for the server invoker handler
* @param metadata - any extra metadata that may be needed by the transport (i.e. GET or POST if using
* http invoker) or if need to pass along extra data to the server invoker handler.
* @return
* @throws Throwable
*/
public Object invoke(Object param, Map metadata)
throws Throwable
{
return invoke(param, metadata, null);
}
private Object invoke(Object param, Map metadata, InvokerLocator callbackServerLocator)
throws Throwable
{
if (isConnected())
{
return invoker.invoke(new InvocationRequest(sessionId, subsystem, param, metadata, null, callbackServerLocator));
}
else
{
throw new Exception("Can not make remoting client invocation due to not being connected to server.");
}
}
/**
* Will invoke a oneway call to server without a return object. This should be used when not expecting a
* return value from the server and wish to achieve higher performance, since the client will not wait for
* a return.
* <b>
* This is done one of two ways. The first is to pass true as the clientSide param. This will cause the
* execution of the remote call to be excuted in a new thread on the client side and will return the calling thread
* before making call to server side. Although, this is optimal for performance, will not know about any problems
* contacting server.
* <p/>
* The second, is to pass false as the clientSide param. This will allow the current calling thread to make
* the call to the remote server, at which point, the server side processing of the thread will be executed on
* the remote server in a new executing thread and the client thread will return. This is a little slower, but
* will know that the call made it to the server.
*
* @param param
* @param sendPayload
* @param clientSide
*/
public void invokeOneway(final Object param, final Map sendPayload, boolean clientSide) throws Throwable
{
if (clientSide)
{
ThreadPool threadPool = getOnewayThreadPool();
Runnable onewayRun = new Runnable()
{
public void run()
{
try
{
invoke(param, sendPayload);
}
catch (Throwable e)
{
// throw away exception since can't get it back to original caller
log.error("Error executing client oneway invocation request: " + param, e);
}
}
};
threadPool.run(onewayRun);
}
else
{
OnewayInvocation invocation = new OnewayInvocation(param);
invoke(invocation, sendPayload);
}
}
/**
* Sets the maximum number of threads to use within client pool for
* one way invocations on the client side (meaning oneway invocation
* is handled by thread in this pool and user's call returns immediately)
* Default value is MAX_NUM_ONEWAY_THREADS.
*
* @param numOfThreads
*/
public void setMaxNumberOfThreads(int numOfThreads)
{
this.maxNumberThreads = numOfThreads;
}
/**
* Gets the maximum number of threads to use within client pool for
* one way invocations on the client side (meaning oneway invocation
* is handled by thread in this pool and user's call returns immediately)
* Default value is MAX_NUM_ONEWAY_THREADS.
*
* @return
*/
public int getMaxNumberOfThreads()
{
return this.maxNumberThreads;
}
/**
* Gets the thread pool being used for making
* one way invocations on the client side.
* If one has not be specifically set via configuration
* or call to set it, will always return instance of
* org.jboss.util.threadpool.BasicThreadPool.
*
* @return
*/
public ThreadPool getOnewayThreadPool()
{
if (onewayThreadPool == null)
{
BasicThreadPool pool = new BasicThreadPool("JBossRemoting Client Oneway");
pool.setMaximumPoolSize(maxNumberThreads);
pool.setBlockingMode(BlockingMode.WAIT);
onewayThreadPool = pool;
}
return onewayThreadPool;
}
/**
* Sets the thread pool to be used for making
* one way invocations on the client side.
*
* @param pool
*/
public void setOnewayThreadPool(ThreadPool pool)
{
this.onewayThreadPool = pool;
}
/**
* The socket factory can only be set on the Client before the connect() method
* has been called. Otherwise, a runtime exception will be thrown.
* @param socketFactory
*/
public void setSocketFactory(SocketFactory socketFactory)
{
if(isConnected())
{
throw new RuntimeException("Cannot set socket factory on Client after the connect() method has been called.");
}
if (invoker != null)
{
invoker.setSocketFactory(socketFactory);
}
else
{
this.socketFactory = socketFactory;
}
}
public SocketFactory getSocketFactory()
{
if (invoker != null)
{
return invoker.getSocketFactory();
}
else
{
return socketFactory;
}
}
/**
* Same as calling invokeOneway(Object param, Map sendPayload, boolean clientSide) with
* clientSide param being false and a null sendPayload. Therefore, client thread will not return till it has made
* remote call.
*
* @param param
*/
public void invokeOneway(Object param) throws Throwable
{
invokeOneway(param, null);
}
/**
* Same as calling invokeOneway(Object param, Map sendPayload, boolean clientSide) with
* clientSide param being false. Therefore, client thread will not return till it has made
* remote call.
*
* @param param
* @param sendPayload
*/
public void invokeOneway(Object param, Map sendPayload) throws Throwable
{
invokeOneway(param, sendPayload, false);
}
/**
* Adds the specified handler as a callback listener for push (async) callbacks.
* If the transport is uni-directional (e.g. http), remoting will automatically
* poll for callbacks from the server and deliver them to the callback handler.
* If the transport is bi-directional (e.g. multiplex), remoting will automatically
* create a callback server internally and receive and deliver to callback handler the callbacks as
* they are generated on the server.
* The metadata map passed will control configuration for how the callbacks are processed,
* such as the polling frequency.
*
* @param callbackhandler
* @param metadata
*/
public void addListener(InvokerCallbackHandler callbackhandler, Map metadata) throws Throwable
{
addListener(callbackhandler, metadata, null);
}
/**
* Adds the specified handler as a callback listener for push (async) callbacks.
* If the transport is uni-directional (e.g. http), remoting will automatically
* poll for callbacks from the server and deliver them to the callback handler.
* If the transport is bi-directional (e.g. multiplex), remoting will automatically
* create a callback server internally and receive and deliver to callback handler the callbacks as
* they are generated on the server.
* The metadata map passed will control configuration for how the callbacks are processed,
* such as the polling frequency.
*
* @param callbackhandler
* @param metadata
* @param callbackHandlerObject this object will be included in the Callback object instance passed
* to the InvokerCallbackHandler specified.
*/
public void addListener(InvokerCallbackHandler callbackhandler, Map metadata, Object callbackHandlerObject)
throws Throwable
{
addListener(callbackhandler, metadata, callbackHandlerObject, false);
}
/**
* Adds the specific handler as a callback listener for async callbacks. If the
* transport support bi-directional calls (meaning server can call back to client
* over same connection that was established by the client) or if the serverToClient flag
* is set to true, a callback server will be created internally and the target server
* will actually send callbacks to the client's internal server. Otherwise, the client
* will simulate push callbacks by internally polling for callbacks on the server and then deliver
* them to the callback handler.
*
* @param callbackhandler
* @param metadata
* @param callbackHandlerObject
* @param serverToClient if true, will allow server to connect to the client directly (which must
* be allowed by firewall in front of client unless transport is bi-directional, such as the multiplex transport).
* If false (and not bi-directional transport), server will not create any new connection to the client.
* @throws Throwable
*/
public void addListener(InvokerCallbackHandler callbackhandler, Map metadata,
Object callbackHandlerObject, boolean serverToClient)
throws Throwable
{
InvokerLocator callbackLocator = null;
if (isConnected())
{
if (callbackhandler != null)
{
boolean isBidirectional = invoker instanceof BidirectionalClientInvoker;
if (isBidirectional || serverToClient)
{
//setup callback server
String transport = null;
String host = null;
int port = -1;
// look for config values
if (metadata != null)
{
transport = (String) metadata.get(CALLBACK_SERVER_PROTOCOL);
host = (String) metadata.get(CALLBACK_SERVER_HOST);
String sPort = (String) metadata.get(CALLBACK_SERVER_PORT);
if (sPort != null)
{
try
{
port = Integer.parseInt(sPort);
}
catch (NumberFormatException e)
{
log.warn("Could not set the internal callback server port as configuration value (" + sPort + ") is not a number.");
}
}
}
else
{
metadata = new HashMap();
}
if (transport == null)
{
transport = invoker.getLocator().getProtocol();
metadata.put(CALLBACK_SERVER_PROTOCOL, transport);
}
if (host == null)
{
host = InetAddress.getLocalHost().getHostAddress();
metadata.put(CALLBACK_SERVER_HOST, host);
}
if (port == -1)
{
port = PortUtil.findFreePort(host);
metadata.put(CALLBACK_SERVER_PORT, String.valueOf(port));
}
if(isBidirectional)
{
callbackLocator = ((BidirectionalClientInvoker)invoker).getCallbackLocator(metadata);
}
else
{
callbackLocator = new InvokerLocator(transport, host, port, null, metadata);
}
Connector callbackServerConnector = new Connector(callbackLocator);
callbackConnectors.put(callbackhandler, callbackServerConnector);
callbackServerConnector.start();
// have to use the locator from the server as can be modified internally
callbackLocator = callbackServerConnector.getServerInvoker().getLocator();
}
else
{
//need to setup poller to get callbacks from the server
CallbackPoller poller = new CallbackPoller(this, callbackhandler, metadata, callbackHandlerObject);
callbackPollers.put(callbackhandler, poller);
poller.start();
}
addCallbackListener(callbackhandler, metadata, callbackLocator, callbackHandlerObject);
}
else
{
throw new NullPointerException("InvokerCallbackHandler to be added as a listener can not be null.");
}
}
else
{
throw new Exception("Can not add callback listener because remoting client is not connected to server.");
}
}
private void addCallbackListener(InvokerCallbackHandler callbackhandler, Map metadata,
InvokerLocator callbackLocator, Object callbackHandlerObject)
throws Throwable
{
Map internalMetadata = createListenerMetadata(callbackhandler);
if (metadata != null)
{
internalMetadata.putAll(metadata);
}
String listenerId = (String) internalMetadata.get(LISTENER_ID_KEY);
invoker.addClientLocator(listenerId, callbackLocator);
if (callbackLocator != null)
{
Client client = new Client(callbackLocator, subsystem);
client.setSessionId(getSessionId());
client.connect();
try
{
client.invoke(new InternalInvocation(InternalInvocation.ADDCLIENTLISTENER,
new Object[]{callbackhandler, callbackHandlerObject}),
internalMetadata);
}
finally
{
client.disconnect();
}
}
// now call server to add listener
invoke(new InternalInvocation(InternalInvocation.ADDLISTENER, null), internalMetadata, callbackLocator);
}
/**
* Adds the specified handler as a callback listener for pull (sync) callbacks.
* Using this method will require the programatic getting of callbacks from the server
* (they will not be pushed to the callback handler automatically).
*
* @param callbackHandler
*/
public void addListener(InvokerCallbackHandler callbackHandler) throws Throwable
{
addListener(callbackHandler, (InvokerLocator) null);
}
/**
* Adds the specified handler as a callback listener for push (async) callbacks.
* The invoker server will then callback on this handler (via the server invoker
* specified by the clientLocator) when it gets a callback from the server handler.
* Note: passing a null clientLocator will cause the client invoker's client
* locator to be set to null, which basically converts the mode to be pull (sync) where
* will require call to get callbacks (as will not automatically be pushed to callback handler).
*
* @param callbackHandler
* @param clientLocator
* @throws Throwable
*/
public void addListener(InvokerCallbackHandler callbackHandler,
InvokerLocator clientLocator) throws Throwable
{
addListener(callbackHandler, clientLocator, null);
}
/**
* Adds the specified handler as a callback listener for push (async) callbacks.
* The invoker server will then callback on this handler (via the server invoker
* specified by the clientLocator) when it gets a callback from the server handler.
* Note: passing a null clientLocator will cause the client invoker's client
* locator to be set to null, which basically converts the mode to be pull (sync) where
* will require call to get callbacks (as will not automatically be pushed to callback handler).
*
* @param callbackHandler interface to call on with callback
* @param clientLocator locator for callback server to callback on
* @param callbackHandlerObject will be included in the callback object passed upon callback
* @throws Throwable
*/
public void addListener(InvokerCallbackHandler callbackHandler,
InvokerLocator clientLocator, Object callbackHandlerObject) throws Throwable
{
if (callbackHandler != null)
{
if (isConnected())
{
addCallbackListener(callbackHandler, null, clientLocator, callbackHandlerObject);
}
else
{
throw new Exception("Can not add callback listener as remoting client is not connected to server.");
}
}
else
{
throw new NullPointerException("InvokerCallbackHandler to be added as a listener can not be null.");
}
}
private Map createListenerMetadata(InvokerCallbackHandler callbackHandler)
{
String listenerId = String.valueOf(callbackHandler.hashCode());
Map metadata = new HashMap();
metadata.put(LISTENER_ID_KEY, listenerId);
return metadata;
}
/**
* Removes callback handler as a callback listener from the server (and client in
* the case that it was setup to receive async callbacks). See addListener().
*
* @param callbackHandler
* @throws Throwable
*/
public void removeListener(InvokerCallbackHandler callbackHandler) throws Throwable
{
if (isConnected())
{
if (callbackHandler != null)
{
Map metadata = createListenerMetadata(callbackHandler);
String listenerId = (String) metadata.get(LISTENER_ID_KEY);
// connect to the given client locator and remove handler as listener
InvokerLocator locator = invoker.getClientLocator(listenerId);
if (locator != null) // async callback
{
Client client = new Client(locator, subsystem);
client.setSessionId(getSessionId());
client.connect();
client.invoke(new InternalInvocation(InternalInvocation.REMOVECLIENTLISTENER,
new Object[]{callbackHandler}),
metadata);
client.disconnect();
}
// now call server to remove listener
invoke(new InternalInvocation(InternalInvocation.REMOVELISTENER, null), metadata);
// clean up callback server connector if one exists
Connector callbackConnector = (Connector) callbackConnectors.remove(callbackHandler);
if (callbackConnector != null)
{
callbackConnector.stop();
callbackConnector.destroy();
}
// clean up callback poller if one exists
CallbackPoller callbackPoller = (CallbackPoller) callbackPollers.remove(callbackHandler);
if (callbackPoller != null)
{
callbackPoller.stop();
}
}
else
{
throw new NullPointerException("Can not remove null InvokerCallbackHandler listener.");
}
}
else
{
throw new Exception("Can not remove callback listener as remoting client is not connected to server.");
}
}
/**
* Gets the callbacks for specified callback handler. The handler is required because an id is generated
* for each handler. So if have two callback handlers registered with the same server, no other way to know
* for which handler to get the callbacks for.
*
* @param callbackHandler
* @return
* @throws Throwable
*/
public List getCallbacks(InvokerCallbackHandler callbackHandler) throws Throwable
{
if (callbackHandler != null)
{
Map metadata = createListenerMetadata(callbackHandler);
return (List) invoke(new InternalInvocation(InternalInvocation.GETCALLBACKS, null), metadata);
}
else
{
throw new NullPointerException("Can not remove null InvokerCallbackHandler listener.");
}
}
/**
* Sets the marshaller implementation that should be used by the
* client invoker (transport). This overrides the client's default
* marshaller (or any set within configuration).
*
* @param marshaller
*/
public void setMarshaller(Marshaller marshaller)
{
if (isConnected())
{
if (marshaller != null)
{
invoker.setMarshaller(marshaller);
}
else
{
throw new NullPointerException("Can not set Marshaller with a null value.");
}
}
else
{
throw new RuntimeException("Can not set remoting client Marshaller when not connected.");
}
}
/**
* Sets the unmarshaller implementation that should be used
* by the client invoker (transport). This overrides the client's default
* unmarshaller (or any set within configuration).
*
* @param unmarshaller
*/
public void setUnMarshaller(UnMarshaller unmarshaller)
{
if (isConnected())
{
if (unmarshaller != null)
{
invoker.setUnMarshaller(unmarshaller);
}
else
{
throw new NullPointerException("Can not set UnMarshaller to null value.");
}
}
else
{
throw new RuntimeException("Can not set remoting client UnMarhshaller when not connected.");
}
}
/**
* Takes an inputstream and wraps a server around. Then calls the target
* remoting server and passes a proxy for an inputstream to the server's handler.
* When the server handler calls on this proxy, it will call back on this server
* wrapped around this inputstream.
*
* @param inputStream
* @param param invocation payload
* @return the return value from the invocation
* @throws Throwable
*/
public Object invoke(InputStream inputStream, Object param) throws Throwable
{
StreamServer streamServer = new StreamServer(inputStream);
String locator = streamServer.getInvokerLocator();
// now call on target server and pass locator for stream callbacks
InvocationRequest invocationRequest = new InvocationRequest(sessionId, subsystem, param, null, null, null);
return invoke(new InternalInvocation(InternalInvocation.ADDSTREAMCALLBACK, new Object[]{locator, invocationRequest}), null);
}
/**
* Takes an inputstream and wraps a server around. Then calls the target
* remoting server and passes a proxy for an inputstream to the server's handler.
* When the server handler calls on this proxy, it will call back on this server
* wrapped around this inputstream. The Connector passed is expected to have already been started and
* will have the stream handler added with subsystem of 'stream'. Also note that the Connector passed
* will not be stopped when/if the server calls to close the input stream.
*
* @param inputStream
* @param param invocation payload
* @return the return value from the invocation
* @throws Throwable
*/
public Object invoke(InputStream inputStream, Object param, Connector streamConnector) throws Throwable
{
StreamServer streamServer = new StreamServer(inputStream, streamConnector);
String locator = streamServer.getInvokerLocator();
// now call on target server and pass locator for stream callbacks
InvocationRequest invocationRequest = new InvocationRequest(sessionId, subsystem, param, null, null, null);
return invoke(new InternalInvocation(InternalInvocation.ADDSTREAMCALLBACK, new Object[]{locator, invocationRequest}), null);
}
/**
* Takes an inputstream and wraps a server around. Then calls the target remoting server and passes proxy for
* an inputstream to the server's handler. When the server handle calls on this proxy, it will call back on this server
* wrapped around this inputstream. The InvokerLocator passed is used to create the internal Connector used to receive the
* calls from the server side.
* @param inputStream
* @param param
* @param streamServerLocator
* @return
* @throws Throwable
*/
public Object invoke(InputStream inputStream, Object param, InvokerLocator streamServerLocator) throws Throwable
{
StreamServer streamServer = new StreamServer(inputStream, streamServerLocator);
String locator = streamServer.getInvokerLocator();
// now call on target server and pass locator for stream callbacks
InvocationRequest invocationRequest = new InvocationRequest(sessionId, subsystem, param, null, null, null);
return invoke(new InternalInvocation(InternalInvocation.ADDSTREAMCALLBACK, new Object[]{locator, invocationRequest}), null);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
{
int version = in.readInt();
switch (version)
{
case Version.VERSION_2:
{
InvokerLocator readLocator = (InvokerLocator) in.readObject();
this.subsystem = (String) in.readObject();
this.configuration = (Map) in.readObject();
boolean wasConnected = in.readBoolean();
this.classloader = Thread.currentThread().getContextClassLoader();
try
{
this.invoker = InvokerRegistry.createClientInvoker(readLocator, configuration);
if(wasConnected)
{
connect();
}
}
catch (Exception e)
{
log.error(e);
throw new IOException(e.getMessage());
}
break;
}
default:
throw new StreamCorruptedException("Unkown version seen: " + version);
}
}
public void writeExternal(ObjectOutput out) throws IOException
{
out.writeInt(Version.getDefaultVersion());
out.writeObject(invoker != null ? invoker.getLocator() : locator);
out.writeObject(subsystem);
out.writeObject(configuration);
out.writeBoolean(isConnected());
out.flush();
}
/**
* PLEASE DO NOT USE THIS CONSTRUCTOR OR YOUR COMPUTER WILL BURST INTO FLAMES!!!
* It is only here so can externalize object and will provide
* a dead object if invoker is not explicitly set. Please use
* other contructors provided.
*/
public Client()
{
}
}