All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.orientechnologies.orient.server.OClientConnectionManager Maven / Gradle / Ivy

The newest version!
/*
 *
 *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
 *  *
 *  *  Licensed under the Apache License, Version 2.0 (the "License");
 *  *  you may not use this file except in compliance with the License.
 *  *  You may obtain a copy of the License at
 *  *
 *  *       http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *  Unless required by applicable law or agreed to in writing, software
 *  *  distributed under the License is distributed on an "AS IS" BASIS,
 *  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *  See the License for the specific language governing permissions and
 *  *  limitations under the License.
 *  *
 *  * For more information: http://orientdb.com
 *
 */
package com.orientechnologies.orient.server;

import com.orientechnologies.common.exception.OException;
import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.common.profiler.OAbstractProfiler.OProfilerHookValue;
import com.orientechnologies.common.profiler.OProfiler.METRIC_TYPE;
import com.orientechnologies.orient.core.Orient;
import com.orientechnologies.orient.core.command.OCommandRequestText;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.security.OParsedToken;
import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSerializer;
import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSerializerFactory;
import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinary;
import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinaryProtocol;
import com.orientechnologies.orient.enterprise.channel.binary.OTokenSecurityException;
import com.orientechnologies.orient.server.network.protocol.ONetworkProtocol;
import com.orientechnologies.orient.server.network.protocol.binary.ONetworkProtocolBinary;
import com.orientechnologies.orient.server.plugin.OServerPluginHelper;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.SSLSocket;

public class OClientConnectionManager {
  private static final long TIMEOUT_PUSH = 3000;

  protected final ConcurrentMap connections =
      new ConcurrentHashMap();
  protected AtomicInteger connectionSerial = new AtomicInteger(0);
  protected final ConcurrentMap sessions =
      new ConcurrentHashMap();
  protected final TimerTask timerTask;
  private OServer server;

  public OClientConnectionManager(OServer server) {
    final int delay = OGlobalConfiguration.SERVER_CHANNEL_CLEAN_DELAY.getValueAsInteger();

    timerTask =
        Orient.instance()
            .scheduleTask(
                () -> {
                  try {
                    cleanExpiredConnections();
                  } catch (Exception e) {
                    OLogManager.instance().debug(this, "Error on client connection purge task", e);
                  }
                },
                delay,
                delay);

    Orient.instance()
        .getProfiler()
        .registerHookValue(
            "server.connections.actives",
            "Number of active network connections",
            METRIC_TYPE.COUNTER,
            new OProfilerHookValue() {
              public Object getValue() {
                return (long) connections.size();
              }
            });
    this.server = server;
  }

  public void cleanExpiredConnections() {
    final Iterator> iterator = connections.entrySet().iterator();
    while (iterator.hasNext()) {
      final Entry entry = iterator.next();

      if (entry.getValue().tryAcquireForExpire()) {
        try {

          final Socket socket;
          if (entry.getValue().getProtocol() == null
              || entry.getValue().getProtocol().getChannel() == null) socket = null;
          else socket = entry.getValue().getProtocol().getChannel().socket;

          if (socket == null || socket.isClosed() || socket.isInputShutdown()) {
            OLogManager.instance()
                .debug(
                    this,
                    "[OClientConnectionManager] found and removed pending closed channel %d (%s)",
                    entry.getKey(),
                    socket);
            try {
              OCommandRequestText command = entry.getValue().getData().command;
              if (command != null && command.isIdempotent()) {
                entry.getValue().getProtocol().sendShutdown();
                entry.getValue().getProtocol().interrupt();
              }
              removeConnectionFromSession(entry.getValue());
              entry.getValue().close();

            } catch (Exception e) {
              OLogManager.instance()
                  .error(this, "Error during close of connection for close channel", e);
            }
            iterator.remove();
          } else if (Boolean.TRUE.equals(entry.getValue().getTokenBased())) {
            if (entry.getValue().getToken() != null
                && !server.getTokenHandler().validateBinaryToken(entry.getValue().getToken())) {
              // Close the current session but not the network because can be used by another
              // session.
              removeConnectionFromSession(entry.getValue());
              entry.getValue().close();
              iterator.remove();
            }
          }
        } finally {
          entry.getValue().release();
        }
      }
    }
    server.getPushManager().cleanPushSockets();
  }

