Package org.xmlBlaster.client.protocol.socket

Source Code of org.xmlBlaster.client.protocol.socket.SocketCallbackImpl

/*------------------------------------------------------------------------------
Name:      SocketCallbackImpl.java
Project:   xmlBlaster.org
Copyright: xmlBlaster.org, see xmlBlaster-LICENSE file
------------------------------------------------------------------------------*/
package org.xmlBlaster.client.protocol.socket;


import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.xmlBlaster.client.protocol.I_CallbackExtended;
import org.xmlBlaster.client.protocol.I_CallbackServer;
import org.xmlBlaster.engine.qos.AddressServer;
import org.xmlBlaster.engine.qos.ConnectQosServer;
import org.xmlBlaster.engine.qos.ConnectReturnQosServer;
import org.xmlBlaster.protocol.I_Authenticate;
import org.xmlBlaster.protocol.I_XmlBlaster;
import org.xmlBlaster.protocol.socket.CallbackSocketDriver;
import org.xmlBlaster.util.Global;
import org.xmlBlaster.util.XmlBlasterException;
import org.xmlBlaster.util.def.Constants;
import org.xmlBlaster.util.def.ErrorCode;
import org.xmlBlaster.util.def.MethodName;
import org.xmlBlaster.util.dispatch.ConnectionStateEnum;
import org.xmlBlaster.util.plugin.PluginInfo;
import org.xmlBlaster.util.protocol.socket.SocketExecutor;
import org.xmlBlaster.util.protocol.socket.SocketUrl;
import org.xmlBlaster.util.qos.address.CallbackAddress;
import org.xmlBlaster.util.xbformat.MsgInfo;


/**
* Used for client to receive xmlBlaster callbacks over plain sockets.
* <p />
* One instance of this for each client, as a separate thread blocking
* on the socket input stream waiting for messages from xmlBlaster.
* @author <a href="mailto:xmlBlaster@marcelruff.info">Marcel Ruff</a>.
* @see org.xmlBlaster.util.xbformat.MsgInfo
* @see <a href="http://www.xmlBlaster.org/xmlBlaster/doc/requirements/protocol.socket.html">The protocol.socket requirement</a>
*/
public class SocketCallbackImpl extends SocketExecutor implements Runnable, I_CallbackServer
{
   private String ME = "SocketCallbackImpl";
   private Global glob;
   private static Logger log = Logger.getLogger(SocketCallbackImpl.class.getName());
   /** The connection manager 'singleton' */
   private SocketConnection sockCon;
   /** A unique name for this client socket */
   private SocketUrl socketUrl;
   private CallbackAddress callbackAddress;
   private PluginInfo pluginInfo;
   /** The socket connection to/from one client */
   protected Socket sock;

   /** Stop the thread */
   private boolean threadRunning = false;

   /** For cluster environment only */
   private boolean useRemoteLoginAsTunnel;
   private SocketExecutor remoteLoginAsTunnelSocketExecutor;
   private boolean acceptRemoteLoginAsTunnel;
   private String secretSessionId; // only for acceptRemoteLoginAsTunnel
  
   private I_Authenticate authenticateCore;
  
   private Thread callbackListenerThread;

   /**
    * Called by plugin loader which calls init(Global, PluginInfo) thereafter.
    * A thread receiving all messages from xmlBlaster, and delivering them back to the client code.
    * <p>
    * After polling -> alive NO new instance is created, but only initialize() is called
    */
   public SocketCallbackImpl() {
      if (log.isLoggable(Level.FINEST)) log.finest("ctor");
   }

   /** Enforced by I_Plugin */
   public String getType() {
      return getCbProtocol();
   }

   /** Enforced by I_Plugin */
   public String getVersion() {
      return (this.pluginInfo == null) ? "1.0" : this.pluginInfo.getVersion();
   }

   /**
    * This method is called by the PluginManager (enforced by I_Plugin).
    * @see org.xmlBlaster.util.plugin.I_Plugin#init(org.xmlBlaster.util.Global,org.xmlBlaster.util.plugin.PluginInfo)
    */
   public void init(org.xmlBlaster.util.Global glob, PluginInfo pluginInfo) {
      this.pluginInfo = pluginInfo;
   }
  
   public void setRunning(boolean run) {
      super.setRunning(run);
      this.threadRunning = run;
   }
  
