Package org.quickserver.net.server.impl

Source Code of org.quickserver.net.server.impl.BasicClientHandler$InstanceId

/*
* This file is part of the QuickServer library
* Copyright (C) QuickServer.org
*
* Use, modification, copying and distribution of this software is subject to
* the terms and conditions of the GNU Lesser General Public License.
* You should have received a copy of the GNU LGP License along with this
* library; if not, you can download a copy from <http://www.quickserver.org/>.
*
* For questions, suggestions, bug-reports, enhancement-requests etc.
* visit http://www.quickserver.org
*
*/

package org.quickserver.net.server.impl;

import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.InetAddress;
import java.util.*;
import java.util.logging.*;
import javax.net.ssl.*;
import java.security.*;
import java.nio.*;
import java.nio.channels.*;

import org.quickserver.net.*;
import org.quickserver.util.*;
import org.quickserver.net.server.*;
import javax.net.ssl.*;

/**
* Basic implementation of ClientHandler that handles clients for QuickServer.
* <p> This class is used by {@link QuickServer} to handle each new client
* connected. This class is responsible to handle client sockets. It can operate
* in both blocking mode and non-blocking mode (java nio).</p>
* <p>
* Contributions By:
*   Martin Benns : BYTE Mode
* </p>
* @author Akshathkumar Shetty
* @author Martin Benns : Added BYTE mode
*/
public abstract class BasicClientHandler implements ClientHandler {
  private static final Logger logger = Logger.getLogger(BasicClientHandler.class.getName());

  protected static final String NEW_LINE = QuickServer.getNewLine();
  protected static final byte NEW_LINE_BYTES[] = NEW_LINE.getBytes();

  //Some variable are not initialised to any value because the
  //default java value was desired initial value.
 
  /** Client socket */
  protected Socket socket;
  /** Client authorisation status */
  protected volatile boolean authorised;
  /** Count of client login attempts */
  protected int counAuthTry;
  /** max allowed login attempts */
  protected int maxAuthTry = 5;
  /** timeout message */
  protected String timeoutMsg;
  /** Message to be displayed when max login attempt reaches.*/
  protected String maxAuthTryMsg; 

  protected int socketTimeout;
  protected volatile boolean connection; //false
  protected boolean lost; //false
 
  protected QuickServer quickServer;
  protected Authenticator authenticator; //v1.3
  protected ClientAuthenticationHandler clientAuthenticationHandler; //v1.4.6
  protected ClientEventHandler clientEventHandler; //v1.4.6
  protected ClientExtendedEventHandler clientExtendedEventHandler; //v1.4.6
  protected ClientCommandHandler clientCommandHandler;
  protected ClientObjectHandler clientObjectHandler; //v1.2
  protected ClientBinaryHandler clientBinaryHandler; //1.4
  protected ClientData clientData;

  protected InputStream in;
  protected OutputStream out;
  protected BufferedReader bufferedReader;
  //if DataMode.OBJECT
  protected ObjectOutputStream o_out; //v1.2
  protected ObjectInputStream o_in; //v1.2
  //added for BYTE mode and BINARY mode
  protected BufferedInputStream b_in;
  protected BufferedOutputStream b_out;

  //logger for the application using this QuickServer
  protected Logger appLogger;
  protected DataMode dataModeIN = null;
  protected DataMode dataModeOUT = null;

  protected boolean communicationLogging = true;
  protected Date clientConnectedTime = null
  protected Date lastCommunicationTime = null;
  protected boolean secure = false;

  //--v1.4.5
  protected static final ThreadLocal threadEvent = new ThreadLocal();

  protected String maxConnectionMsg;
  protected final Set clientEvents = new HashSet()
  protected List unprocessedClientEvents = Collections.synchronizedList(new ArrayList());
 
  protected volatile boolean closeOrLostNotified;
  protected Object lockObj = new Object();
  protected volatile boolean willClean;
  protected String charset;

  private static Map idMap = new HashMap();
  private int instanceCount;
  private int id;
  private String name;
  private String hostAddress;
  private int port;

  protected SSLEngine sslEngine;

  static class InstanceId {
    private int id = 0;
    public int getNextId() {
      return ++id;
    }
  };

  private static int getNewId(int instanceCount) {
    InstanceId instanceId = (InstanceId) idMap.get(""+instanceCount);
    if(instanceId==null) {
      instanceId = new InstanceId();
      idMap.put(""+instanceCount, instanceId);
    }
    return instanceId.getNextId();
  }

  public BasicClientHandler(int instanceCount) {
    this.instanceCount = instanceCount;
    id = getNewId(instanceCount);

    StringBuilder sb = new StringBuilder();
    sb.append("<ClientHandler-Pool#");
    sb.append(instanceCount);
    sb.append("-ID:");
    sb.append(id);
    sb.append(">");
    name = sb.toString();
  }

  public int getInstanceCount() {
    return instanceCount;
  }