  /**
   * Create a connection.
   *
   * @param iProtocol protocol which will be used by connection
   * @return new connection
   */
  public OClientConnection connect(final ONetworkProtocol iProtocol) {

    final OClientConnection connection;

    connection = new OClientConnection(connectionSerial.incrementAndGet(), iProtocol);

    connections.put(connection.getId(), connection);
    OLogManager.instance().config(this, "Remote client connected from: " + connection);
    OServerPluginHelper.invokeHandlerCallbackOnClientConnection(iProtocol.getServer(), connection);
    return connection;
  }

  /**
   * Create a connection.
   *
   * @param iProtocol protocol which will be used by connection
   * @return new connection
   */
  public OClientConnection connect(
      final ONetworkProtocol iProtocol,
      final OClientConnection connection,
      final byte[] tokenBytes) {

    OParsedToken parsedToken;
    try {
      parsedToken = server.getTokenHandler().parseOnlyBinary(tokenBytes);
    } catch (Exception e) {
      throw OException.wrapException(new OTokenSecurityException("Error on token parsing"), e);
    }
    if (!server.getTokenHandler().validateBinaryToken(parsedToken)) {
      throw new OTokenSecurityException("The token provided is expired");
    }
    OClientSessions session;
    synchronized (sessions) {
      session = new OClientSessions(tokenBytes);
      sessions.put(new OHashToken(tokenBytes), session);
    }
    connection.setToken(parsedToken, tokenBytes);
    session.addConnection(connection);
    OLogManager.instance().config(this, "Remote client connected from: " + connection);
    OServerPluginHelper.invokeHandlerCallbackOnClientConnection(iProtocol.getServer(), connection);
    return connection;
  }

  public OClientConnection reConnect(final ONetworkProtocol iProtocol, final byte[] tokenBytes) {
    final OClientConnection connection;
    connection = new OClientConnection(connectionSerial.incrementAndGet(), iProtocol);
    connections.put(connection.getId(), connection);
    OParsedToken parsedToken;
    try {
      parsedToken = server.getTokenHandler().parseOnlyBinary(tokenBytes);
    } catch (Exception e) {
      throw OException.wrapException(new OTokenSecurityException("Error on token parsing"), e);
    }
    if (!server.getTokenHandler().validateBinaryToken(parsedToken)) {
      throw new OTokenSecurityException("The token provided is expired");
    }

    OHashToken key = new OHashToken(tokenBytes);
    OClientSessions sess;
    synchronized (sessions) {
      sess = sessions.get(key);
      if (sess == null) {
        // RECONNECT
        sess = new OClientSessions(tokenBytes);
        sessions.put(new OHashToken(tokenBytes), sess);
      }
    }
    connection.setToken(parsedToken, tokenBytes);
    sess.addConnection(connection);
    OServerPluginHelper.invokeHandlerCallbackOnClientConnection(iProtocol.getServer(), connection);
    return connection;
  }

  /**
   * Retrieves the connection by id.
   *
   * @param iChannelId id of connection
   * @return The connection if any, otherwise null
   */
  public OClientConnection getConnection(final int iChannelId, ONetworkProtocol protocol) {
    // SEARCH THE CONNECTION BY ID
    OClientConnection connection = connections.get(iChannelId);
    if (connection != null) connection.setProtocol(protocol);

    return connection;
  }

  /**
   * Retrieves the connection by address/port.
   *
   * @param iAddress The address as string in the format address as format :
   * @return The connection if any, otherwise null
   */
  public OClientConnection getConnection(final String iAddress) {
    for (OClientConnection conn : connections.values()) {
      if (iAddress.equals(conn.getRemoteAddress())) return conn;
    }
    return null;
  }

  /**
   * Disconnects and kill the associated network manager.
   *
   * @param iChannelId id of connection
   */
  public void kill(final int iChannelId) {
    kill(connections.get(iChannelId));
  }

  /**
   * Disconnects and kill the associated network manager.
   *
   * @param connection connection to kill
   */
  public void kill(final OClientConnection connection) {
    if (connection != null) {
      final ONetworkProtocol protocol = connection.getProtocol();

      try {
        // INTERRUPT THE NEWTORK MANAGER TOO
        protocol.interrupt();
      } catch (Exception e) {
        OLogManager.instance().error(this, "Error during interruption of binary protocol", e);
      }

      disconnect(connection);

      // KILL THE NETWORK MANAGER TOO
      protocol.sendShutdown();
    }
  }

