Package org.jboss.remoting

Source Code of org.jboss.remoting.Client

/*
* 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()
   {
   }
}
TOP

Related Classes of org.jboss.remoting.Client

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.