  public BasicClientHandler() {
    this(-1);
 
 
  public void clean() {
    counAuthTry = 0;
    authorised = false;
    in = null;
    out = null;
    bufferedReader = null;
    o_out = null; o_in = null;
    b_in = null; b_out = null;

    dataModeIN = null;
    dataModeOUT = null;

    lost = false;
    clientData = null;
    clientConnectedTime = null;
    lastCommunicationTime = null;
    communicationLogging = true;
    socketTimeout = 0;
    secure = false;
   
    authenticator = null;
    clientAuthenticationHandler = null;//1.4.6
    clientCommandHandler = null;
    clientObjectHandler = null;
    clientBinaryHandler = null;//1.4   
    clientData = null;

    maxConnectionMsg = null;
    synchronized(clientEvents) {
      clientEvents.clear();
      unprocessedClientEvents.clear();
    }   

    closeOrLostNotified = false;

    if(socket!=null) {
      try {
        socket.close();
      } catch(Exception er) {
        appLogger.warning("Error in closing socket: "+er);
      }
      socket = null;
    }

    hostAddress = null;
    port = 0;

    quickServer = null;
    willClean  = false;
    charset = null;

    sslEngine = null;
  }

  protected void finalize() throws Throwable {
    super.finalize();
  }

  /**
   * Associates the ClientHanlder with the client encapsulated by
   * <code>theClient</code>.
   * @param theClient object that encapsulates client socket
   *  and its configuration details.
   */
  public void handleClient(TheClient theClient) throws Exception {
    setServer(theClient.getServer());

    if(getServer().isRunningSecure()==true) {
      setSecure(true);
      sslEngine = getServer().getSSLContext().createSSLEngine();
    }
    setSocket(theClient.getSocket());

    if(theClient.getTrusted()==false) {
      setAuthenticator(theClient.getAuthenticator());
      setClientAuthenticationHandler(theClient.getClientAuthenticationHandler());
    }
    setClientEventHandler(theClient.getClientEventHandler());
    setClientExtendedEventHandler(theClient.getClientExtendedEventHandler());
    setClientCommandHandler(theClient.getClientCommandHandler());
    setClientObjectHandler(theClient.getClientObjectHandler());
    setClientBinaryHandler(theClient.getClientBinaryHandler()); //v1.4
   
    setClientData(theClient.getClientData());
    if(theClient.getTrusted()==false) {
      socketTimeout = theClient.getTimeout();
    }
    timeoutMsg = theClient.getTimeoutMsg();
    maxAuthTryMsg = theClient.getMaxAuthTryMsg();
    maxAuthTry = theClient.getMaxAuthTry(); //v1.2
    appLogger = quickServer.getAppLogger(); //v1.2
   
    setCommunicationLogging(theClient.getCommunicationLogging()); //v1.3.2

    maxConnectionMsg = theClient.getMaxConnectionMsg();//1.4.5
    addEvent(theClient.getClientEvent());//1.4.5
  }

  /**
     * Returns the QuickServer object that created it.
     * @see #setServer
     */
  public QuickServer getServer() {
    return quickServer;
  }
  /**
     * Sets the QuickServer object associated with this ClientHandler.
     * @see #getServer
     */
  protected void setServer(QuickServer server) {
    Assertion.affirm(server!=null, "QuickServer can't be null!");
    quickServer = server;
  }
 
  /**
     * Sets the ClientData object associated with this ClientHandler
   * @see ClientData
     * @see #getClientData
     */
  protected void setClientData(ClientData data) {
    this.clientData = data;
  }
  /**
     * Returns the ClientData object associated with this ClientHandler,
   * if not set will return <code>null</code>
   * @see ClientData
     * @see #setClientData
     */
  public ClientData getClientData() {
    return clientData;
  }

  /**
     * Sets the ClientAuthenticationHandler class that handles the
   * authentication of a client.
   * @param clientAuthenticationHandler fully qualified name of the class that
   * implements {@link ClientAuthenticationHandler}.
   * @since 1.4.6
     */
  protected void setClientAuthenticationHandler(ClientAuthenticationHandler clientAuthenticationHandler) {
    this.clientAuthenticationHandler = clientAuthenticationHandler;
  }

  /**
     * Sets the Authenticator class that handles the
   * authentication of a client.
   * @param authenticator fully qualified name of the class that
   * implements {@link Authenticator}.
   * @since 1.3
     */
  protected void setAuthenticator(Authenticator authenticator) {
    this.authenticator = authenticator;
  }

  /**
   * Returns the {@link java.io.InputStream} associated with
   * the Client being handled.
   * @see #setInputStream
   */
  public InputStream getInputStream() {
    return in;
  }
  /**
   * Sets the {@link java.io.InputStream} associated with
   * the Client being handled.
   * @since 1.1
   * @see #getInputStream
   */
  protected abstract void setInputStream(InputStream in) throws IOException;

  /**
   * Returns the {@link java.io.OutputStream} associated with
   * the Client being handled.
   * @see #setOutputStream
   */
  public OutputStream getOutputStream() {
    return out;
  }
  /**
   * Set the {@link java.io.OutputStream} associated with
   * the Client being handled.
   * @since 1.1
   * @see #getOutputStream
   * @exception IOException if ObjectOutputStream could not be created.
   */
  public void setOutputStream(OutputStream out) throws IOException {
    this.out = out;
    if(getDataMode(DataType.OUT) == DataMode.STRING ||
        getDataMode(DataType.OUT) == DataMode.BYTE ||
        getDataMode(DataType.OUT) == DataMode.BINARY) {
      o_out = null;
      b_out = new BufferedOutputStream(out);
    } else if(getDataMode(DataType.OUT) == DataMode.OBJECT) {
      b_out = null;
      o_out = new ObjectOutputStream(out);
      o_out.flush();
    } else {
      throw new IllegalStateException("Unknown DataMode " +getDataMode(DataType.OUT));
    }
  }
 
  /**
   * Returns the {@link java.io.BufferedReader} associated with
   * the Client being handled. Note that this is only available under blocking mode.
   * @see #getBufferedWriter
   */
  public abstract BufferedReader getBufferedReader();

  /**
   * Returns the {@link java.io.BufferedWriter} associated with
   * the Client being handled.
   * @deprecated since 1.4.5 use getOutputStream()
   */
  public BufferedWriter getBufferedWriter() {
    try {
      return new BufferedWriter(new OutputStreamWriter(b_out, charset));
    } catch(UnsupportedEncodingException e) {
      logger.warning(charset + " was not supported : "+e);
      return new BufferedWriter(new OutputStreamWriter(b_out));
    }   
  }

  /**
   * Returns the {@link java.io.ObjectOutputStream} associated with
   * the Client being handled.
   * It will be <code>null</code> if no {@link ClientObjectHandler}
   * was set in {@link QuickServer}.
   * @see #getObjectInputStream
   * @since 1.2
   */
  public ObjectOutputStream getObjectOutputStream() {
    return o_out;
  }
  /**
   * Returns the {@link java.io.ObjectInputStream} associated with
   * the Client being handled.
   * It will be <code>null</code> if no {@link ClientObjectHandler}
   * was set in {@link QuickServer}.
   * @see #getObjectOutputStream
   * @since 1.2
   */
  public ObjectInputStream getObjectInputStream() {
    return o_in;
  }

  /**
     * Sets the ClientEventHandler class that gets notified of client events.
   * @since 1.4.6
     */
  protected void setClientEventHandler(ClientEventHandler handler) {
    clientEventHandler=handler;
  }

  /**
     * Sets the ClientExtendedEventHandler class that gets notified of extended client events.
   * @since 1.4.6
     */
  protected void setClientExtendedEventHandler(ClientExtendedEventHandler handler) {
    clientExtendedEventHandler=handler;
  }

  /**
     * Sets the ClientCommandHandler class that interacts with
   * client sockets.
     */
  protected void setClientCommandHandler(ClientCommandHandler handler) {
    clientCommandHandler=handler;
  }

  /**
     * Sets the ClientObjectHandler class that interacts with
   * client sockets.
   * @param handler fully qualified name of the class that
   * implements {@link ClientObjectHandler}
   * @since 1.2
     */
  protected void setClientObjectHandler(ClientObjectHandler handler) {
    clientObjectHandler = handler;
  }

  /** Closes client socket associated. */
  public abstract void closeConnection();

  /** Returns client socket associated. */
  public Socket getSocket() {
    return socket;
  }

  /**
   * Returns client socket associated.
   * @since 1.4.0
   * @see #updateInputOutputStreams
   */
  public void setSocket(Socket socket) {
    this.socket = socket;
  }

  /**
   * Checks if the client is still connected.
   * @exception SocketException if Socket is not open.
   * @deprecated since 1.4.5 Use {@link #isConnected}
   */
  public boolean isConected() throws SocketException {
    return isConnected();
  }

  /**
   * Checks if the client is still connected.
   * @exception SocketException if Socket is not open.
   * @since 1.4.5
   */
  public boolean isConnected() throws SocketException {
    if(isOpen()==false)
      throw new SocketException("Connection is no more open!");
    else
      return true;
  }

  /**
   * Checks if the client is still connected and if socket is open. This is same as isConnected()
     * but does not throw SocketException.
   * @since 1.4.6
   */
  public boolean isOpen() {
    if(lost==true || socket==null || socket.isConnected()==false || socket.isClosed()==true)
      return false;
    else
      return true;
  }

  /**
   * Checks if the client is closed.
   * @since 1.4.1
   */
  public boolean isClosed() {
    if(socket==null || socket.isClosed()==true)
      return true;
    else
      return false;
  }

  /**
   * Send a String message to the connected client
   * it adds a new line{\r\n} to the end of the string.
   * If client is not connected it will just return.
   * @exception IOException
   *        if Socket IO Error or Socket was closed by the client.
   */
  public void sendClientMsg(String msg) throws IOException {
    isConnected();

    if(dataModeOUT != DataMode.STRING)
      throw new IllegalStateException("Can't send String :" +
        "DataType.OUT is not in DataMode.STRING");
    if(getCommunicationLogging()) {
      appLogger.fine("Sending ["+getHostAddress()+"] : "+msg);
    }
    byte data[] = msg.getBytes(charset);

    synchronized(this) {
      b_out.write(data, 0, data.length);
      b_out.write(NEW_LINE_BYTES, 0, NEW_LINE_BYTES.length);
    }
    b_out.flush();

    updateLastCommunicationTime();
  }

  /**
   * Send a String message to the connected client as a string of bytes.
   * If client is not connected it will just return.
   * @since 1.3.1
   * @exception IOException
   *        if Socket IO Error or Socket was closed by the client.
   */
  public void sendClientBytes(String msg) throws IOException {
    isConnected();

    if (dataModeOUT != DataMode.BYTE)
      throw new IllegalStateException("Can't send String :" +
        "DataType.OUT is not in DataMode.BYTE");
    if(getCommunicationLogging()) {
      appLogger.fine("Sending ["+getHostAddress()+"] : "+msg);
    }
    byte data[] = msg.getBytes(charset);

    synchronized(this) {
      b_out.write(data,0,data.length);
    }
    b_out.flush();

    updateLastCommunicationTime();
  }


  /**
   * Send a Object message to the connected client. The message Object
   * passed must be serializable. If client is not connected it
   * will just return.
   * @exception IOException if Socket IO Error or Socket was closed
   * by the client.
   * @exception IllegalStateException if DataType.OUT is not in
   *  DataMode.OBJECT
   * @see #setDataMode
   * @since 1.2
   */
  public void sendClientObject(Object msg) throws IOException {
    isConnected();

    if(dataModeOUT != DataMode.OBJECT)
      throw new IllegalStateException("Can't send Object : DataType.OUT is not in DataMode.OBJECT");
    if(getCommunicationLogging()) {
      appLogger.fine("Sending ["+getHostAddress()+"] : "+msg.toString());
    }
    synchronized(this) {
      o_out.writeObject(msg);
    }
    o_out.flush();

    updateLastCommunicationTime();
  }

  /**
   * Send a String message to the logger associated with
   * {@link QuickServer#getAppLogger} with Level.INFO as its level.
   */
  public void sendSystemMsg(String msg) {
    sendSystemMsg(msg, Level.INFO);
  }

  /**
   * Send a String message to the logger associated with
   * {@link QuickServer#getAppLogger}.
   * @since 1.2
   */
  public void sendSystemMsg(String msg, Level level) {
    appLogger.log(level, msg);
  }

  /**
   * Send a String message to the system output stream.
   * @param newline indicates if new line required at the end.
   * @deprecated Use {@link #sendSystemMsg(java.lang.String)},
   *   since it uses Logging.
   */
  public void sendSystemMsg(String msg, boolean newline) {
    if(newline)
      System.out.println(msg);
    else
      System.out.print(msg);
  }

  public abstract void run();

  protected void prepareForRun() throws SocketException, IOException {
    clientConnectedTime = new java.util.Date(); //v1.3.2
    lastCommunicationTime = clientConnectedTime;//v1.3.3

    setCharset(getServer().getBasicConfig().getAdvancedSettings().getCharset());//1.4.5 
    hostAddress = getSocket().getInetAddress().getHostAddress();//1.4.5
    port = getSocket().getPort();

    if(logger.isLoggable(Level.FINEST)) {
      StringBuilder sb = new StringBuilder();
      sb.append(getName());
      sb.append(" -> ");
      sb.append(hostAddress);
      sb.append(':');
      sb.append(port);
      logger.finest(sb.toString());
    }

    socket.setSoTimeout(socketTimeout);
    connection = true;

    dataModeIN = getServer().getDefaultDataMode(DataType.IN);
    dataModeOUT = getServer().getDefaultDataMode(DataType.OUT);

    updateInputOutputStreams();
  }

  protected void processMaxConnection(ClientEvent currentEvent) throws IOException {
    if(clientExtendedEventHandler!=null) {
      if(clientExtendedEventHandler.handleMaxConnection(this)) {
        removeEvent(getThreadEvent());
        if(getThreadEvent()==ClientEvent.MAX_CON) {
          currentEvent = ClientEvent.ACCEPT;
        } else if(getThreadEvent()==ClientEvent.MAX_CON_BLOCKING) {
          currentEvent = ClientEvent.RUN_BLOCKING;
        } else {
          throw new IllegalArgumentException("Unknown ClientEvent: "+getThreadEvent());
        }
        synchronized(clientEvents) {
          clientEvents.add(currentEvent);
        }
        threadEvent.set(currentEvent);
      }
    } else if(maxConnectionMsg.length()!=0) {
      out.write(maxConnectionMsg.getBytes(charset), 0, maxConnectionMsg.length());
      out.write(NEW_LINE_BYTES, 0, NEW_LINE_BYTES.length);
      out.flush();
    }
  }

  protected AuthStatus processAuthorisation() throws SocketException,
      IOException, AppException {
    logger.finest("INSIDE");
    while(authorised==false && connection==true) {
      isConnected();

      counAuthTry++;

      if(authorised == false) {
        if(counAuthTry > maxAuthTry) {
          processMaxAuthTry();
        }
      } 

      try  {
        if(clientAuthenticationHandler!=null) {
          return clientAuthenticationHandler.askAuthentication(this);
        } else if(authenticator!=null) {
          authorised = authenticator.askAuthorisation(this)
        }
      } catch(NullPointerException e) {
        logger.severe("Authenticator implementation has not handled null properly."+
          " Input from client should be checked for null!");
        throw e;
      } catch(SocketTimeoutException e) {
        handleTimeout(e);
      }

      updateLastCommunicationTime();     
    } //end of auth while
    return AuthStatus.SUCCESS;
  }

  private void processMaxAuthTry() throws SocketException, IOException, AppException {
    if(clientExtendedEventHandler!=null) {
      clientExtendedEventHandler.handleMaxAuthTry(this);
    } else {
      String temp = maxAuthTryMsg;
      if(dataModeOUT == DataMode.STRING)
        temp = temp + NEW_LINE;
      if(dataModeOUT != DataMode.OBJECT) {
        out.write(temp.getBytes(charset));
        out.flush();
      }
    }
    appLogger.warning("Max Auth Try Reached - Client : "+getHostAddress());
    if(true) throw new AppException(maxAuthTryMsg);
  }


  protected void notifyCloseOrLost() throws IOException {
    synchronized(this) {
      if(closeOrLostNotified==false) {
        if(lost==true) {
          clientEventHandler.lostConnection(this);       
        } else {
          clientEventHandler.closingConnection(this);
        }
        closeOrLostNotified = true;
      }     
    }
  }

  protected synchronized void returnClientData() {
    if(clientData==null || getServer().getClientDataPool()==null)
      return;
    logger.finest("Returning ClientData to pool");
    try  {
      getServer().getClientDataPool().returnObject(clientData);
      clientData = null;
    } catch(Exception e) {
      logger.warning("IGNORED: Could not return ClientData to pool: "+e);
    }
  } 

  protected void returnClientHandler() {
    try  {
      synchronized(lockObj) {
        logger.finest(Thread.currentThread().getName()+" returning "+getName());
        getServer().getClientHandlerPool().returnObject(this);
      }
    } catch(Exception e) {
      logger.warning("IGNORED: Could not return ClientHandler to pool: "+e);
    }
  }

  /**
     * Returns the ClientHandler name
   * @since 1.4.6
     */
  public String getName() {
    return name;
  }

  /**
     * Returns the ClientHandler detailed information.
   * If ClientData is present and is ClientIdentifiable will return ClientInfo else
   * it will return Clients InetAddress and port information.
     */
  public String info() {
    StringBuilder sb = new StringBuilder();
    sb.append("{");
    sb.append(name);
    sb.append(" - ");
    String info = getClientIdentifiable(this);
    if(info!=null) {
      sb.append("[ClientInfo: ");
      sb.append(info);
      sb.append(']');
    }

    if(getSocket()==null || getSocket().isClosed()==true) {
      sb.append("[non-connected]");
    } else if(info==null) {
      sb.append('[');
      sb.append(hostAddress);
      sb.append(':');
      sb.append(port)
      sb.append(']');
    }
    sb.append('}');
    return sb.toString();
  }

  /**
     * Returns the ClientHandler information.
   * If ClientData is present and is ClientIdentifiable will return ClientInfo else
   * it will return Clients InetAddress and port information.
     */
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("{");
    sb.append(name);
    sb.append(" - ");
    if(getSocket()==null || getSocket().isClosed()==true) {
      sb.append("[non-connected]");
    } else if(hostAddress!=null) {
      sb.append('[');
      sb.append(hostAddress);
      sb.append(':');
      sb.append(port);
      sb.append(']');
    }
    synchronized(clientEvents) {
      if(clientEvents.size()!=0) {
        sb.append(' ');
        sb.append(clientEvents);
      }
    }
    sb.append('}');
    return sb.toString();
  }