   public SocketExecutor getSocketExecutor() {
      return (this.useRemoteLoginAsTunnel && this.remoteLoginAsTunnelSocketExecutor != null) ? this.remoteLoginAsTunnelSocketExecutor : this;
   }

   /**
    * Initialize and start the callback server
    * A thread receiving all messages from xmlBlaster, and delivering them back to the client code.
    * <p>
    * Same SocketCallback instance is called several times by SocketConnection.connectLowLevel() (on polling->alive)
    */
   public synchronized final void initialize(Global glob, String loginName,
                            CallbackAddress callbackAddress, I_CallbackExtended cbClient) throws XmlBlasterException {
      this.glob = (glob == null) ? Global.instance() : glob;

      this.ME = "SocketCallbackImpl-" + loginName;
      this.callbackAddress = callbackAddress;
      if (this.pluginInfo != null)
         this.callbackAddress.setPluginInfoParameters(this.pluginInfo.getParameters());
      setLoginName(loginName);
     
      // The cluster slave accepts publish(), subscribe() etc callbacks
      this.acceptRemoteLoginAsTunnel = this.callbackAddress.getEnv("acceptRemoteLoginAsTunnel", false).getValue();
     
      ///// TODO Don't start thread!!!
      // If we are a client XmlBlasterAccess reusing a remote login socket
      this.useRemoteLoginAsTunnel = this.callbackAddress.getEnv("useRemoteLoginAsTunnel", false).getValue();
      String entryKey = SocketExecutor.getGlobalKey(this.callbackAddress.getSessionName());
      Object obj = glob.getObjectEntry(entryKey); //getAddressServer()
      if (obj != null) {
         log.info(loginName + " Getting " + getType() + " obj=" + obj +
                  " entryKey=" + entryKey + " global.instanceId=" +glob.getInstanceId() + "-" + glob.hashCode());
         if (obj instanceof org.xmlBlaster.util.protocol.socket.SocketExecutor) {
            this.remoteLoginAsTunnelSocketExecutor = (SocketExecutor)obj;
         }
         this.useRemoteLoginAsTunnel = true;
         //if (obj instanceof org.xmlBlaster.protocol.socket.HandleClient) {
         //   org.xmlBlaster.protocol.socket.HandleClient h = (org.xmlBlaster.protocol.socket.HandleClient)obj;
         //}
      }

      if (cbClient != null)
         getSocketExecutor().setCbClient(cbClient); // access callback client in super class SocketExecutor:callback

      // If we are server side and a client which receives publish(),subscribe()... from remote cluster node
      obj = glob.getObjectEntry("ClusterManager[cluster]/I_Authenticate");
      if (obj != null) {
         setAuthenticateCore((I_Authenticate)obj);
         log.info("Setting I_Authenticate for useRemoteLoginAsTunnel");
      }
      obj = glob.getObjectEntry("ClusterManager[cluster]/I_XmlBlaster");
      if (obj != null) {
         super.setXmlBlasterCore((I_XmlBlaster)obj);
         log.info("Setting I_XmlBlaster for useRemoteLoginAsTunnel");
      }
     
      // Lookup SocketConnection instance in the NameService
      this.sockCon = (SocketConnection)glob.getObjectEntry("org.xmlBlaster.client.protocol.socket.SocketConnection");

      if (this.sockCon == null) {
         // SocketConnection.java must be instantiated first and registered to reuse the socket for callbacks
         throw new XmlBlasterException(glob, ErrorCode.INTERNAL_NOTIMPLEMENTED, ME,
               "Sorry, creation of SOCKET callback handler is not possible if client connection is not of type 'SOCKET'");
      }

      this.sockCon.registerCbReceiver(this);

      if (this.threadRunning == false) {
         try {
            this.sock = this.sockCon.getSocket();
         }
         catch (XmlBlasterException e) {
            log.fine("There is no client socket connection which i could use: " + e.getMessage());
            return ;
         }

         if (useRemoteLoginAsTunnel) {
            log.info("We use the remote socket connection to tunnel our communication");
            if (this.remoteLoginAsTunnelSocketExecutor != null)
               this.remoteLoginAsTunnelSocketExecutor.setRunning(true); // Fake that we are OK
            return;
         }

         try { // SocketExecutor
            super.initialize(this.sockCon.getGlobal(), this.callbackAddress, this.sock.getInputStream(), this.sock.getOutputStream());
         }
         catch (IOException e) {
            throw new XmlBlasterException(glob, ErrorCode.COMMUNICATION_NOCONNECTION, ME, "Creation of SOCKET callback handler failed", e);
         }

         try {
            // You should not activate SoTimeout, as this timeouts if InputStream.read() blocks too long.
            // But we always block on input read() to receive update() messages.
            setSoTimeout(this.callbackAddress.getEnv("SoTimeout", 0L).getValue()); // switch off
            this.sock.setSoTimeout((int)this.soTimeout);
            if (log.isLoggable(Level.FINE)) log.fine(this.callbackAddress.getEnvLookupKey("SoTimeout") + "=" + this.soTimeout);

            setSoLingerTimeout(this.callbackAddress.getEnv("SoLingerTimeout", soLingerTimeout).getValue());
            if (log.isLoggable(Level.FINE)) log.fine(this.callbackAddress.getEnvLookupKey("SoLingerTimeout") + "=" + getSoLingerTimeout());
            if (getSoLingerTimeout() >= 0L) {
               // >0: Try to send any unsent data on close(socket) (The UNIX kernel waits very long and ignores the given time)
               // =0: Discard remaining data on close()  <-- CHOOSE THIS TO AVOID BLOCKING close() calls
               this.sock.setSoLinger(true, (int)this.soLingerTimeout);
            }
            else
               this.sock.setSoLinger(false, 0); // false: default handling, kernel tries to send queued data after close() (the 0 is ignored)
         }
         catch (SocketException e) {
            log.severe("Failed to set socket attributes, we ignore it and continue: " + e.toString());
         }

         this.socketUrl = this.sockCon.getLocalSocketUrl();
         this.callbackAddress.setRawAddress(this.socketUrl.getUrl());
         if (log.isLoggable(Level.FINE)) log.fine("Callback uri=" + this.socketUrl.getUrl());

         callbackListenerThread = new Thread(this, "XmlBlaster."+getType());
         callbackListenerThread.setDaemon(true);
         int threadPrio = this.callbackAddress.getEnv("threadPrio", Thread.NORM_PRIORITY).getValue();
         try {
            callbackListenerThread.setPriority(threadPrio);
            if (log.isLoggable(Level.FINE)) log.fine("-dispatch/callback/plugin/socket/threadPrio = " + threadPrio);
         }
         catch (IllegalArgumentException e) {
            log.warning("Your -dispatch/callback/plugin/socket/threadPrio " + threadPrio + " is out of range, we continue with default setting " + Thread.NORM_PRIORITY);
         }
         this.threadRunning = true;
         callbackListenerThread.start();
      }
   }

