org.waarp.openr66.protocol.networkhandler.NetworkTransaction.java Source code

Java tutorial

Introduction

Here is the source code for org.waarp.openr66.protocol.networkhandler.NetworkTransaction.java

Source

/**
 * This file is part of Waarp Project.
 * 
 * Copyright 2009, Frederic Bregier, and individual contributors by the @author tags. See the
 * COPYRIGHT.txt in the distribution for a full listing of individual contributors.
 * 
 * All Waarp Project 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, or (at your option) any later version.
 * 
 * Waarp 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 Waarp . If not, see
 * <http://www.gnu.org/licenses/>.
 */
package org.waarp.openr66.protocol.networkhandler;

import static org.waarp.openr66.context.R66FiniteDualStates.AUTHENTR;

import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelPipelineException;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.local.LocalChannel;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import io.netty.util.internal.ConcurrentSet;

import org.waarp.common.crypto.ssl.WaarpSslUtility;
import org.waarp.common.database.DbAdmin;
import org.waarp.common.digest.FilesystemBasedDigest;
import org.waarp.common.future.WaarpLock;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;
import org.waarp.common.lru.SynchronizedLruCache;
import org.waarp.common.utility.WaarpNettyUtil;
import org.waarp.common.utility.WaarpThreadFactory;
import org.waarp.openr66.context.ErrorCode;
import org.waarp.openr66.context.R66Result;
import org.waarp.openr66.context.R66Session;
import org.waarp.openr66.context.task.exception.OpenR66RunnerErrorException;
import org.waarp.openr66.database.data.DbHostAuth;
import org.waarp.openr66.protocol.configuration.Configuration;
import org.waarp.openr66.protocol.exception.OpenR66Exception;
import org.waarp.openr66.protocol.exception.OpenR66ProtocolNetworkException;
import org.waarp.openr66.protocol.exception.OpenR66ProtocolNoConnectionException;
import org.waarp.openr66.protocol.exception.OpenR66ProtocolNoDataException;
import org.waarp.openr66.protocol.exception.OpenR66ProtocolNoSslException;
import org.waarp.openr66.protocol.exception.OpenR66ProtocolPacketException;
import org.waarp.openr66.protocol.exception.OpenR66ProtocolRemoteShutdownException;
import org.waarp.openr66.protocol.exception.OpenR66ProtocolSystemException;
import org.waarp.openr66.protocol.localhandler.LocalChannelReference;
import org.waarp.openr66.protocol.localhandler.RetrieveRunner;
import org.waarp.openr66.protocol.localhandler.packet.AuthentPacket;
import org.waarp.openr66.protocol.localhandler.packet.ConnectionErrorPacket;
import org.waarp.openr66.protocol.networkhandler.packet.NetworkPacket;
import org.waarp.openr66.protocol.networkhandler.ssl.NetworkSslServerHandler;
import org.waarp.openr66.protocol.networkhandler.ssl.NetworkSslServerInitializer;
import org.waarp.openr66.protocol.utils.ChannelUtils;
import org.waarp.openr66.protocol.utils.R66Future;
import org.waarp.openr66.protocol.utils.R66ShutdownHook;

/**
 * This class handles Network Transaction connections
 * 
 * @author frederic bregier
 */
public class NetworkTransaction {
    /**
     * Internal Logger
     */
    private static final WaarpLogger logger = WaarpLoggerFactory.getLogger(NetworkTransaction.class);

    /**
     * To protect access to socketLocks when no address associated
     */
    private static final WaarpLock emptyLock = new WaarpLock();
    /**
     * Lock for Lock management operations
     */
    private static final ReentrantLock lockOfLock = new ReentrantLock();
    /**
     * Hashmap for lock based on remote address
     */
    private static final SynchronizedLruCache<Integer, WaarpLock> reentrantLockOnSocketAddressConcurrentHashMap = new SynchronizedLruCache<Integer, WaarpLock>(
            20000, 180000);
    /**
     * Hashmap for Currently Shutdown remote host based on socketAddress.hashCode()
     */
    private static final ConcurrentHashMap<Integer, NetworkChannelReference> networkChannelShutdownOnSocketAddressConcurrentHashMap = new ConcurrentHashMap<Integer, NetworkChannelReference>();
    /**
     * Hashmap for Currently blacklisted remote host based on IP address(String).hashCode()
     */
    private static final ConcurrentHashMap<Integer, NetworkChannelReference> networkChannelBlacklistedOnInetSocketAddressConcurrentHashMap = new ConcurrentHashMap<Integer, NetworkChannelReference>();

    /**
     * Hashmap for currently active remote host based on socketAddress.hashCode()
     */
    private static final ConcurrentHashMap<Integer, NetworkChannelReference> networkChannelOnSocketAddressConcurrentHashMap = new ConcurrentHashMap<Integer, NetworkChannelReference>();
    /**
     * Remote Client NetworkChannels: used to centralize remote requester hosts (possible different address used)
     */
    private static final ConcurrentHashMap<String, ClientNetworkChannels> clientNetworkChannelsPerHostId = new ConcurrentHashMap<String, ClientNetworkChannels>();
    /**
     * Hashmap for currently active Retrieve Runner (sender)
     */
    private static final ConcurrentHashMap<Integer, RetrieveRunner> retrieveRunnerConcurrentHashMap = new ConcurrentHashMap<Integer, RetrieveRunner>();

    /**
     * ExecutorService for RetrieveOperation
     */
    private static final ExecutorService retrieveExecutor = Executors
            .newCachedThreadPool(new WaarpThreadFactory("RetrieveExecutor"));

    private final Bootstrap clientBootstrap;
    private final Bootstrap clientSslBootstrap;
    private final ChannelGroup networkChannelGroup;