  protected static String getClientIdentifiable(ClientHandler foundClientHandler) {
    if(foundClientHandler==null) return null;
    ClientData foundClientData = null;
    foundClientData = foundClientHandler.getClientData();
    if(foundClientData==null)
      return null;
    else if(ClientIdentifiable.class.isInstance(foundClientData)==false)
      return null;
    else
      return ((ClientIdentifiable)foundClientData).getClientInfo();
  }

  /**
   * Sets the {@link DataMode} for the ClientHandler
   *
   * Note: When mode is DataMode.OBJECT and type is DataType.IN
   * this call will block until the client ObjectOutputStream has
   * written and flushes the header.
   * @since 1.2
   * @exception IOException if mode could not be changed.
   * @param dataMode mode of data exchange - String or Object.
   * @param dataType type of data for which mode has to be set.
   */
  public abstract void setDataMode(DataMode dataMode, DataType dataType) throws IOException;

  protected void checkDataModeSet(DataMode dataMode, DataType dataType) {
    if(dataMode==DataMode.STRING && dataType==DataType.IN && clientCommandHandler==null) {
      throw new IllegalArgumentException("Can't set DataType.IN mode to STRING when ClientCommandHandler is not set!");
    }

    if(dataMode==DataMode.BYTE && dataType==DataType.IN && clientCommandHandler==null) {
      throw new IllegalArgumentException("Can't set DataType.IN mode to BYTE when ClientCommandHandler is not set!");
    }

    if(dataMode==DataMode.OBJECT && dataType==DataType.IN && clientObjectHandler==null) {
      throw new IllegalArgumentException("Can't set DataType.IN mode to OBJECT when ClientObjectHandler is not set!");
    }

    if(dataMode==DataMode.BINARY && dataType==DataType.IN && clientBinaryHandler==null) {
      throw new IllegalArgumentException("Can't set DataType.IN mode to BINARY when ClientBinaryHandler is not set!");
    }
  }