   /*
    * TODO: Is this needed anymore?
    * @return
    */
   protected boolean hasConnection() {
      return (this.sock != null);
   }

   /**
    * Returns the protocol type.
    * @return The configured [type] in xmlBlaster.properties, defaults to "SOCKET"
    */
   public final String getCbProtocol()
   {
      return (this.pluginInfo == null) ? "SOCKET" : this.pluginInfo.getType();
   }

   /**
    * Returns the callback address.
    * <p />
    * This is no listen socket, as we need no callback server.
    * It is just the client side socket data of the established connection to xmlBlaster.
    * @return "socket://192.168.2.1:34520"
    */
   public String getCbAddress() throws XmlBlasterException
   {
      if ( socketUrl == null ) {
         return "";
      }
      return socketUrl.getUrl();
   }

   /**
    * Starts the callback thread
    */
   public void run()
   {
      log.info("Started callback receiver plugin on '" + this.socketUrl.getUrl() + "'");
      boolean multiThreaded = this.callbackAddress.getEnv("multiThreaded", true).getValue();
      if (log.isLoggable(Level.FINE)) log.fine("SOCKET multiThreaded=" + multiThreaded);

      while(threadRunning && this.callbackListenerThread == Thread.currentThread()) {

         try {
            // This method blocks until a message arrives
            MsgInfo[] msgInfoArr = MsgInfo.parse(glob, progressListener, iStream, getMsgInfoParserClassName(), this.pluginInfo);
            if (msgInfoArr.length < 1) {
               log.warning(toString() + ": Got unexpected empty data from SOCKET, closing connection now");
               break;
            }
            final MsgInfo receiver = msgInfoArr[0];
           
            //if (MethodName.CONNECT.equals(receiver.getMethodName()))
            //   log.info("Test: Got connectQos");

            if (log.isLoggable(Level.FINEST)) log.finest("Receiving message >" + receiver.toLiteral() + "<\n" + receiver.dump());

            if (this.acceptRemoteLoginAsTunnel
                  && receiver.isInvoke()
                  && !MethodName.UPDATE.equals(receiver.getMethodName())
                  && !MethodName.UPDATE_ONEWAY.equals(receiver.getMethodName())
                  && !MethodName.EXCEPTION.equals(receiver.getMethodName())
                  && !MethodName.PING.equals(receiver.getMethodName())) {
              
               //getSocketExecutor().getXmlBlasterCore().
               if (MethodName.CONNECT == receiver.getMethodName()) {
                  // TODO: crypt.importMessage(receiver.getQos()); see also ClientDispatchConnection.java:440
                  Socket socket = this.sock;
                  if (socket == null) return; // Is possible when EOF arrived inbetween
                  ConnectQosServer conQos = new ConnectQosServer(glob, receiver.getQos());
                  if (conQos.getSecurityQos() == null)
                     throw new XmlBlasterException(glob, ErrorCode.USER_SECURITY_AUTHENTICATION_ILLEGALARGUMENT, ME, "connect() without securityQos");
                  conQos.getSecurityQos().setClientIp (socket.getInetAddress().getHostAddress());
                  conQos.setAddressServer(getAddressServer());
                  Object callbackSocketDriver = new CallbackSocketDriver(conQos.getSessionName().getLoginName(), this);
                  conQos.getAddressServer().setCallbackDriver(callbackSocketDriver);
                  conQos.getData().getCurrentCallbackAddress().setCallbackDriver(callbackSocketDriver);
                  ConnectReturnQosServer retQos = getAuthenticateCore().connect(conQos);
                  this.secretSessionId = retQos.getSecretSessionId();
                  receiver.setSecretSessionId(retQos.getSecretSessionId()); // executeResponse needs it
                  executeResponse(receiver, retQos.toXml(), SocketUrl.SOCKET_TCP);
               }
               else if (MethodName.DISCONNECT == receiver.getMethodName()) {
                  executeResponse(receiver, Constants.RET_OK, SocketUrl.SOCKET_TCP);   // ACK the disconnect to the client and then proceed to the server core
                  // Note: the disconnect will call over the CbInfo our shutdown as well
                  // setting sessionId = null prevents that our shutdown calls disconnect() again.
                  getAuthenticateCore().disconnect(getAddressServer(), receiver.getSecretSessionId(), receiver.getQos());
                  shutdown();
               }
               else {
                  if (log.isLoggable(Level.FINE)) log.fine("Received tunneled message, forwarding now to xmlBlaster core: " + receiver.getMethodNameStr());
                  boolean processed = receiveReply(receiver, SocketUrl.SOCKET_TCP);    // Parse the message and invoke actions in same thread
                  if (!processed)
                     log.warning("Received message is not processed: " + receiver.toLiteral());
               }
            }
            else if (this.acceptRemoteLoginAsTunnel
                  && receiver.isResponse()
                  && MethodName.UPDATE.equals(receiver.getMethodName())
                  && MethodName.UPDATE_ONEWAY.equals(receiver.getMethodName()) // ONEWAY have no response, just do be complete
            ) {
               log.severe("UPDATE RESPONSE IS NOT YET IMPLEMENTED");              
               boolean processed = receiveReply(receiver, SocketUrl.SOCKET_TCP);    // Parse the message and invoke actions in same thread
               if (!processed)
                  log.warning("Received message is not processed: " + receiver.toLiteral());
            }
            else {
               // Normal client operation
               if (receiver.isInvoke() && multiThreaded) {
                  // Parse the message and invoke callback to client code in a separate thread
                  // to avoid dead lock when client does a e.g. publish() during this update()
                  WorkerThread t = new WorkerThread(glob, this, receiver);
                  // -dispatch/callback/plugin/socket/invokerThreadPrio 5
                  t.setPriority(this.callbackAddress.getEnv("invokerThreadPrio", Thread.NORM_PRIORITY).getValue());
                  t.start();
               }
               else {
                  boolean processed = receiveReply(receiver, SocketUrl.SOCKET_TCP);    // Parse the message and invoke actions in same thread
                  if (!processed)
                     log.warning("Received message is not processed: " + receiver.toLiteral());
               }
               if (MethodName.DISCONNECT == receiver.getMethodName() && receiver.isResponse()) {
                  if (log.isLoggable(Level.FINE)) log.fine("Terminating socket callback thread because of disconnect response");
                  threadRunning = false;
               }
            }
         }
         catch(XmlBlasterException e) {
            log.warning(e.toString());
         }
         catch(Throwable e) {
            if (e instanceof NullPointerException)
               e.printStackTrace();
            if (threadRunning == true) {
               if (e.toString().indexOf("javax.net.ssl") != -1) {
                  log.warning("Closing connection to server, please try debugging SSL with 'java -Djavax.net.debug=all ...': " + e.toString());
               }
               else if (e instanceof IOException) {
                  log.warning("Closing connection to server: " + e.toString());
               }
               else {
                  log.severe("Closing connection to server: " + e.toString());
               }
               try {
                  // calls our shutdownSocket() which does this.threadRunning = false
                  sockCon.shutdown();
               }
               catch (XmlBlasterException ex) {
                  log.severe("run() could not shutdown correctly. " + ex.getMessage());
               }
               // Exceptions ends nowhere but terminates the thread
              
               // Notify client library  XmlBlasterAccess.java to go to polling
               try {
                  I_CallbackExtended cb = this.cbClient;
                  if (cb != null) {
                     cb.lostConnection(XmlBlasterException.convert(this.glob, ME, "Lost socket connection", e));
                  }
               }
               catch(Throwable xx) {
                  xx.printStackTrace();
               }
              
               if (this.acceptRemoteLoginAsTunnel) {
                  try {
                     AddressServer addr = this.addressServer;
                     if (addr != null) {
                        CallbackSocketDriver cbd = (CallbackSocketDriver)addr.getCallbackDriver();
                        if (cbd != null) {
                           cbd.shutdown(); // notify about lost connection
                        }
                     }
                  }
                  catch(Throwable xx) {
                     xx.printStackTrace();
                  }
               }
              
               if (e instanceof IOException || e instanceof java.net.SocketException) {
                  //2008-06-24 07:22:34.544 WARNING 14-XmlBlaster.SOCKET org.xmlBlaster.client.protocol.socket.SocketCallbackImpl run: Closing connection to server: java.net.SocketException: Socket closed
                 shutdownSocket(); // stop thread
               }
            }
         }
      }
      if (log.isLoggable(Level.FINE)) log.fine("Terminating socket callback thread");
   }

   final SocketConnection getSocketConnection() {
      return sockCon;
   }

   /**
    * Shutdown callback only.
    */
   public synchronized void shutdown() {
      super.shutdown();
      //setCbClient(null); NO, not sure if this is a good idea // reset callback client in super class SocketExecutor:callback
   }

   /**
    * Shutdown SOCKET connection and callback
    * Called by SocketConnection.shutdown() on problems
    */
   public synchronized void shutdownSocket() {
      if (log.isLoggable(Level.FINE)) log.fine("Entering shutdownSocket()");
      boolean needsCleanup = this.threadRunning; // good enough for all cases?
      this.threadRunning = false;
      if (!this.useRemoteLoginAsTunnel) { // Do we own the socket?
         if (this.iStream != null) {
            try {
               this.iStream.close();
               //this.iStream = null; multi threading -> NPE danger
            } catch(IOException e) {
               log.warning(e.toString());
            }
         }
      }
      clearResponseListenerMap();
      freePendingThreads();
      if (this.acceptRemoteLoginAsTunnel) {
         if (needsCleanup) {
            I_Authenticate a = getAuthenticateCore();
            String s = this.secretSessionId;
            if (a != null && s != null)
               a.connectionState(s, ConnectionStateEnum.DEAD);
         }
      }
   }

   public I_Authenticate getAuthenticateCore() {
      return authenticateCore;
   }

   public void setAuthenticateCore(I_Authenticate authenticateImpl) {
      this.authenticateCore = authenticateImpl;
   }
} // class SocketCallbackImpl
TOP

Related Classes of org.xmlBlaster.client.protocol.socket.SocketCallbackImpl

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.