    public NetworkTransaction() {
        networkChannelGroup = new DefaultChannelGroup("NetworkChannels",
                Configuration.configuration.getSubTaskGroup().next());
        NetworkServerInitializer networkServerInitializer = new NetworkServerInitializer(false);
        clientBootstrap = new Bootstrap();
        WaarpNettyUtil.setBootstrap(clientBootstrap, Configuration.configuration.getNetworkWorkerGroup(),
                (int) Configuration.configuration.getTIMEOUTCON());
        clientBootstrap.handler(networkServerInitializer);
        clientSslBootstrap = new Bootstrap();
        if (Configuration.configuration.isUseSSL() && Configuration.configuration.getHOST_SSLID() != null) {
            NetworkSslServerInitializer networkSslServerInitializer = new NetworkSslServerInitializer(true);
            WaarpNettyUtil.setBootstrap(clientSslBootstrap, Configuration.configuration.getNetworkWorkerGroup(),
                    (int) Configuration.configuration.getTIMEOUTCON());
            clientSslBootstrap.handler(networkSslServerInitializer);
        } else {
            if (Configuration.configuration.isWarnOnStartup()) {
                logger.warn("No SSL support configured");
            } else {
                logger.info("No SSL support configured");
            }
        }
    }

    public static String hashStatus() {
        String partial = "NetworkTransaction: [InShutdown: "
                + networkChannelShutdownOnSocketAddressConcurrentHashMap.size() + " Blacklisted: "
                + networkChannelBlacklistedOnInetSocketAddressConcurrentHashMap.size() + "\n RetrieveRunner: "
                + retrieveRunnerConcurrentHashMap.size() + " ClientNetworkChannels: "
                + clientNetworkChannelsPerHostId.size();
        int nb = 0;
        for (ClientNetworkChannels clientNetworkChannels : clientNetworkChannelsPerHostId.values()) {
            nb += clientNetworkChannels.size();
        }
        partial += " Sum of ClientNetworkChannels NetworkClients: " + nb;
        nb = 0;
        for (NetworkChannelReference ncr : networkChannelOnSocketAddressConcurrentHashMap.values()) {
            nb += ncr.nbLocalChannels();
        }
        partial += "\n NetworkChannels: " + networkChannelOnSocketAddressConcurrentHashMap.size()
                + " LockOnSocketAddress: " + reentrantLockOnSocketAddressConcurrentHashMap.size()
                + " Sum of NetworkChannels LocalClients: " + nb + "] ";
        return partial;
    }

    private static final WaarpLock getLockNCR(SocketAddress sa) {
        return reentrantLockOnSocketAddressConcurrentHashMap.get(sa.hashCode());
    }

    private static final void addLockNCR(SocketAddress sa, WaarpLock lock) {
        reentrantLockOnSocketAddressConcurrentHashMap.put(sa.hashCode(), lock);
    }

    private static final void addNCR(NetworkChannelReference ncr) {
        networkChannelOnSocketAddressConcurrentHashMap.put(ncr.getSocketHashCode(), ncr);
    }

    private static final NetworkChannelReference removeNCR(NetworkChannelReference ncr) {
        return networkChannelOnSocketAddressConcurrentHashMap.remove(ncr.getSocketHashCode());
    }

    private static final NetworkChannelReference getNCR(SocketAddress sa) {
        return networkChannelOnSocketAddressConcurrentHashMap.get(sa.hashCode());
    }

    private static final boolean containsNCR(SocketAddress address) {
        return networkChannelOnSocketAddressConcurrentHashMap.containsKey(address.hashCode());
    }

    private static final void addShutdownNCR(NetworkChannelReference ncr) {
        networkChannelShutdownOnSocketAddressConcurrentHashMap.put(ncr.getSocketHashCode(), ncr);
    }

    private static final NetworkChannelReference removeShutdownNCR(NetworkChannelReference ncr) {
        return networkChannelShutdownOnSocketAddressConcurrentHashMap.remove(ncr.getSocketHashCode());
    }

    private static final boolean containsShutdownNCR(NetworkChannelReference ncr) {
        return networkChannelShutdownOnSocketAddressConcurrentHashMap.containsKey(ncr.getSocketHashCode());
    }

    private static final boolean containsShutdownNCR(SocketAddress sa) {
        return networkChannelShutdownOnSocketAddressConcurrentHashMap.containsKey(sa.hashCode());
    }

    private static final NetworkChannelReference getShutdownNCR(SocketAddress sa) {
        return networkChannelShutdownOnSocketAddressConcurrentHashMap.get(sa.hashCode());
    }

    private static final void addBlacklistNCR(NetworkChannelReference ncr) {
        networkChannelBlacklistedOnInetSocketAddressConcurrentHashMap.put(ncr.getAddressHashCode(), ncr);
    }

    private static final NetworkChannelReference removeBlacklistNCR(NetworkChannelReference ncr) {
        return networkChannelBlacklistedOnInetSocketAddressConcurrentHashMap.remove(ncr.getAddressHashCode());
    }

    private static final boolean containsBlacklistNCR(NetworkChannelReference ncr) {
        return networkChannelBlacklistedOnInetSocketAddressConcurrentHashMap.containsKey(ncr.getAddressHashCode());
    }

    private static final boolean containsBlacklistNCR(SocketAddress address) {
        return networkChannelBlacklistedOnInetSocketAddressConcurrentHashMap.containsKey(address.hashCode());
    }

    private static final NetworkChannelReference getBlacklistNCR(SocketAddress sa) {
        InetAddress address = ((InetSocketAddress) sa).getAddress();
        if (address == null) {
            return null;
        }

        return networkChannelBlacklistedOnInetSocketAddressConcurrentHashMap
                .get(address.getHostAddress().hashCode());
    }

    private static final WaarpLock getChannelLock(SocketAddress socketAddress) {
        lockOfLock.lock();
        try {
            if (socketAddress == null) {
                // should not
                logger.info("SocketAddress empty here !");
                return emptyLock;
            }
            WaarpLock socketLock = getLockNCR(socketAddress);
            if (socketLock == null) {
                socketLock = new WaarpLock(true);
            }
            // update TTL
            addLockNCR(socketAddress, socketLock);
            return socketLock;
        } finally {
            lockOfLock.unlock();
        }
    }