  /**
   * Returns the {@link DataMode} of the ClientHandler for the
   * DataType.
   * @since 1.2
   */
  public DataMode getDataMode(DataType dataType) {
    if(dataType == DataType.IN)
      return dataModeIN;
    else if(dataType == DataType.OUT)
      return dataModeOUT;
    else
      throw new IllegalArgumentException("Unknown DataType : " +
        dataType);
  }

  /**
   * Returns the {@link java.sql.Connection} object for the
   * DatabaseConnection that is identified by id passed. If id passed
   * does not match with any connection loaded by this class it will
   * return <code>null</code>.
   * This just calls <code>getServer().getDBPoolUtil().getConnection(id)</code>
   * @since 1.3
   * @deprecated as of v1.4.5 use <code>getServer().getDBPoolUtil().getConnection(id)</code>
   */
  public java.sql.Connection getConnection(String id) throws Exception {
    if(getServer()==null)
      throw new Exception("ClientHandler no longer is associated with any client! Try to use quickserver.getDBPoolUtil().getConnection("+id+")");
    return getServer().getDBPoolUtil().getConnection(id);
  }

  /**
   * Returns the date/time when the client socket was assigned to this
   * ClientHanlder. If no client is currently connected it will return
   * <code>null</code>
   * @since 1.3.1
   */
  public Date getClientConnectedTime() {
    return clientConnectedTime;
  }