  public boolean has(final int id) {
    return connections.containsKey(id);
  }

  /**
   * Interrupt the associated network manager.
   *
   * @param iChannelId id of connection
   */
  public void interrupt(final int iChannelId) {
    final OClientConnection connection = connections.get(iChannelId);
    if (connection != null) {
      final ONetworkProtocol protocol = connection.getProtocol();
      if (protocol != null)
        // INTERRUPT THE NEWTORK MANAGER
        protocol.softShutdown();
    }
  }

  /**
   * Disconnects a client connections
   *
   * @param iChannelId id of connection
   * @return true if was last one, otherwise false
   */
  public boolean disconnect(final int iChannelId) {
    OLogManager.instance().debug(this, "Disconnecting connection with id=%d", iChannelId);

    final OClientConnection connection = connections.remove(iChannelId);

    if (connection != null) {
      OServerPluginHelper.invokeHandlerCallbackOnClientDisconnection(server, connection);
      connection.close();
      removeConnectionFromSession(connection);

      // CHECK IF THERE ARE OTHER CONNECTIONS
      for (Entry entry : connections.entrySet()) {
        if (entry.getValue().getProtocol().equals(connection.getProtocol())) {
          OLogManager.instance()
              .debug(
                  this,
                  "Disconnected connection with id=%d but are present other active channels",
                  iChannelId);
          return false;
        }
      }

      OLogManager.instance()
          .debug(
              this,
              "Disconnected connection with id=%d, no other active channels found",
              iChannelId);
      return true;
    }

    OLogManager.instance().debug(this, "Cannot find connection with id=%d", iChannelId);
    return false;
  }

  private void removeConnectionFromSession(OClientConnection connection) {
    if (connection.getProtocol() instanceof ONetworkProtocolBinary) {
      byte[] tokenBytes = connection.getTokenBytes();
      OHashToken hashToken = new OHashToken(tokenBytes);
      synchronized (sessions) {
        OClientSessions sess = sessions.get(hashToken);
        if (sess != null) {
          sess.removeConnection(connection);
          if (!sess.isActive()) {
            sessions.remove(hashToken);
          }
        }
      }
    }
  }

  public void disconnect(final OClientConnection iConnection) {
    OLogManager.instance().debug(this, "Disconnecting connection %s...", iConnection);
    OServerPluginHelper.invokeHandlerCallbackOnClientDisconnection(server, iConnection);
    removeConnectionFromSession(iConnection);
    iConnection.close();

    int totalRemoved = 0;
    for (Entry entry :
        new HashMap(connections).entrySet()) {
      final OClientConnection conn = entry.getValue();
      if (conn != null && conn.equals(iConnection)) {
        connections.remove(entry.getKey());
        totalRemoved++;
      }
    }

    OLogManager.instance()
        .debug(this, "Disconnected connection %s found %d channels", iConnection, totalRemoved);
  }

  public List getConnections() {
    return new ArrayList(connections.values());
  }

  public int getTotal() {
    return connections.size();
  }

  /** Pushes the distributed configuration to all the connected clients. */
  public void pushDistribCfg2Clients(final ODocument iConfig) {
    if (iConfig == null) return;

    final Set pushed = new HashSet();
    for (OClientConnection c : connections.values()) {
      if (!c.getData().supportsLegacyPushMessages) continue;

      try {
        final String remoteAddress = c.getRemoteAddress();
        if (pushed.contains(remoteAddress))
          // ALREADY SENT: JUMP IT
          continue;

      } catch (Exception e) {
        // SOCKET EXCEPTION SKIP IT
        continue;
      }

      if (!(c.getProtocol() instanceof ONetworkProtocolBinary)
          || c.getData().getSerializationImpl() == null)
        // INVOLVE ONLY BINARY PROTOCOLS
        continue;

      final ONetworkProtocolBinary p = (ONetworkProtocolBinary) c.getProtocol();
      final OChannelBinary channel = p.getChannel();
      final ORecordSerializer ser =
          ORecordSerializerFactory.instance().getFormat(c.getData().getSerializationImpl());
      if (ser == null) return;

      final byte[] content = ser.toStream(iConfig);

      try {
        // TRY ACQUIRING THE LOCK FOR MAXIMUM 3 SECS TO AVOID TO FREEZE CURRENT THREAD
        if (channel.tryAcquireWriteLock(TIMEOUT_PUSH)) {
          try {
            channel.writeByte(OChannelBinaryProtocol.PUSH_DATA);
            channel.writeInt(Integer.MIN_VALUE);
            channel.writeByte(OChannelBinaryProtocol.REQUEST_PUSH_DISTRIB_CONFIG);
            channel.writeBytes(content);
            channel.flush();

            pushed.add(c.getRemoteAddress());
            OLogManager.instance()
                .debug(
                    this,
                    "Sent updated cluster configuration to the remote client %s",
                    c.getRemoteAddress());

          } finally {
            channel.releaseWriteLock();
          }
        } else {
          OLogManager.instance()
              .info(
                  this,
                  "Timeout on sending updated cluster configuration to the remote client %s",
                  c.getRemoteAddress());
        }
      } catch (Exception e) {
        OLogManager.instance()
            .warn(
                this,
                "Cannot push cluster configuration to the client %s",
                e,
                c.getRemoteAddress());
      }
    }
  }