    private static void removeChannelLock() {
        lockOfLock.lock();
        try {
            reentrantLockOnSocketAddressConcurrentHashMap.forceClearOldest();
        } finally {
            lockOfLock.unlock();
        }
    }

    /**
     * Create a connection to the specified socketAddress with multiple retries
     * 
     * @param socketAddress
     * @param isSSL
     * @param futureRequest
     * @return the LocalChannelReference
     */
    public LocalChannelReference createConnectionWithRetry(SocketAddress socketAddress, boolean isSSL,
            R66Future futureRequest) {
        LocalChannelReference localChannelReference = null;
        OpenR66Exception lastException = null;
        for (int i = 0; i < Configuration.RETRYNB; i++) {
            if (R66ShutdownHook.isShutdownStarting()) {
                lastException = new OpenR66ProtocolSystemException("Local system in shutdown");
                break;
            }
            try {
                localChannelReference = createConnection(socketAddress, isSSL, futureRequest);
                break;
            } catch (OpenR66ProtocolRemoteShutdownException e1) {
                lastException = e1;
                localChannelReference = null;
                break;
            } catch (OpenR66ProtocolNoConnectionException e1) {
                lastException = e1;
                localChannelReference = null;
                break;
            } catch (OpenR66ProtocolNetworkException e1) {
                // Can retry
                lastException = e1;
                localChannelReference = null;
                try {
                    Thread.sleep(Configuration.configuration.getDelayRetry());
                } catch (InterruptedException e) {
                    break;
                }
            }
        }
        if (localChannelReference == null) {
            logger.debug("Cannot connect : {}", lastException.getMessage());
        } else if (lastException != null) {
            logger.debug("Connection retried since {}", lastException.getMessage());
        }
        return localChannelReference;
    }

    /**
     * Create a connection to the specified socketAddress
     * 
     * @param socketAddress
     * @param isSSL
     * @param futureRequest
     * @return the LocalChannelReference
     * @throws OpenR66ProtocolNetworkException
     * @throws OpenR66ProtocolRemoteShutdownException
     * @throws OpenR66ProtocolNoConnectionException
     */
    private LocalChannelReference createConnection(SocketAddress socketAddress, boolean isSSL,
            R66Future futureRequest) throws OpenR66ProtocolNetworkException, OpenR66ProtocolRemoteShutdownException,
            OpenR66ProtocolNoConnectionException {
        NetworkChannelReference networkChannelReference = null;
        LocalChannelReference localChannelReference = null;
        boolean ok = false;
        // check valid limit on server side only (could be the initiator but not a client)
        DbHostAuth auth = isSSL ? Configuration.configuration.getHOST_SSLAUTH()
                : Configuration.configuration.getHOST_AUTH();
        if (!auth.isClient()) {
            boolean valid = false;
            for (int i = 0; i < Configuration.RETRYNB * 2; i++) {
                if (Configuration.configuration.getConstraintLimitHandler().checkConstraintsSleep(i)) {
                    logger.debug("Constraints exceeded: " + i);
                } else {
                    logger.debug("Constraints NOT exceeded");
                    valid = true;
                    break;
                }
            }
            if (!valid) {
                // Limit is locally exceeded
                logger.debug("Overloaded local system");
                throw new OpenR66ProtocolNetworkException("Cannot connect to remote server due to local overload");
            }
        }
        try {
            networkChannelReference = createNewConnection(socketAddress, isSSL);
            try {
                localChannelReference = Configuration.configuration.getLocalTransaction()
                        .createNewClient(networkChannelReference, ChannelUtils.NOCHANNEL, futureRequest);
            } catch (OpenR66ProtocolSystemException e) {
                throw new OpenR66ProtocolNetworkException("Cannot connect to local channel", e);
            } catch (NullPointerException e) {
                throw new OpenR66ProtocolNetworkException("Cannot connect to local channel", e);
            }
            ok = true;
        } finally {
            if (!ok) {
                if (networkChannelReference != null) {
                    checkClosingNetworkChannel(networkChannelReference, null);
                }
            }
        }
        if (localChannelReference.getFutureValidateStartup().isDone()
                && localChannelReference.getFutureValidateStartup().isSuccess()) {
            sendValidationConnection(localChannelReference);
        } else {
            OpenR66ProtocolNetworkException exc = new OpenR66ProtocolNetworkException("Startup is invalid");
            logger.debug("Startup is Invalid", exc);
            R66Result finalValue = new R66Result(exc, null, true, ErrorCode.ConnectionImpossible, null);
            localChannelReference.invalidateRequest(finalValue);
            localChannelReference.getLocalChannel().close();
            throw exc;
        }
        return localChannelReference;
    }

    /**
     * 
     * @param socketServerAddress
     * @param isSSL
     * @return the NetworkChannelReference
     * @throws OpenR66ProtocolNetworkException
     * @throws OpenR66ProtocolRemoteShutdownException
     * @throws OpenR66ProtocolNoConnectionException
     */
    private NetworkChannelReference createNewConnection(SocketAddress socketServerAddress, boolean isSSL)
            throws OpenR66ProtocolNetworkException, OpenR66ProtocolRemoteShutdownException,
            OpenR66ProtocolNoConnectionException {
        WaarpLock socketLock = getChannelLock(socketServerAddress);
        NetworkChannelReference networkChannelReference;
        socketLock.lock();
        try {
            try {
                networkChannelReference = getRemoteChannel(socketServerAddress);
            } catch (OpenR66ProtocolNoDataException e1) {
                networkChannelReference = null;
            }
            if (networkChannelReference != null) {
                networkChannelReference.use();
                logger.info("Already Connected: {}", networkChannelReference);
                return networkChannelReference;
            }
            logger.debug("NEW PHYSICAL CONNECTION REQUIRED");
            ChannelFuture channelFuture = null;
            for (int i = 0; i < Configuration.RETRYNB; i++) {
                if (R66ShutdownHook.isShutdownStarting()) {
                    throw new OpenR66ProtocolNoConnectionException("Local system in shutdown");
                }
                try {
                    if (isSSL) {
                        if (Configuration.configuration.getHOST_SSLID() != null) {
                            channelFuture = clientSslBootstrap.connect(socketServerAddress);
                        } else {
                            throw new OpenR66ProtocolNoConnectionException("No SSL support");
                        }
                    } else {
                        channelFuture = clientBootstrap.connect(socketServerAddress);
                    }
                } catch (ChannelPipelineException e) {
                    throw new OpenR66ProtocolNoConnectionException(
                            "Cannot connect to remote server due to a channel exception");
                }
                try {
                    channelFuture.await(Configuration.configuration.getTIMEOUTCON() / 3);
                } catch (InterruptedException e1) {
                }
                if (channelFuture.isSuccess()) {
                    final Channel channel = channelFuture.channel();
                    if (isSSL) {
                        if (!NetworkSslServerHandler.isSslConnectedChannel(channel)) {
                            logger.debug("KO CONNECT since SSL handshake is over");
                            channel.close();
                            throw new OpenR66ProtocolNoConnectionException(
                                    "Cannot finish connect to remote server");
                        }
                    }
                    networkChannelGroup.add(channel);
                    networkChannelReference = new NetworkChannelReference(channel, socketLock);
                    addNCR(networkChannelReference);
                    return networkChannelReference;
                } else {
                    try {
                        Thread.sleep(Configuration.RETRYINMS);
                    } catch (InterruptedException e) {
                    }
                    if (!channelFuture.isDone()) {
                        throw new OpenR66ProtocolNoConnectionException(
                                "Cannot connect to remote server due to interruption");
                    }
                    if (channelFuture.cause() instanceof ConnectException) {
                        logger.debug("KO CONNECT:" + channelFuture.cause().getMessage());
                        throw new OpenR66ProtocolNoConnectionException("Cannot connect to remote server",
                                channelFuture.cause());
                    } else {
                        logger.debug("KO CONNECT but retry", channelFuture.cause());
                    }
                }
            }
            throw new OpenR66ProtocolNetworkException("Cannot connect to remote server", channelFuture.cause());
        } finally {
            socketLock.unlock();
        }
    }

    /**
     * Create the LocalChannelReference when a remote local channel starts its connection
     * 
     * @param networkChannelReference
     * @param packet
     */
    public static void createConnectionFromNetworkChannelStartup(NetworkChannelReference networkChannelReference,
            NetworkPacket packet) {
        CreateConnectionFromNetworkChannel ccfnc = new CreateConnectionFromNetworkChannel(networkChannelReference,
                packet);
        ccfnc.setDaemon(true);
        Configuration.configuration.getExecutorService().execute(ccfnc);
    }

    private static class CreateConnectionFromNetworkChannel extends Thread {
        final NetworkChannelReference networkChannelReference;
        final NetworkPacket startupPacket;

        private CreateConnectionFromNetworkChannel(NetworkChannelReference networkChannelReference,
                NetworkPacket packet) {
            this.networkChannelReference = networkChannelReference;
            this.startupPacket = packet;
        }

        @Override
        public void run() {
            setName("Connect_" + startupPacket.getRemoteId());
            Channel channel = networkChannelReference.channel();
            LocalChannelReference lcr = null;
            try {
                lcr = Configuration.configuration.getLocalTransaction().createNewClient(networkChannelReference,
                        startupPacket.getRemoteId(), null);
            } catch (OpenR66ProtocolSystemException e1) {
                logger.error("Cannot create LocalChannel for: " + startupPacket + " due to " + e1.getMessage());
                final ConnectionErrorPacket error = new ConnectionErrorPacket(
                        "Cannot connect to localChannel since cannot create it", null);
                NetworkServerHandler.writeError(channel, startupPacket.getRemoteId(), startupPacket.getLocalId(),
                        error);
                NetworkTransaction.checkClosingNetworkChannel(this.networkChannelReference, null);
                startupPacket.clear();
                return;
            } catch (OpenR66ProtocolRemoteShutdownException e1) {
                logger.info("Will Close Local from Network Channel");
                WaarpSslUtility.closingSslChannel(channel);
                startupPacket.clear();
                return;
            } catch (OpenR66ProtocolNoConnectionException e1) {
                logger.error("Cannot create LocalChannel for: " + startupPacket + " due to " + e1.getMessage());
                final ConnectionErrorPacket error = new ConnectionErrorPacket(
                        "Cannot connect to localChannel since cannot create it", null);
                NetworkServerHandler.writeError(channel, startupPacket.getRemoteId(), startupPacket.getLocalId(),
                        error);
                NetworkTransaction.checkClosingNetworkChannel(this.networkChannelReference, null);
                startupPacket.clear();
                return;
            }
            ByteBuf buf = startupPacket.getBuffer();
            lcr.getLocalChannel().writeAndFlush(buf);
        }
    }

    /**
     * Send a validation of connection with Authentication
     * 
     * @param localChannelReference
     * @throws OpenR66ProtocolNetworkException
     * @throws OpenR66ProtocolRemoteShutdownException
     */
    private void sendValidationConnection(LocalChannelReference localChannelReference)
            throws OpenR66ProtocolNetworkException, OpenR66ProtocolRemoteShutdownException {
        AuthentPacket authent;

        try {
            DbHostAuth auth = localChannelReference.getNetworkServerHandler().isSsl()
                    ? Configuration.configuration.getHOST_SSLAUTH()
                    : Configuration.configuration.getHOST_AUTH();
            authent = new AuthentPacket(
                    Configuration.configuration.getHostId(localChannelReference.getNetworkServerHandler().isSsl()),
                    FilesystemBasedDigest.passwdCrypt(auth.getHostkey()), localChannelReference.getLocalId());
        } catch (OpenR66ProtocolNoSslException e1) {
            R66Result finalValue = new R66Result(new OpenR66ProtocolSystemException("No SSL support", e1),
                    localChannelReference.getSession(), true, ErrorCode.ConnectionImpossible, null);
            logger.error("Authent is Invalid due to no SSL: {}", e1.getMessage());
            if (localChannelReference.getRemoteId().compareTo(ChannelUtils.NOCHANNEL) == 0) {
                ConnectionErrorPacket error = new ConnectionErrorPacket(
                        "Cannot connect to localChannel since SSL is not supported", null);
                try {
                    ChannelUtils.writeAbstractLocalPacket(localChannelReference, error, true);
                } catch (OpenR66ProtocolPacketException e) {
                }
            }
            localChannelReference.invalidateRequest(finalValue);
            localChannelReference.getLocalChannel().close();
            throw new OpenR66ProtocolNetworkException(e1);
        }
        logger.debug("Will send request of connection validation");
        localChannelReference.sessionNewState(AUTHENTR);
        try {
            ChannelUtils.writeAbstractLocalPacket(localChannelReference, authent, true);
        } catch (OpenR66ProtocolPacketException e) {
            R66Result finalValue = new R66Result(new OpenR66ProtocolSystemException("Wrong Authent Protocol", e),
                    localChannelReference.getSession(), true, ErrorCode.ConnectionImpossible, null);
            logger.error("Authent is Invalid due to protocol: {}", e.getMessage());
            localChannelReference.invalidateRequest(finalValue);
            if (localChannelReference.getRemoteId() != ChannelUtils.NOCHANNEL) {
                ConnectionErrorPacket error = new ConnectionErrorPacket(
                        "Cannot connect to localChannel since Authent Protocol is invalid", null);
                try {
                    ChannelUtils.writeAbstractLocalPacket(localChannelReference, error, true);
                } catch (OpenR66ProtocolPacketException e1) {
                }
            }
            localChannelReference.getLocalChannel().close();
            throw new OpenR66ProtocolNetworkException("Bad packet", e);
        }
        R66Future future = localChannelReference.getFutureValidateConnection();
        if (future.isFailed()) {
            logger.debug("Will close NETWORK channel since Future cancelled: {}", future);
            R66Result finalValue = new R66Result(
                    new OpenR66ProtocolSystemException("Out of time or Connection invalid during Authentication"),
                    localChannelReference.getSession(), true, ErrorCode.ConnectionImpossible, null);
            logger.info("Authent is Invalid due to: {} {}", finalValue.getException().getMessage(),
                    future.toString());
            localChannelReference.invalidateRequest(finalValue);
            if (localChannelReference.getRemoteId() != ChannelUtils.NOCHANNEL) {
                ConnectionErrorPacket error = new ConnectionErrorPacket(
                        "Cannot connect to localChannel with Out of Time", null);
                try {
                    ChannelUtils.writeAbstractLocalPacket(localChannelReference, error, true);
                } catch (OpenR66ProtocolPacketException e) {
                }
            }
            localChannelReference.getLocalChannel().close().awaitUninterruptibly(Configuration.RETRYINMS * 2);
            throw new OpenR66ProtocolNetworkException("Cannot validate connection: " + future.getResult(),
                    future.getCause());
        }
    }

    /**
     * Add a new NetworkChannel from connection
     * 
     * @param channel
     * @throws OpenR66ProtocolRemoteShutdownException
     */
    public static NetworkChannelReference addNetworkChannel(Channel channel)
            throws OpenR66ProtocolRemoteShutdownException {
        SocketAddress socketAddress = channel.remoteAddress();
        WaarpLock socketLock = getChannelLock(socketAddress);
        //socketLock.lock(Configuration.WAITFORNETOP, TimeUnit.MILLISECONDS);
        socketLock.lock();
        try {
            NetworkChannelReference nc = null;
            try {
                nc = getRemoteChannel(socketAddress);
            } catch (OpenR66ProtocolNoDataException e1) {
            }
            if (nc == null) {
                // not an issue: needs to be created
                nc = new NetworkChannelReference(channel, socketLock);
                addNCR(nc);
            }
            return nc;
        } finally {
            socketLock.unlock();
        }
    }

    /**
     * To be called when a remote server seems to be down for a while, so to not retry immediately
     * 
     * @param socketAddress
     */
    public static void proposeShutdownNetworkChannel(SocketAddress socketAddress) {
        WaarpLock lock = getChannelLock(socketAddress);
        lock.lock(Configuration.WAITFORNETOP, TimeUnit.MILLISECONDS);
        try {
            logger.info("Seem Shutdown: {}", socketAddress);
            if (containsShutdownNCR(socketAddress)) {
                // already done
                logger.debug("Already set as shutdown");
                return;
            }
            if (containsBlacklistNCR(socketAddress)) {
                // already done
                logger.debug("Already set as blocked");
                return;
            }
            if (containsNCR(socketAddress)) {
                // already done
                logger.debug("Still existing so shutdown is refused");
                return;
            }
            logger.warn(
                    "This host address will be set as unavailable for 3xTIMEOUT since not reacheable multiple times: {}",
                    socketAddress);
            NetworkChannelReference networkChannelReference = new NetworkChannelReference(socketAddress, lock);
            addShutdownNCR(networkChannelReference);
            R66ShutdownNetworkChannelTimerTask timerTask;
            try {
                timerTask = new R66ShutdownNetworkChannelTimerTask(networkChannelReference, false);
                Configuration.configuration.getTimerClose().newTimeout(timerTask,
                        Configuration.configuration.getTIMEOUTCON() * 3, TimeUnit.MILLISECONDS);
            } catch (OpenR66RunnerErrorException e) {
                // ignore
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * Shutdown one Network Channel
     * 
     * @param networkChannelReference
     */
    private static void shuttingDownNetworkChannelInternal(NetworkChannelReference networkChannelReference) {
        logger.info("Shutdown: {}", networkChannelReference);
        if (containsShutdownNCR(networkChannelReference)) {
            // already done
            logger.debug("Already set as shutdown");
            return;
        }
        logger.debug("Set as shutdown");
        if (networkChannelReference != null) {
            addShutdownNCR(networkChannelReference);
            if (!networkChannelReference.isShuttingDown) {
                networkChannelReference.shutdownAllLocalChannels();
            }
            R66ShutdownNetworkChannelTimerTask timerTask;
            try {
                timerTask = new R66ShutdownNetworkChannelTimerTask(networkChannelReference, false);
                Configuration.configuration.getTimerClose().newTimeout(timerTask,
                        Configuration.configuration.getTIMEOUTCON() * 3, TimeUnit.MILLISECONDS);
            } catch (OpenR66RunnerErrorException e) {
                // ignore
            }
        }
    }

    /**
     * Shutdown one Network Channel
     * 
     * @param networkChannelReference
     */
    public static void shuttingDownNetworkChannel(NetworkChannelReference networkChannelReference) {
        shuttingDownNetworkChannelInternal(networkChannelReference);
    }

    /**
     * Shutdown a NetworkChannel and add it to BlaclList
     * 
     * @param networkChannelReference
     * @return True if this channel is now blacklisted for a while
     */
    public static boolean shuttingDownNetworkChannelBlackList(NetworkChannelReference networkChannelReference) {
        shuttingDownNetworkChannelInternal(networkChannelReference);
        if (!Configuration.configuration.isBlacklistBadAuthent()) {
            return false;
        }
        if (containsBlacklistNCR(networkChannelReference)) {
            return false;
        }
        addBlacklistNCR(networkChannelReference);
        R66ShutdownNetworkChannelTimerTask timerTask;
        try {
            timerTask = new R66ShutdownNetworkChannelTimerTask(networkChannelReference, true);
            Configuration.configuration.getTimerClose().newTimeout(timerTask,
                    Configuration.configuration.getTIMEOUTCON() * 10, TimeUnit.MILLISECONDS);
        } catch (OpenR66RunnerErrorException e) {
            // ignore
        }
        return true;
    }

    /**
     * 
     * @param channel
     * @return True if this channel is blacklisted
     */
    public static boolean isBlacklisted(Channel channel) {
        if (!Configuration.configuration.isBlacklistBadAuthent()) {
            return false;
        }
        SocketAddress address = channel.remoteAddress();
        if (address == null) {
            return false;
        }

        NetworkChannelReference networkChannelReference = getBlacklistNCR(address);
        return (networkChannelReference != null);
    }

    /**
     * 
     * @param address
     * @return True if this address (associated channel) is currently in shutdown (or if this channel is not valid)
     */
    public static boolean isShuttingdownNetworkChannel(SocketAddress address) {
        return !isAddressValid(address);
    }

    /**
     * Shutdown NetworkChannelReference as client
     * 
     * @param requester
     * @return True if shutdown occurs
     */
    public static boolean shuttingdownNetworkChannelsPerHostID(String requester) {
        if (requester == null) {
            return false;
        }
        ClientNetworkChannels clientNetworkChannels = clientNetworkChannelsPerHostId.get(requester);
        logger.debug(
                "AddClient: shutdown previous exist? " + (clientNetworkChannels != null) + " for :" + requester);
        if (clientNetworkChannels != null) {
            return clientNetworkChannels.shutdownAllNetworkChannels();
        }
        return false;
    }

    /**
     * Add a requester channel
     * 
     * @param networkChannelReference
     * @param requester
     */
    public static void addClient(NetworkChannelReference networkChannelReference, String requester) {
        if (networkChannelReference != null && requester != null) {
            ClientNetworkChannels clientNetworkChannels = clientNetworkChannelsPerHostId.get(requester);
            if (clientNetworkChannels == null) {
                clientNetworkChannels = new ClientNetworkChannels(requester);
                clientNetworkChannelsPerHostId.put(requester, clientNetworkChannels);
            }
            clientNetworkChannels.add(networkChannelReference);
            logger.debug("AddClient: add count? " + clientNetworkChannels.size() + " for " + requester);
        }
    }

    private static void removeClient(NetworkChannelReference networkChannelReference, String requester,
            ClientNetworkChannels clientNetworkChannels) {
        if (networkChannelReference != null && clientNetworkChannels != null && requester != null) {
            clientNetworkChannels.remove(networkChannelReference);
            logger.debug("removeClient: remove for :" + requester + " still " + clientNetworkChannels.size());
            if (clientNetworkChannels.isEmpty()) {
                clientNetworkChannelsPerHostId.remove(requester);
            }
        }
    }

    /**
     * 
     * @param requester
     * @return The number of NetworkChannels associated with this requester
     */
    public static int getNumberClients(String requester) {
        ClientNetworkChannels clientNetworkChannels = clientNetworkChannelsPerHostId.get(requester);
        if (clientNetworkChannels != null) {
            return clientNetworkChannels.size();
        }
        return 0;
    }

    /**
     * Force remove of NetworkChannelReference when it is closed
     * 
     * @param networkChannelReference
     */
    public static void closedNetworkChannel(NetworkChannelReference networkChannelReference) {
        if (networkChannelReference == null) {
            return;
        }
        try {
            if (!networkChannelReference.isShuttingDown) {
                networkChannelReference.shutdownAllLocalChannels();
            }
            logger.debug("NC left: {}", networkChannelReference);
            removeNCR(networkChannelReference);
            if (networkChannelReference.clientNetworkChannels != null) {
                String requester = networkChannelReference.clientNetworkChannels.getHostId();
                removeClient(networkChannelReference, requester, networkChannelReference.clientNetworkChannels);
            } else if (networkChannelReference.getHostId() != null) {
                String requester = networkChannelReference.getHostId();
                ClientNetworkChannels clientNetworkChannels = clientNetworkChannelsPerHostId.get(requester);
                if (clientNetworkChannels != null) {
                    removeClient(networkChannelReference, requester, clientNetworkChannels);
                }
            }
        } finally {
            removeChannelLock();
        }
    }

    /**
     * Force remove of NetworkChannelReference when it is closed
     * 
     * @param address
     */
    public static void closedNetworkChannel(SocketAddress address) {
        if (address == null) {
            return;
        }
        NetworkChannelReference networkChannelReference = networkChannelOnSocketAddressConcurrentHashMap
                .get(address.hashCode());
        closedNetworkChannel(networkChannelReference);
    }

    /**
     * Class to close the Network Channel if after some delays it has really no Local Channel
     * attached
     * 
     * @author Frederic Bregier
     * 
     */
    private static class CloseFutureChannel implements TimerTask {

        private static final ConcurrentSet<ChannelId> inCloseRunning = new ConcurrentSet<ChannelId>();
        private final NetworkChannelReference networkChannelReference;

        /**
         * @param networkChannelReference
         * @param requester
         * @throws OpenR66RunnerErrorException
         */
        private CloseFutureChannel(NetworkChannelReference networkChannelReference)
                throws OpenR66RunnerErrorException {
            if (!inCloseRunning.add(networkChannelReference.channel.id()))
                throw new OpenR66RunnerErrorException("Already scheduled");
            this.networkChannelReference = networkChannelReference;
        }

        public void run(Timeout timeout) throws Exception {
            networkChannelReference.lock.lock(Configuration.WAITFORNETOP, TimeUnit.MILLISECONDS);
            try {
                logger.debug("NC count: {}", networkChannelReference);
                if (networkChannelReference.nbLocalChannels() <= 0) {
                    long time = networkChannelReference
                            .checkLastTime(Configuration.configuration.getTIMEOUTCON() * 2);
                    if (time > Configuration.RETRYINMS) {
                        logger.debug("NC reschedule at " + time + " : {}", networkChannelReference);
                        // will re execute this request later on
                        time = (time / 10) * 10 + 100; // round to 10
                        Configuration.configuration.getTimerClose().newTimeout(this, time, TimeUnit.MILLISECONDS);
                        return;
                    }
                    logger.info("Closing NETWORK channel {}", networkChannelReference);
                    networkChannelReference.isShuttingDown = true;
                    WaarpSslUtility.closingSslChannel(networkChannelReference.channel);
                }
                inCloseRunning.remove(networkChannelReference.channel.id());
            } finally {
                networkChannelReference.lock.unlock();
            }
        }

    }

    /**
     * Check if closing of the localChannel will bring future closing of NetworkChannel
     * 
     * @param networkChannelReference
     * @param localChannelReference
     * @return the number of local channel still connected to this channel
     */
    public static int checkClosingNetworkChannel(NetworkChannelReference networkChannelReference,
            LocalChannelReference localChannelReference) {
        networkChannelReference.lock.lock(Configuration.WAITFORNETOP, TimeUnit.MILLISECONDS);
        try {
            logger.debug("Close con: " + networkChannelReference);
            if (localChannelReference != null) {
                networkChannelReference.remove(localChannelReference);
            }
            int count = networkChannelReference.nbLocalChannels();
            if (count <= 0) {
                CloseFutureChannel cfc;
                try {
                    cfc = new CloseFutureChannel(networkChannelReference);
                    Configuration.configuration.getTimerClose().newTimeout(cfc,
                            Configuration.configuration.getTIMEOUTCON() * 2, TimeUnit.MILLISECONDS);
                } catch (OpenR66RunnerErrorException e) {
                } catch (IllegalStateException e) {
                }
            }
            logger.debug("NC left: {}", networkChannelReference);
            return count;
        } finally {
            networkChannelReference.lock.unlock();
        }
    }

    /**
     * 
     * @param address
     * @param host
     * @return a number > 0 if a connection is still active on this socket or for this host
     */
    public static int nbAttachedConnection(SocketAddress address, String host) {
        logger.debug("nbAttachedConnection: "
                + networkChannelOnSocketAddressConcurrentHashMap.containsKey(address.hashCode()) + ":"
                + getNumberClients(host));
        return (networkChannelOnSocketAddressConcurrentHashMap.containsKey(address.hashCode()) ? 1 : 0)
                + getNumberClients(host);
    }

    /**
     * 
     * @param address
     * @return True if this socket Address is currently valid for connection
     */
    private static boolean isAddressValid(SocketAddress address) {
        if (R66ShutdownHook.isShutdownStarting()) {
            logger.debug("IS IN SHUTDOWN");
            return false;
        }
        if (address == null) {
            logger.debug("ADDRESS IS NULL");
            return false;
        }
        try {
            NetworkChannelReference networkChannelReference = getRemoteChannel(address);
            logger.debug("IS IN SHUTDOWN: " + networkChannelReference.isShuttingDown);
            return !networkChannelReference.isShuttingDown;
        } catch (OpenR66ProtocolRemoteShutdownException e) {
            logger.debug("ALREADY IN SHUTDOWN");
            return false;
        } catch (OpenR66ProtocolNoDataException e) {
            logger.debug("NOT FOUND SO NO SHUTDOWN");
            return true;
        }
    }

    /**
     * Returns the NetworkChannelReference if it exists associated with this address
     * 
     * @param address
     * @return NetworkChannelReference
     * @throws OpenR66ProtocolRemoteShutdownException
     * @throws OpenR66ProtocolNoDataException
     */
    private static NetworkChannelReference getRemoteChannel(SocketAddress address)
            throws OpenR66ProtocolRemoteShutdownException, OpenR66ProtocolNoDataException {
        if (R66ShutdownHook.isShutdownStarting()) {
            logger.debug("IS IN SHUTDOWN");
            throw new OpenR66ProtocolRemoteShutdownException("Local Host already in shutdown");
        }
        if (address == null) {
            logger.debug("ADDRESS IS NULL");
            throw new OpenR66ProtocolRemoteShutdownException(
                    "Cannot connect to remote server since address is not specified");
        }
        NetworkChannelReference nc = getShutdownNCR(address);
        if (nc != null) {
            logger.debug("HOST STILL IN SHUTDOWN STATUS: {}", address);
            throw new OpenR66ProtocolRemoteShutdownException("Remote Host already in shutdown");
        }
        nc = getBlacklistNCR(address);
        if (nc != null) {
            logger.debug("HOST IN BLACKLISTED STATUS: {}", address);
            throw new OpenR66ProtocolRemoteShutdownException("Remote Host is blacklisted");
        }
        nc = getNCR(address);
        if (nc != null && (nc.isShuttingDown || !nc.channel().isActive())) {
            logger.debug("HOST IS DisActive: {}", address);
            throw new OpenR66ProtocolRemoteShutdownException("Remote Host is disActive");
        }
        if (nc == null) {
            throw new OpenR66ProtocolNoDataException("Channel not found");
        }
        return nc;
    }

    /**
     * 
     * @param channel
     * @return the associated NetworkChannelReference immediately (if known)
     */
    public static final NetworkChannelReference getImmediateNetworkChannel(Channel channel) {
        if (channel.remoteAddress() != null) {
            return getNCR(channel.remoteAddress());
        }
        return null;
    }

    /**
     * Remover of Shutdown Remote Host
     * 
     * @author Frederic Bregier
     * 
     */
    private static class R66ShutdownNetworkChannelTimerTask implements TimerTask {
        private static final ConcurrentSet<ChannelId> inShutdownRunning = new ConcurrentSet<ChannelId>();
        private static final ConcurrentSet<ChannelId> inBlacklistedRunning = new ConcurrentSet<ChannelId>();
        /**
         * NCR to remove
         */
        private final NetworkChannelReference ncr;
        private final boolean isBlacklisted;

        /**
         * Constructor from type
         * 
         * @param href
         * @throws OpenR66RunnerErrorException
         */
        public R66ShutdownNetworkChannelTimerTask(NetworkChannelReference ncr, boolean blackListed)
                throws OpenR66RunnerErrorException {
            super();
            if (blackListed) {
                if (!inBlacklistedRunning.add(ncr.channel.id())) {
                    throw new OpenR66RunnerErrorException("Already scheduled");
                }
            } else {
                if (ncr.channel != null && !inShutdownRunning.add(ncr.channel.id())) {
                    throw new OpenR66RunnerErrorException("Already scheduled");
                }
            }
            this.ncr = ncr;
            this.isBlacklisted = blackListed;
        }

        public void run(Timeout timeout) throws Exception {
            if (isBlacklisted) {
                logger.debug("Will remove Blacklisted for : {}", ncr);
                removeBlacklistNCR(ncr);
                inBlacklistedRunning.remove(ncr.channel.id());
                return;
            }
            logger.debug("Will remove Shutdown for : {}", ncr);
            if (ncr.channel != null && ncr.channel.isActive()) {
                WaarpSslUtility.closingSslChannel(ncr.channel);
            }
            removeShutdownNCR(ncr);
            if (ncr.channel != null) {
                inShutdownRunning.remove(ncr.channel.id());
            }
        }
    }

    public static ExecutorService getRetrieveExecutor() {
        return retrieveExecutor;
    }

    public static ConcurrentHashMap<Integer, RetrieveRunner> getRetrieveRunnerConcurrentHashMap() {
        return retrieveRunnerConcurrentHashMap;
    }

    /**
     * Start retrieve operation
     * 
     * @param session
     * @param channel
     */
    public static void runRetrieve(R66Session session, LocalChannel channel) {
        RetrieveRunner retrieveRunner = new RetrieveRunner(session, channel);
        retrieveRunnerConcurrentHashMap.put(session.getLocalChannelReference().getLocalId(), retrieveRunner);
        retrieveRunner.setDaemon(true);
        Configuration.configuration.getLocalWorkerGroup().execute(retrieveRunner);
    }

    /**
     * Stop a retrieve operation
     * 
     * @param localChannelReference
     */
    public static void stopRetrieve(LocalChannelReference localChannelReference) {
        RetrieveRunner retrieveRunner = retrieveRunnerConcurrentHashMap.remove(localChannelReference.getLocalId());
        if (retrieveRunner != null) {
            retrieveRunner.stopRunner();
        }
    }

    /**
     * Normal end of a Retrieve Operation
     * 
     * @param localChannelReference
     */
    public static void normalEndRetrieve(LocalChannelReference localChannelReference) {
        retrieveRunnerConcurrentHashMap.remove(localChannelReference.getLocalId());
    }

    /**
     * Stop all Retrieve Executors
     */
    public static void closeRetrieveExecutors() {
        retrieveExecutor.shutdownNow();
    }

    /**
     * Close all Network Ttransaction
     */
    public void closeAll() {
        closeAll(true);
    }

    /**
     * Close all Network Ttransaction
     */
    public void closeAll(boolean quickShutdown) {
        logger.debug("close All Network Channels");
        if (!Configuration.configuration.isServer()) {
            R66ShutdownHook.shutdownHook.launchFinalExit();
        }
        closeRetrieveExecutors();
        networkChannelGroup.close();
        try {
            Thread.sleep(Configuration.WAITFORNETOP);
        } catch (InterruptedException e) {
        }
        DbAdmin.closeAllConnection();
        Configuration.configuration.clientStop(quickShutdown);
        if (!Configuration.configuration.isServer()) {
            logger.debug("Last action before exit");
            ChannelUtils.stopLogger();
        }
    }
}