  /**
   * Read the byte input. This will block till some data is
   * received from the stream.
   * @return The data as a String
   * @since 1.3.1
   */
  protected abstract byte[] readInputStream() throws IOException;

  protected static byte[] readInputStream(InputStream _in) throws IOException {
    byte data[] = null;
    if(_in==null)
      throw new IOException("InputStream can't be null!");
   
    int s = _in.read();
    if(s==-1) {
      return null; //Connection lost
    }
    int alength = _in.available();
    if(alength > 0) {
      data = new byte[alength+1]
      data[0] = (byte) s;
      int len = _in.read(data, 1, alength);
      if(len < alength) {
        data = copyOf(data, len+1);
      }
    } else {
      data = new byte[1];
      data[0] = (byte) s;
    }
    return data;
  }
 
  private static byte[] copyOf(byte data[], int len) {
    byte newdate[] = new byte[len];
    System.arraycopy(data, 0, newdate, 0, len);
    return newdate;
  }

  /**
   * Read the byte input. This will block till some data is
   * received from the stream. Allowed only when
   * <code>DataType.IN</code> is in <code>DataMode.BYTE</code> mode.
   * @return The data as a String
   * @since 1.3.2
   */
  public String readBytes() throws IOException {
    if(dataModeIN != DataMode.BYTE)
        throw new IllegalStateException("Can't read Byte: " +
          "DataType.IN is not in DataMode.BYTE");
    byte data[] = readInputStream();
    if(data!=null)
      return new String(data, charset);
    else
      return null;
  }