  public void shutdown() {
    timerTask.cancel();

    List toWait = new ArrayList();

    final Iterator> iterator = connections.entrySet().iterator();
    while (iterator.hasNext()) {
      final Entry entry = iterator.next();

      final ONetworkProtocol protocol = entry.getValue().getProtocol();

      if (protocol != null) protocol.sendShutdown();

      OLogManager.instance().debug(this, "Sending shutdown to thread %s", protocol);

      OCommandRequestText command = entry.getValue().getData().command;
      if (command != null && command.isIdempotent()) {
        protocol.interrupt();
      } else {
        if (protocol instanceof ONetworkProtocolBinary
            && ((ONetworkProtocolBinary) protocol).getRequestType()
                == OChannelBinaryProtocol.REQUEST_SHUTDOWN) {
          continue;
        }

        final Socket socket;
        if (protocol == null || protocol.getChannel() == null) socket = null;
        else socket = protocol.getChannel().socket;

        if (socket != null && !socket.isClosed() && !socket.isInputShutdown()) {
          try {
            OLogManager.instance().debug(this, "Closing input socket of thread %s", protocol);
            if (!(socket
                instanceof SSLSocket)) // An SSLSocket will throw an UnsupportedOperationException.
            socket.shutdownInput();
          } catch (IOException e) {
            OLogManager.instance()
                .debug(
                    this,
                    "Error on closing connection of %s client during shutdown",
                    e,
                    entry.getValue().getRemoteAddress());
          }
        }
        if (protocol.isAlive()) {
          if (protocol instanceof ONetworkProtocolBinary
              && ((ONetworkProtocolBinary) protocol).getRequestType() == -1) {
            try {
              OLogManager.instance().debug(this, "Closing socket of thread %s", protocol);
              protocol.getChannel().close();
            } catch (Exception e) {
              OLogManager.instance().debug(this, "Error during chanel close at shutdown", e);
            }
            OLogManager.instance().debug(this, "Sending interrupt signal to thread %s", protocol);
            protocol.interrupt();
          }
          toWait.add(protocol);
        }
      }
    }

    for (ONetworkProtocol protocol : toWait) {
      try {
        protocol.join(
            server
                .getContextConfiguration()
                .getValueAsInteger(OGlobalConfiguration.SERVER_CHANNEL_CLEAN_DELAY));
        if (protocol.isAlive()) {
          protocol.interrupt();
          protocol.join();
        }
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
      }
    }
  }

  public void killAllChannels() {
    for (Map.Entry entry : connections.entrySet()) {
      try {
        ONetworkProtocol protocol = entry.getValue().getProtocol();

        protocol.getChannel().close();

        final Socket socket;
        if (protocol == null || protocol.getChannel() == null) socket = null;
        else socket = protocol.getChannel().socket;

        if (socket != null && !socket.isClosed() && !socket.isInputShutdown()) {
          if (!(socket
              instanceof SSLSocket)) // An SSLSocket will throw an UnsupportedOperationException.
          socket.shutdownInput();
        }

      } catch (Exception e) {
        OLogManager.instance()
            .debug(
                this,
                "Error on killing connection to %s client",
                e,
                entry.getValue().getRemoteAddress());
      }
    }
  }

  public OClientSessions getSession(OClientConnection connection) {
    OHashToken key = new OHashToken(connection.getTokenBytes());
    synchronized (sessions) {
      return sessions.get(key);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy