Package tigase.net

Source Code of tigase.net.IOService

/*
* Tigase Jabber/XMPP Server
* Copyright (C) 2004-2007 "Artur Hefczyc" <artur.hefczyc@tigase.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. Look for COPYING file in the top folder.
* If not, see http://www.gnu.org/licenses/.
*
* $Rev: 1284 $
* Last modified by $Author: kobit $
* $Date: 2008-12-11 19:36:28 +0000 (Thu, 11 Dec 2008) $
*/

package tigase.net;

import java.io.IOException;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CharsetDecoder;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger;
import tigase.io.IOInterface;
import tigase.io.SocketIO;
import tigase.io.TLSIO;
import tigase.io.TLSUtil;
import tigase.io.TLSWrapper;
import tigase.io.BufferUnderflowException;
import tigase.util.TimeUtils;

/**
* <code>IOService</code> offers thread thread safe
* <code>call()</code> method execution, however you must be prepared that other
* methods can be called simultanously like <code>stop()</code>,
* <code>getProtocol()</code> or <code>isConnected()</code>.
* <br/>It is recomended that developers extend <code>AbsractServerService</code>
* rather then implement <code>ServerService</code> interface directly.
* <p>If you directly implement <code>ServerService</code> interface you must
* take care about <code>SocketChannel</code> I/O, queueing tasks, processing
* results and thread safe execution of <code>call()</code> method. If you
* however extend <code>IOService</code> class all this basic
* operation are implemented and you have only to take care about parsing data
* received from network socket. Parsing data is expected to be implemented in
* <code>parseData(char[] data)</code> method.</p>
*
* <p>
* Created: Tue Sep 28 23:00:34 2004
* </p>
* @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a>
* @version $Rev: 1284 $
*/
public abstract class IOService implements Callable<IOService> {

  /**
   * Variable <code>log</code> is a class logger.
   */
  private static final Logger log = Logger.getLogger("tigase.net.IOService");

  /**
   * This is key used to store session ID in temporary session data storage.
   * As it is used by many components it is required that all components access
   * session ID with this constant.
   */
  public static final String SESSION_ID_KEY = "sessionID";
  public static final String PORT_TYPE_PROP_KEY = "type";
  public static final String HOSTNAME_KEY = "hostname-key";

  private IOInterface socketIO = null;
  private String sslId = null;
  private String id = null;
  private ConnectionType connectionType = null;
  private String local_address = null;
  private String remote_address = null;
  /**
   * This variable keeps the time of last transfer in any direction
   * it is used to help detect dead connections.
   */
  private long lastTransferTime = 0;
  private boolean stopping = false;

  private long[] rdData = new long[60];
  private long[] wrData = new long[60];
  private int lastMinuteRd = 0;
  private int lastMinuteWr = 0;

  private IOServiceListener serviceListener = null;

  private ConcurrentMap<String, Object> sessionData =
    new ConcurrentHashMap<String, Object>();

  /**
   * <code>socketInput</code> buffer keeps data read from socket.
   */
  private ByteBuffer socketInput = null;

  private CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
  private CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();

  private String dataReceiver = null;

  private void addRead(long read) {
    int minute = TimeUtils.getMinuteNow();
    if (lastMinuteRd != minute) {
      lastMinuteRd = minute;
      rdData[minute] = 0;
    }
    rdData[minute] += read;
  }

  public long[] getReadCounters() {
    return rdData;
  }

  private void addWritten(long wrote) {
    int minute = TimeUtils.getMinuteNow();
    if (lastMinuteWr != minute) {
      lastMinuteWr = minute;
      wrData[minute] = 0;
    }
    wrData[minute] += wrote;
  }

  public long[] getWriteCounters() {
    return wrData;
  }

  public void setDataReceiver(String address) {
    this.dataReceiver = address;
  }

  public String getDataReceiver() {
    return this.dataReceiver;
  }

  public void setSSLId(final String id) {
    sslId = id;
  }

  /**
   * This method returns the time of last transfer in any direction
   * through this service. It is used to help detect dead connections.
   */
  public long getLastTransferTime() {
    return lastTransferTime;
  }

  private void setLastTransferTime() {
    lastTransferTime = System.currentTimeMillis();
  }

  public void startSSL(final boolean clientMode)
    throws IOException {

    TLSWrapper wrapper = new TLSWrapper(TLSUtil.getSSLContext(sslId, "SSL",
        (String)sessionData.get(HOSTNAME_KEY)), null, clientMode);
    socketIO = new TLSIO(socketIO, wrapper);
    setLastTransferTime();
  }

  public void startTLS(final boolean clientMode)
    throws IOException {

    TLSWrapper wrapper = new TLSWrapper(TLSUtil.getSSLContext(sslId, "TLS",
        (String)sessionData.get(HOSTNAME_KEY)), null, clientMode);
    socketIO = new TLSIO(socketIO, wrapper);
    setLastTransferTime();
  }

  public void setIOServiceListener(IOServiceListener sl) {
    this.serviceListener = sl;
  }

  public String getUniqueId() {
    return id;
  }

  public ConnectionType connectionType() {
    return this.connectionType;
  }

  public ConcurrentMap<String, Object> getSessionData() {
    return sessionData;
  }

  public void setSessionData(Map<String, Object> props) {
    sessionData = new ConcurrentHashMap<String, Object>(props);
    connectionType =
      ConnectionType.valueOf(sessionData.get(PORT_TYPE_PROP_KEY).toString());
  }

  /**
   * Describe <code>isConnected</code> method here.
   *
   * @return a <code>boolean</code> value
   */
  public synchronized boolean isConnected() {
    log.finest("socketIO = " + socketIO);
    return socketIO == null ? false : socketIO.isConnected();
  }

  public String getRemoteAddress() {
    return remote_address;
  }

  public String getLocalAddress() {
    return local_address;
  }

  /**
   * Method <code>accept</code> is used to perform
   *
   * @param socketChannel a <code>SocketChannel</code> value
   */
  public void accept(final SocketChannel socketChannel)
    throws IOException {
    try {
      if (socketChannel.isConnectionPending()) {
        socketChannel.finishConnect();
      } // end of if (socketChannel.isConnecyionPending())
      socketIO = new SocketIO(socketChannel);
    } catch (IOException e) {
      String host = (String)sessionData.get("remote-hostname");
      if (host == null) {
        host = (String)sessionData.get("remote-host");
      }
      log.info("Problem connecting to remote host: " + host +
              ", address: " + remote_address + " - exception: " + e);
      throw e;
    }
    socketInput = ByteBuffer.allocate(socketIO.getInputPacketSize());
    Socket sock = socketIO.getSocketChannel().socket();
    local_address = sock.getLocalAddress().getHostAddress();
    remote_address = sock.getInetAddress().getHostAddress();
    id = local_address + "_" + sock.getLocalPort()
      + "_" + remote_address + "_" + sock.getPort();
    setLastTransferTime();
  }

  /**
   * Method <code>getSocketChannel</code> is used to perform
   *
   * @return a <code>SocketChannel</code> value
   */
  public SocketChannel getSocketChannel() {
    return socketIO.getSocketChannel();
  }

  /**
   * Describe <code>stop</code> method here.
   *
   * @exception IOException if an error occurs
   */
  public void stop() {
    if (socketIO != null && socketIO.waitingToSend()) {
      stopping = true;
    } else {
      forceStop();
    }
  }

  public synchronized void forceStop() {
    log.finer("Force stop called...");
    try {
      if (socketIO != null) {
        socketIO.stop();
      }
    } catch (Exception e) {
      // Well, do nothing, we are closing the connection anyway....
    } finally {
      if (serviceListener != null) {
        IOServiceListener tmp = serviceListener;
        serviceListener = null;
        tmp.serviceStopped(this);
      }
    }
  }

  /**
   * Method <code>run</code> is used to perform
   *
   */
  public IOService call() throws IOException {
    // It is not safe to call below function here....
    // It might be already executing in different thread...
    // and we don't want to put any locking or synchronization
    //    processWaitingPackets();
    writeData(null);
    if (stopping) {
      stop();
    } else {
      processSocketData();
      if (receivedPackets() > 0 && serviceListener != null) {
        serviceListener.packetsReady(this);
      } // end of if (receivedPackets.size() > 0)
    }
    return this;
  }

  public abstract void processWaitingPackets() throws IOException;

  protected abstract void processSocketData() throws IOException;

  protected abstract int receivedPackets();

  private void resizeInputBuffer() throws IOException {
    int netSize = socketIO.getInputPacketSize();
    // Resize buffer if needed.
    if (netSize > socketInput.remaining()) {
      log.fine("Resizing buffer to "
        + (netSize + socketInput.capacity())
        + " bytes.");
      ByteBuffer b = ByteBuffer.allocate(netSize+socketInput.capacity());
      b.put(socketInput);
      socketInput = b;
    }
  }

  protected void readCompleted() {
    decoder.reset();
  }

  private long empty_read_call_count = 0;
  private static final long MAX_ALLOWED_EMPTY_CALLS = 100;

  /**
   * Describe <code>readData</code> method here.
   *
   * @return a <code>char[]</code> value
   * @exception IOException if an error occurs
   */
  protected char[] readData() throws IOException {
    setLastTransferTime();
    CharBuffer cb = null;
    synchronized (decoder) {
      try {
        //      resizeInputBuffer();
        ByteBuffer tmpBuffer = socketIO.read(socketInput);
        if (socketIO.bytesRead() > 0) {
          empty_read_call_count = 0;
          tmpBuffer.flip();
          cb = decoder.decode(tmpBuffer);
          tmpBuffer.clear();
          addRead(cb.array().length);
        } else {
          // Detecting infinite read 0 bytes
          // sometimes it happens that the connection has been lost
          // and the select thinks there are some bytes waiting for reading
          // and 0 bytes are read
          if ((++empty_read_call_count) > MAX_ALLOWED_EMPTY_CALLS) {
            log.warning("Max allowed empty calls excceeded, closing connection.");
            forceStop();
          }
        }
      } catch (BufferUnderflowException underfl) {
        // Obtain more inbound network data for src,
        // then retry the operation.
        resizeInputBuffer();
        return null;
      } catch (Exception eof) {
        //      eof.printStackTrace();
        forceStop();
      } // end of try-catch
    }
    return cb != null ? cb.array() : null;
  }

  public boolean waitingToSend() {
    return socketIO.waitingToSend();
  }

  /**
   * Describe <code>writeData</code> method here.
   *
   * @param data a <code>String</code> value
   * @exception IOException if an error occurs
   */
  protected void writeData(final String data) {
    synchronized (encoder) {
      try {
        if (data != null && data.length() > 0) {
          log.finest("Writing data: " + data);
          ByteBuffer dataBuffer = null;
          encoder.reset();
          dataBuffer = encoder.encode(CharBuffer.wrap(data));
          encoder.flush(dataBuffer);
          socketIO.write(dataBuffer);

          setLastTransferTime();
          addWritten(data.length());
        } else {
          if (socketIO.waitingToSend()) {
            socketIO.write(null);

            setLastTransferTime();
          }
        }
        empty_read_call_count = 0;
      } catch (Exception e) {
        forceStop();
      }
    }
  }

  /**
   * Describe <code>debug</code> method here.
   *
   * @param msg a <code>char[]</code> value
   * @return a <code>boolean</code> value
   */
  protected boolean debug(final char[] msg) {
    if (msg != null) {
      System.out.print(new String(msg));
      //      log.finest("\n" + new String(msg) + "\n");
    } // end of if (msg != null)
    return true;
  }

  /**
   * Describe <code>debug</code> method here.
   *
   * @param msg a <code>String</code> value
   * @return a <code>boolean</code> value
   */
  protected boolean debug(final String msg, final String prefix) {
    if (msg != null && msg.trim().length() > 0) {
      String log_msg = "\n"
        + (connectionType() != null ?  connectionType().toString() : "null-type")
        + " " + prefix + "\n" + msg + "\n";
        //      System.out.print(log_msg);
        log.finest(log_msg);
    }
    return true;
  }

}// IOService
TOP

Related Classes of tigase.net.IOService

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.