  /**
   * Sets the communication logging flag.
   * @see #getCommunicationLogging
   * @since 1.3.2
   */
  public void setCommunicationLogging(boolean communicationLogging) {
    this.communicationLogging = communicationLogging;
  }
  /**
   * Returns the communication logging flag.
   * @see #setCommunicationLogging
   * @since 1.3.2
   */
  public boolean getCommunicationLogging() {
    return communicationLogging;
  }

  /**
   * Returns the date/time when the client socket last sent a data to this
   * ClientHanlder. If no client is currently connected it will return
   * <code>null</code>
   * @since 1.3.3
   */
  public Date getLastCommunicationTime() {
    return lastCommunicationTime;
  }

  /**
   * Updates the last communication time for this client
   * @since 1.3.3
   */
  public void updateLastCommunicationTime() {
    lastCommunicationTime = new Date();
  }

  /**
   * Force the closing of the client by closing the associated socket.
   * @since 1.3.3
   */
  public synchronized void forceClose() throws IOException {
    if(getBlockingMode()==false) {
      if(getSelectionKey()!=null) getSelectionKey().cancel();
      if(getSocketChannel()!=null) {
        getSocketChannel().close();
        setSocketChannel(null);
      }
    }
    if(getSocket()!=null) {
      getSocket().close();
      setSocket(null);
    }
  }

  /**
   * Returns flag indicating if the client is connected in secure mode
   * (SSL or TLS).
   * @return secure flag
   * @since 1.4.0
   */
  public boolean isSecure() {
    return secure;
  }

  /**
   * Sets flag indicating if the client is connected in secure mode
   * (SSL or TLS).
    * @param secure
   * @since 1.4.0
   */
  public void setSecure(boolean secure) {
    this.secure = secure;
  }

  /**
   * Updates the InputStream and OutputStream for the ClientHandler for the
   * set Socket.
   * @since 1.4.0
   * @see #setSocket
   */
  public abstract void updateInputOutputStreams() throws IOException;

  /**
   * Makes current Client connection to secure protocol based on the
   * secure configuration set to the server. This method will just call
   * <code>makeSecure(false, false, true, null)</code>.
   * @throws IOException
   * @throws NoSuchAlgorithmException
   * @throws KeyManagementException
   * @since 1.4.0
   */
  public void makeSecure() throws IOException, NoSuchAlgorithmException,
      KeyManagementException {
    makeSecure(false, false, true, null);
  }

  /**
   * Makes current Client connection to secure protocol.
   * This method will just call <code>makeSecure(false, false, true, protocol)</code>.
   * @throws IOException
   * @throws NoSuchAlgorithmException
   * @throws KeyManagementException
   * @since 1.4.0
   */
  public void makeSecure(String protocol) throws IOException,
      NoSuchAlgorithmException, KeyManagementException {
    makeSecure(false, false, true, protocol);
  }

  /**
   * Makes current Client connection to secure protocol.
   * @param useClientMode falg if the socket should start its first handshake in "client" mode.
   * @param needClientAuth flag if the clients must authenticate themselves.
   * @param autoClose close the underlying socket when this socket is closed
   * @param protocol the standard name of the requested protocol. If <code>null</code> will use the protocol set in secure configuration of the server.
   * @throws IOException
   * @throws NoSuchAlgorithmException
   * @throws KeyManagementException
   * @since 1.4.0
   */
  public void makeSecure(boolean useClientMode, boolean needClientAuth,
      boolean autoClose, String protocol) throws IOException,
      NoSuchAlgorithmException, KeyManagementException {
    if(isSecure()==true) {
      throw new IllegalStateException("Client is already in secure mode!");
    }
   
    appLogger.fine("Making secure - Protocol: "+protocol+
      ", Client: ["+getHostAddress()+"]");

    javax.net.ssl.SSLSocketFactory sslSf = getServer().getSSLSocketFactory(protocol);
    String host = getServer().getBindAddr().getHostAddress();
    if(host.equals("0.0.0.0")) host = InetAddress.getLocalHost().getHostAddress();
    SSLSocket newSocket = (SSLSocket) sslSf.createSocket(
      getSocket(), host, getServer().getPort(), autoClose);
    newSocket.setNeedClientAuth(needClientAuth);
    newSocket.setUseClientMode(useClientMode);
    setSocket(newSocket);
    setSecure(true);
    updateInputOutputStreams();   
  }

  /**
   * Send a binary data to the connected client.
   * If client is not connected it will just return.
   * @since 1.4
   * @exception IOException
   *        if Socket IO Error or Socket was closed by the client.
   */
  public void sendClientBinary(byte data[]) throws IOException {
    sendClientBinary(data, 0, data.length);
  }

  /**
   * Send a binary data to the connected client.
   * If client is not connected it will just return.
   * @since 1.4.5
   * @exception IOException
   *        if Socket IO Error or Socket was closed by the client.
   */
  public void sendClientBinary(byte data[], int off, int len) throws IOException {
    if(isConnected()) {
      if(dataModeOUT != DataMode.BINARY)
        throw new IllegalStateException("Can't send Binary :" +
          "DataType.OUT is not in DataMode.BINARY");
      if(getCommunicationLogging()) {
        appLogger.fine("Sending ["+getHostAddress()+"] : "+MyString.getMemInfo(len));
      }
      b_out.write(data, off, len);
      b_out.flush();
    } else {
      logger.warning("Client not connected.");
    }
    updateLastCommunicationTime();
  }

  /**
   * Read the binary input. This will block till some data is
   * received from the stream. Allowed only when
   * <code>DataType.IN</code> is in <code>DataMode.BINARY</code> mode.
   * @return The data as a String
   * @since 1.4
   */
  public byte[] readBinary() throws IOException {
    if(dataModeIN != DataMode.BINARY)
        throw new IllegalStateException("Can't read Binary :" +
          "DataType.IN is not in DataMode.BINARY");
    byte data[] = readInputStream();
    return data;
  }

  /**
     * Sets the ClientBinaryHandler class that interacts with
   * client sockets.
   * @param handler fully qualified name of the class that
   * implements {@link ClientBinaryHandler}
   * @since 1.4
     */
  protected void setClientBinaryHandler(ClientBinaryHandler handler) {
    clientBinaryHandler=handler;
  }

  /**
   * Returns client SelectionKey associated, if any.
   * @since 1.4.5
   */
  public Logger getAppLogger() {
    return appLogger;
  }

  /**
     * Sets the client socket's timeout.
   * @param time client socket timeout in milliseconds.
   * @see #getTimeout
   * @since 1.4.5
     */
  public void setTimeout(int time) {
    socketTimeout = time;
 
  /**
     * Returns the Client socket timeout in milliseconds.
   * @see #setTimeout
   * @since 1.4.5
     */
  public int getTimeout() {
    return socketTimeout;
  }

  /**
   * Checks if this client has the event.
   * @since 1.4.5
   */
  public boolean hasEvent(ClientEvent event) {
    synchronized(clientEvents) {
      return clientEvents.contains(event);
    }
  }

  /**
   * Adds the ClientEvent.
   * @since 1.4.5
   */
  public void addEvent(ClientEvent event) {
    synchronized(clientEvents) {
      unprocessedClientEvents.add(event);
      clientEvents.add(event);
    }
  }

  /**
   * Removes the ClientEvent.
   * @since 1.4.5
   */
  public void removeEvent(ClientEvent event) {
    if(event==null) return;

    synchronized(clientEvents) {
      clientEvents.remove(event);
    }

    ClientEvent _clientEvent = (ClientEvent)threadEvent.get();
    if(_clientEvent!=null && _clientEvent==event) {
      threadEvent.set(null);
    }
   
  }

  /**
   * Returns threads current event for this client.
   * @since 1.4.5
   */
  protected ClientEvent getThreadEvent() {
    return (ClientEvent)threadEvent.get();
  }

  /**
   * Sets message to be displayed when maximum connection reaches.
   * @since 1.4.5
   */
  public void setMaxConnectionMsg(String msg) {
    maxConnectionMsg = msg;
  }
  /**
   * Returns message to be displayed to the client when maximum
   * connection reaches.
   * @since 1.4.5
   */
  public String getMaxConnectionMsg() {
    return maxConnectionMsg;
  }

  /**
   * Returns the current blocking mode of the server.
   * @since 1.4.9
   */
  public abstract boolean getBlockingMode();

  /**
   * Sets client socket channel associated, if any.
   * @since 1.4.5
   */
  public abstract void setSocketChannel(SocketChannel socketChannel);
  /**
   * Returns client socket channel associated, if any.
   * @since 1.4.5
   */
  public abstract SocketChannel getSocketChannel();

  /**
   * Sets client SelectionKey associated, if any.
   * @since 1.4.5
   */
  public abstract void setSelectionKey(SelectionKey selectionKey);
  /**
   * Returns client SelectionKey associated, if any.
   * @since 1.4.5
   */
  public abstract SelectionKey getSelectionKey();

  public boolean getWillClean() {
    return willClean;
  }

  /**
   * Register OP_READ with the SelectionKey associated with the channel. If SelectionKey is
   * not set then it registers the channel with the Selector.
   * @since 1.4.5
   */
  public abstract void registerForRead() throws IOException,
    ClosedChannelException;
 
  /**
   * Register OP_WRITE with the SelectionKey associated with the channel.
   * @since 1.4.5
   */
  public abstract void registerForWrite() throws IOException,
    ClosedChannelException;

  /**
     * Sets the ClientWriteHandler class that interacts with
   * client sockets.
   * @param handler fully qualified name of the class that
   * implements {@link ClientWriteHandler}
   * @since 1.4.5
     */
  protected abstract void setClientWriteHandler(ClientWriteHandler handler);

  /**
     * Sets the Charset to be used for String decoding and encoding.
   * @param charset to be used for String decoding and encoding
   * @see #getCharset
   * @since 1.4.5
     */
  public void setCharset(String charset) {
    if(charset==null || charset.trim().length()==0)
      return;
    this.charset = charset;
  }
  /**
     * Returns Charset to be used for String decoding and encoding..
     * @see #setCharset
   * @since 1.4.5
     */
  public String getCharset() {
    return charset;
  }

  /**
   * Returns cached socket host ip address.
   * @since 1.4.5
   */
  public String getHostAddress() {
    return hostAddress;
  }

  protected void assertionSystemExit() {
    logger.warning("[Assertions Was Enabled] Forcing program exit to help developer.");
    org.quickserver.net.qsadmin.QSAdminShell.tryFullThreadDump();//it can help debug.
    try {
      Thread.sleep(100)
    } catch(InterruptedException e) {
      logger.fine("Interrupted: "+e);
    }   
    System.exit(-1);
  }

  /**
   * Checks if the passed ClientEvent is the one next for
   * processing if a thread is allowed through this object.
   * @since 1.4.6
   */
  public boolean isClientEventNext(ClientEvent clientEvent) {
    ClientEvent ce = null;
    synchronized(clientEvents) {
      if(unprocessedClientEvents.size()>0)
        ce = (ClientEvent) unprocessedClientEvents.get(0);
    }
    return clientEvent == ce;
  }

  /**
   *Returns the {@link java.io.BufferedInputStream} associated with
   * the Client being handled. Can be null if not available at the time of method call.
   * @see #getBufferedOutputStream
   * @since 1.4.6
   */
  public BufferedInputStream getBufferedInputStream() {
    return b_in;
  }

  /**
   * Returns the {@link java.io.BufferedOutputStream} associated with
   * the Client being handled. Can be null if not available at the time of method call.
   * @see #getBufferedInputStream
   * @since 1.4.6
   */
  public BufferedOutputStream getBufferedOutputStream() {
    return b_out;
  }

  protected void handleTimeout(SocketTimeoutException e) throws SocketException, IOException {
    appLogger.fine("Timeout - Client [" + getHostAddress() +"]");
    appLogger.fine("LastCommunicationTime - " +getLastCommunicationTime());
    appLogger.finest("SocketTimeoutException : " + e.getMessage());

    String temp = null;
    if(clientExtendedEventHandler!=null) {
      clientExtendedEventHandler.handleTimeout(this);
    } else {
      temp = timeoutMsg;
      if(dataModeOUT == DataMode.STRING)
        temp = temp + NEW_LINE;
      if(dataModeOUT != DataMode.OBJECT) {
        out.write(temp.getBytes(charset));
        out.flush();
      }
      if(true) throw new SocketException("Timeout");
    }
  }

  /**
   * Returns SSLEngine if in secure mode.
   * @since 1.4.9
   */
  public SSLEngine getSSLEngine() {
    return sslEngine;
  }
 
}
TOP

Related Classes of org.quickserver.net.server.impl.BasicClientHandler$InstanceId

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.