/*
 * TeleStax, Open Source Cloud Communications
 * Copyright 2011-2014, Telestax Inc and individual contributors
 * by the @authors tag.
 *
 * This program is free software: you can redistribute it and/or modify
 * under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */
package org.mobicents.protocols.sctp.netty;

import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.concurrent.DefaultThreadFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;

import javolution.text.TextBuilder;
import javolution.util.FastList;
import javolution.util.FastMap;
import javolution.xml.XMLObjectReader;
import javolution.xml.XMLObjectWriter;
import javolution.xml.stream.XMLStreamException;

import org.apache.log4j.Logger;
import org.mobicents.protocols.api.Association;
import org.mobicents.protocols.api.AssociationType;
import org.mobicents.protocols.api.CongestionListener;
import org.mobicents.protocols.api.IpChannelType;
import org.mobicents.protocols.api.Management;
import org.mobicents.protocols.api.ManagementEventListener;
import org.mobicents.protocols.api.Server;
import org.mobicents.protocols.api.ServerListener;

import com.sun.nio.sctp.SctpStandardSocketOptions;
import com.sun.nio.sctp.SctpStandardSocketOptions.InitMaxStreams;

/**
 * @author <a href="mailto:[email protected]">Amit Bhayani</a>
 * 
 */
public class NettySctpManagementImpl implements Management {

    private static final Logger logger = Logger.getLogger(NettySctpManagementImpl.class);

    private static final String SCTP_PERSIST_DIR_KEY = "sctp.persist.dir";
    private static final String USER_DIR_KEY = "user.dir";
    private static final String PERSIST_FILE_NAME = "sctp.xml";

    private static final String SERVERS = "servers";
    private static final String ASSOCIATIONS = "associations";

    private static final String CONNECT_DELAY_PROP = "connectdelay";
    private static final String SINGLE_THREAD_PROP = "singlethread";
    private static final String WORKER_THREADS_PROP = "workerthreads";

    public static final String CONG_CONTROL_DELAY_THRESHOLD_1 = "congControl_DelayThreshold_1";
    public static final String CONG_CONTROL_DELAY_THRESHOLD_2 = "congControl_DelayThreshold_2";
    public static final String CONG_CONTROL_DELAY_THRESHOLD_3 = "congControl_DelayThreshold_3";
    public static final String CONG_CONTROL_BACK_TO_NORMAL_DELAY_THRESHOLD_1 = "congControl_BackToNormalDelayThreshold_1";
    public static final String CONG_CONTROL_BACK_TO_NORMAL_DELAY_THRESHOLD_2 = "congControl_BackToNormalDelayThreshold_2";
    public static final String CONG_CONTROL_BACK_TO_NORMAL_DELAY_THRESHOLD_3 = "congControl_BackToNormalDelayThreshold_3";

    // TODO: make options configurable in future
//    public static final String OPTION_SCTP_DISABLE_FRAGMENTS = "optionSctpDisableFragments";
//    public static final String OPTION_SCTP_FRAGMENT_INTERLEAVE = "optionSctpFragmentInterleave";
//    public static final String OPTION_SCTP_INIT_MAXSTREAMS_IN = "optionSctpInitMaxstreamsIn";
//    public static final String OPTION_SCTP_INIT_MAXSTREAMS_OUT = "optionSctpInitMaxstreamsOut";
//    public static final String OPTION_SCTP_NODELAY = "optionSctpNodelay";
//    public static final String OPTION_SO_SNDBUF = "optionSoSndbuf";
//    public static final String OPTION_SO_RCVBUF = "optionSoRcvbuf";
//    public static final String OPTION_SO_LINGER = "optionSoLinger";

    static final int DEFAULT_IO_THREADS = Runtime.getRuntime().availableProcessors() * 2;

    private final TextBuilder persistFile = TextBuilder.newInstance();

    protected static final NettySctpXMLBinding binding = new NettySctpXMLBinding();
    protected static final String TAB_INDENT = "\t";
    private static final String CLASS_ATTRIBUTE = "type";

    private final String name;

    protected String persistDir = null;
    private int connectDelay = 5000;

    protected double[] congControl_DelayThreshold = new double[] { 2.5, 8, 14 };
    protected double[] congControl_BackToNormalDelayThreshold = new double[] { 1.5, 5.5, 10 };

//    private int workerThreads = DEFAULT_IO_THREADS;
//    private boolean singleThread = true;

    // private NettyClientOpsThread nettyClientOpsThread = null;

    private ServerListener serverListener = null;

    private FastList<ManagementEventListener> managementEventListeners = new FastList<ManagementEventListener>();
    private FastList<CongestionListener> congestionListeners = new FastList<CongestionListener>();
    protected FastList<Server> servers = new FastList<Server>();
    protected NettyAssociationMap<String, Association> associations = new NettyAssociationMap<String, Association>();
    private volatile boolean started = false;

    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    private ScheduledExecutorService clientExecutor;

    // SctpStandardSocketOptions

    // SCTP option: Enables or disables message fragmentation.
    // If enabled no SCTP message fragmentation will be performed.
    // Instead if a message being sent exceeds the current PMTU size,
    // the message will NOT be sent and an error will be indicated to the user.
    private Boolean optionSctpDisableFragments = null;
    // SCTP option: Fragmented interleave controls how the presentation of messages occur for the message receiver.
    // There are three levels of fragment interleave defined
    // level 0 - Prevents the interleaving of any messages
    // level 1 - Allows interleaving of messages that are from different associations
    // level 2 - Allows complete interleaving of messages.
    private Integer optionSctpFragmentInterleave = null;
    // SCTP option: The maximum number of streams requested by the local endpoint during association initialization
    // For an SctpServerChannel this option determines the maximum number of inbound/outbound streams
    // accepted sockets will negotiate with their connecting peer.
    private Integer optionSctpInitMaxstreams_MaxOutStreams = null;
    private Integer optionSctpInitMaxstreams_MaxInStreams = null;
    // SCTP option: Enables or disables a Nagle-like algorithm.
    // The value of this socket option is a Boolean that represents whether the option is enabled or disabled.
    // SCTP uses an algorithm like The Nagle Algorithm to coalesce short segments and improve network efficiency.
    private Boolean optionSctpNodelay = true;
    // SCTP option: The size of the socket send buffer.
    private Integer optionSoSndbuf = null;
    // SCTP option: The size of the socket receive buffer.
    private Integer optionSoRcvbuf = null;
    // SCTP option: Linger on close if data is present.
    // The value of this socket option is an Integer that controls the action taken when unsent data is queued on the socket
    // and a method to close the socket is invoked.
    // If the value of the socket option is zero or greater, then it represents a timeout value, in seconds, known as the linger interval.
    // The linger interval is the timeout for the close method to block while the operating system attempts to transmit the unsent data
    // or it decides that it is unable to transmit the data.
    // If the value of the socket option is less than zero then the option is disabled.
    // In that case the close method does not wait until unsent data is transmitted;
    // if possible the operating system will transmit any unsent data before the connection is closed. 
    private Integer optionSoLinger = null;

    /**
	 * 
	 */
    public NettySctpManagementImpl(String name) throws IOException {
        this.name = name;
        binding.setClassAttribute(CLASS_ATTRIBUTE);
        binding.setAlias(NettyServerImpl.class, "server");
        binding.setAlias(NettyAssociationImpl.class, "association");
        binding.setAlias(String.class, "string");
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#getName()
     */
    @Override
    public String getName() {
        return this.name;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#getPersistDir()
     */
    @Override
    public String getPersistDir() {
        return this.persistDir;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#setPersistDir(java.lang.String)
     */
    @Override
    public void setPersistDir(String persistDir) {
        this.persistDir = persistDir;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#getServerListener()
     */
    @Override
    public ServerListener getServerListener() {
        return this.serverListener;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#setServerListener(org.mobicents .protocols.api.ServerListener)
     */
    @Override
    public void setServerListener(ServerListener serverListener) {
        this.serverListener = serverListener;
    }

    protected EventLoopGroup getBossGroup() {
        return bossGroup;
    }

    protected EventLoopGroup getWorkerGroup() {
        return workerGroup;
    }

    protected ScheduledExecutorService getClientExecutor() {
        return clientExecutor;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#addManagementEventListener(org
     * .mobicents.protocols.api.ManagementEventListener)
     */
    @Override
    public void addManagementEventListener(ManagementEventListener listener) {
        synchronized (this) {
            if (this.managementEventListeners.contains(listener))
                return;

            FastList<ManagementEventListener> newManagementEventListeners = new FastList<ManagementEventListener>();
            newManagementEventListeners.addAll(this.managementEventListeners);
            newManagementEventListeners.add(listener);
            this.managementEventListeners = newManagementEventListeners;
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#removeManagementEventListener(
     * org.mobicents.protocols.api.ManagementEventListener)
     */
    @Override
    public void removeManagementEventListener(ManagementEventListener listener) {
        synchronized (this) {
            if (!this.managementEventListeners.contains(listener))
                return;

            FastList<ManagementEventListener> newManagementEventListeners = new FastList<ManagementEventListener>();
            newManagementEventListeners.addAll(this.managementEventListeners);
            newManagementEventListeners.remove(listener);
            this.managementEventListeners = newManagementEventListeners;
        }
    }

    @Override
    public void addCongestionListener(CongestionListener listener) {
        synchronized (this) {
            if (this.congestionListeners.contains(listener))
                return;

            FastList<CongestionListener> newCongestionListeners = new FastList<CongestionListener>();
            newCongestionListeners.addAll(this.congestionListeners);
            newCongestionListeners.add(listener);
            this.congestionListeners = newCongestionListeners;
        }
    }

    @Override
    public void removeCongestionListener(CongestionListener listener) {
        synchronized (this) {
            if (!this.congestionListeners.contains(listener))
                return;

            FastList<CongestionListener> newCongestionListeners = new FastList<CongestionListener>();
            newCongestionListeners.addAll(this.congestionListeners);
            newCongestionListeners.remove(listener);
            this.congestionListeners = newCongestionListeners;
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#start()
     */
    @Override
    public void start() throws Exception {
        if (this.started) {
            logger.warn(String.format("management=%s is already started", this.name));
            return;
        }

        synchronized (this) {
            this.persistFile.clear();

            if (persistDir != null) {
                this.persistFile.append(persistDir).append(File.separator).append(this.name).append("_")
                        .append(PERSIST_FILE_NAME);
            } else {
                persistFile.append(System.getProperty(SCTP_PERSIST_DIR_KEY, System.getProperty(USER_DIR_KEY)))
                        .append(File.separator).append(this.name).append("_").append(PERSIST_FILE_NAME);
            }

            logger.info(String.format("SCTP configuration file path %s", persistFile.toString()));

            this.bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("Sctp-BossGroup-" + this.name));
            // TODO: make a thread count for WorkerGroup configurable
            this.workerGroup = new NioEventLoopGroup(0, new DefaultThreadFactory("Sctp-WorkerGroup-" + this.name));
            this.clientExecutor = new ScheduledThreadPoolExecutor(1, new DefaultThreadFactory("Sctp-ClientExecutorGroup-"
                    + this.name));

            // this.nettyClientOpsThread = new NettyClientOpsThread(this);
            // (new Thread(this.nettyClientOpsThread )).start();

            try {
                this.load();
            } catch (FileNotFoundException e) {
                logger.warn(String.format("Failed to load the SCTP configuration file. \n%s", e.getMessage()));
            }

            this.started = true;

            if (logger.isInfoEnabled()) {
                logger.info(String.format("Started SCTP Management=%s", this.name));
            }

            for (ManagementEventListener lstr : managementEventListeners) {
                try {
                    lstr.onServiceStarted();
                } catch (Throwable ee) {
                    logger.error("Exception while invoking onServiceStarted", ee);
                }
            }
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#stop()
     */
    @Override
    public void stop() throws Exception {
        if (!this.started) {
            logger.warn(String.format("management=%s is already stopped", this.name));
            return;
        }

        // this.nettyClientOpsThread.setStarted(false);

        for (ManagementEventListener lstr : managementEventListeners) {
            try {
                lstr.onServiceStopped();
            } catch (Throwable ee) {
                logger.error("Exception while invoking onServiceStopped", ee);
            }
        }

        // We store the original state first
        this.store();

        // Stop all associations
        FastMap<String, Association> associationsTemp = this.associations;
        for (FastMap.Entry<String, Association> n = associationsTemp.head(), end = associationsTemp.tail(); (n = n.getNext()) != end;) {
            Association associationTemp = n.getValue();
            if (associationTemp.isStarted()) {
                ((NettyAssociationImpl) associationTemp).stop();
            }
        }

        FastList<Server> tempServers = servers;
        for (FastList.Node<Server> n = tempServers.head(), end = tempServers.tail(); (n = n.getNext()) != end;) {
            Server serverTemp = n.getValue();
            if (serverTemp.isStarted()) {
                try {
                    ((NettyServerImpl) serverTemp).stop();
                } catch (Exception e) {
                    logger.error(String.format("Exception while stopping the Server=%s", serverTemp.getName()), e);
                }
            }
        }

        // waiting till stopping associations
        for (int i1 = 0; i1 < 20; i1++) {
            boolean assConnected = false;
            for (FastMap.Entry<String, Association> n = this.associations.head(), end = this.associations.tail(); (n = n
                    .getNext()) != end;) {
                Association associationTemp = n.getValue();
                if (associationTemp.isConnected()) {
                    assConnected = true;
                    break;
                }
            }
            if (!assConnected)
                break;
            Thread.sleep(100);
        }

        // TODO - make a general shutdown and waiting for it instead of "waiting till stopping associations" 
        this.bossGroup.shutdownGracefully();
        this.workerGroup.shutdownGracefully();
        this.clientExecutor.shutdown();
       

        // TODO Should servers be also checked for shutdown?

        this.started = false;

    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#isStarted()
     */
    @Override
    public boolean isStarted() {
        return this.started;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#removeAllResourses()
     */
    @Override
    public void removeAllResourses() throws Exception {
        synchronized (this) {
            if (!this.started) {
                throw new Exception(String.format("Management=%s not started", this.name));
            }

            if (this.associations.size() == 0 && this.servers.size() == 0)
                // no resources allocated - nothing to do
                return;

            if (logger.isInfoEnabled()) {
                logger.info(String.format("Removing allocated resources: Servers=%d, Associations=%d", this.servers.size(),
                        this.associations.size()));
            }

            // Remove all associations
            ArrayList<String> lst = new ArrayList<String>();
            for (FastMap.Entry<String, Association> n = this.associations.head(), end = this.associations.tail(); (n = n
                    .getNext()) != end;) {
                lst.add(n.getKey());
            }
            for (String n : lst) {
                this.stopAssociation(n);
                this.removeAssociation(n);
            }

            // Remove all servers
            lst.clear();
            for (FastList.Node<Server> n = this.servers.head(), end = this.servers.tail(); (n = n.getNext()) != end;) {
                lst.add(n.getValue().getName());
            }
            for (String n : lst) {
                this.stopServer(n);
                this.removeServer(n);
            }

            // We store the cleared state
            this.store();

            for (ManagementEventListener lstr : managementEventListeners) {
                try {
                    lstr.onRemoveAllResources();
                } catch (Throwable ee) {
                    logger.error("Exception while invoking onRemoveAllResources", ee);
                }
            }
        }

    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#addServer(java.lang.String, java.lang.String, int,
     * org.mobicents.protocols.api.IpChannelType, boolean, int, java.lang.String[])
     */
    @Override
    public Server addServer(String serverName, String hostAddress, int port, IpChannelType ipChannelType,
            boolean acceptAnonymousConnections, int maxConcurrentConnectionsCount, String[] extraHostAddresses)
            throws Exception {
        if (!this.started) {
            throw new Exception(String.format("Management=%s not started", this.name));
        }

        if (serverName == null) {
            throw new Exception("Server name cannot be null");
        }

        if (hostAddress == null) {
            throw new Exception("Server host address cannot be null");
        }

        if (port < 1) {
            throw new Exception("Server host port cannot be less than 1");
        }

        synchronized (this) {
            for (FastList.Node<Server> n = this.servers.head(), end = this.servers.tail(); (n = n.getNext()) != end;) {
                Server serverTemp = n.getValue();
                if (serverName.equals(serverTemp.getName())) {
                    throw new Exception(String.format("Server name=%s already exist", serverName));
                }

                if (hostAddress.equals(serverTemp.getHostAddress()) && port == serverTemp.getHostport()) {
                    throw new Exception(String.format("Server name=%s is already bound to %s:%d", serverTemp.getName(),
                            serverTemp.getHostAddress(), serverTemp.getHostport()));
                }
            }

            NettyServerImpl server = new NettyServerImpl(serverName, hostAddress, port, ipChannelType,
                    acceptAnonymousConnections, maxConcurrentConnectionsCount, extraHostAddresses);
            server.setManagement(this);

            FastList<Server> newServers = new FastList<Server>();
            newServers.addAll(this.servers);
            newServers.add(server);
            this.servers = newServers;
            // this.servers.add(server);

            this.store();

            for (ManagementEventListener lstr : managementEventListeners) {
                try {
                    lstr.onServerAdded(server);
                } catch (Throwable ee) {
                    logger.error("Exception while invoking onServerAdded", ee);
                }
            }

            if (logger.isInfoEnabled()) {
                logger.info(String.format("Created Server=%s", server.getName()));
            }

            return server;
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#addServer(java.lang.String, java.lang.String, int,
     * org.mobicents.protocols.api.IpChannelType, java.lang.String[])
     */
    @Override
    public Server addServer(String serverName, String hostAddress, int port, IpChannelType ipChannelType,
            String[] extraHostAddresses) throws Exception {
        return addServer(serverName, hostAddress, port, ipChannelType, false, 0, extraHostAddresses);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#addServer(java.lang.String, java.lang.String, int)
     */
    @Override
    public Server addServer(String serverName, String hostAddress, int port) throws Exception {
        return addServer(serverName, hostAddress, port, IpChannelType.SCTP, false, 0, null);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#removeServer(java.lang.String)
     */
    @Override
    public void removeServer(String serverName) throws Exception {
        if (!this.started) {
            throw new Exception(String.format("Management=%s not started", this.name));
        }

        if (serverName == null) {
            throw new Exception("Server name cannot be null");
        }

        synchronized (this) {
            Server removeServer = null;
            for (FastList.Node<Server> n = this.servers.head(), end = this.servers.tail(); (n = n.getNext()) != end;) {
                NettyServerImpl serverTemp = (NettyServerImpl) n.getValue();

                if (serverName.equals(serverTemp.getName())) {
                    if (serverTemp.isStarted()) {
                        throw new Exception(String.format("Server=%s is started. Stop the server before removing", serverName));
                    }

                    if (serverTemp.anonymAssociations.size() != 0 || serverTemp.associations.size() != 0) {
                        throw new Exception(String.format(
                                "Server=%s has Associations. Remove all those Associations before removing Server", serverName));
                    }
                    removeServer = serverTemp;
                    break;
                }
            }

            if (removeServer == null) {
                throw new Exception(String.format("No Server found with name=%s", serverName));
            }

            FastList<Server> newServers = new FastList<Server>();
            newServers.addAll(this.servers);
            newServers.remove(removeServer);
            this.servers = newServers;
            // this.servers.remove(removeServer);

            this.store();

            for (ManagementEventListener lstr : managementEventListeners) {
                try {
                    lstr.onServerRemoved(removeServer);
                } catch (Throwable ee) {
                    logger.error("Exception while invoking onServerRemoved", ee);
                }
            }
        }

    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#startServer(java.lang.String)
     */
    @Override
    public void startServer(String serverName) throws Exception {
        if (!this.started) {
            throw new Exception(String.format("Management=%s not started", this.name));
        }

        if (name == null) {
            throw new Exception("Server name cannot be null");
        }

        FastList<Server> tempServers = servers;
        for (FastList.Node<Server> n = tempServers.head(), end = tempServers.tail(); (n = n.getNext()) != end;) {
            Server serverTemp = n.getValue();

            if (serverName.equals(serverTemp.getName())) {
                if (serverTemp.isStarted()) {
                    throw new Exception(String.format("Server=%s is already started", serverName));
                }
                ((NettyServerImpl) serverTemp).start();
                this.store();
                return;
            }
        }

        throw new Exception(String.format("No Server foubd with name=%s", serverName));

    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#stopServer(java.lang.String)
     */
    @Override
    public void stopServer(String serverName) throws Exception {
        if (!this.started) {
            throw new Exception(String.format("Management=%s not started", this.name));
        }

        if (serverName == null) {
            throw new Exception("Server name cannot be null");
        }

        FastList<Server> tempServers = servers;
        for (FastList.Node<Server> n = tempServers.head(), end = tempServers.tail(); (n = n.getNext()) != end;) {
            Server serverTemp = n.getValue();

            if (serverName.equals(serverTemp.getName())) {
                ((NettyServerImpl) serverTemp).stop();
                this.store();
                return;
            }
        }

        throw new Exception(String.format("No Server found with name=%s", serverName));

    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#getServers()
     */
    @Override
    public List<Server> getServers() {
        return servers.unmodifiable();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#addServerAssociation(java.lang .String, int, java.lang.String,
     * java.lang.String)
     */
    @Override
    public Association addServerAssociation(String peerAddress, int peerPort, String serverName, String assocName)
            throws Exception {
        return addServerAssociation(peerAddress, peerPort, serverName, assocName, IpChannelType.SCTP);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#addServerAssociation(java.lang .String, int, java.lang.String,
     * java.lang.String, org.mobicents.protocols.api.IpChannelType)
     */
    @Override
    public Association addServerAssociation(String peerAddress, int peerPort, String serverName, String assocName,
            IpChannelType ipChannelType) throws Exception {
        if (!this.started) {
            throw new Exception(String.format("Management=%s not started", this.name));
        }

        if (peerAddress == null) {
            throw new Exception("Peer address cannot be null");
        }

        if (peerPort < 0) {
            throw new Exception("Peer port cannot be less than 0");
        }

        if (serverName == null) {
            throw new Exception("Server name cannot be null");
        }

        if (assocName == null) {
            throw new Exception("Association name cannot be null");
        }

        synchronized (this) {
            if (this.associations.get(assocName) != null) {
                throw new Exception(String.format("Already has association=%s", assocName));
            }

            Server server = null;

            for (FastList.Node<Server> n = this.servers.head(), end = this.servers.tail(); (n = n.getNext()) != end;) {
                Server serverTemp = n.getValue();
                if (serverTemp.getName().equals(serverName)) {
                    server = serverTemp;
                }
            }

            if (server == null) {
                throw new Exception(String.format("No Server found for name=%s", serverName));
            }

            for (FastMap.Entry<String, Association> n = this.associations.head(), end = this.associations.tail(); (n = n
                    .getNext()) != end;) {
                Association associationTemp = n.getValue();
                
                if (associationTemp.getServerName().equals(server.getName()) && peerAddress.equals(associationTemp.getPeerAddress()) && associationTemp.getPeerPort() == peerPort) {
                    throw new Exception(String.format("Already has association=%s with same peer address=%s and port=%d",
                            associationTemp.getName(), peerAddress, peerPort));
                }
            }

            if (server.getIpChannelType() != ipChannelType)
                throw new Exception(String.format("Server and Accociation has different IP channel type"));

            NettyAssociationImpl association = new NettyAssociationImpl(peerAddress, peerPort, serverName, assocName,
                    ipChannelType);
            association.setManagement(this);

            NettyAssociationMap<String, Association> newAssociations = new NettyAssociationMap<String, Association>();
            newAssociations.putAll(this.associations);
            newAssociations.put(assocName, association);
            this.associations = newAssociations;
            // this.associations.put(assocName, association);

            FastList<String> newAssociations2 = new FastList<String>();
            newAssociations2.addAll(((NettyServerImpl) server).associations);
            newAssociations2.add(assocName);
            ((NettyServerImpl) server).associations = newAssociations2;
            // ((ServerImpl) server).associations.add(assocName);

            this.store();

            for (ManagementEventListener lstr : managementEventListeners) {
                try {
                    lstr.onAssociationAdded(association);
                } catch (Throwable ee) {
                    logger.error("Exception while invoking onAssociationAdded", ee);
                }
            }

            if (logger.isInfoEnabled()) {
                logger.info(String.format("Added Associoation=%s of type=%s", association.getName(),
                        association.getAssociationType()));
            }

            return association;
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#addAssociation(java.lang.String, int, java.lang.String, int,
     * java.lang.String)
     */
    @Override
    public Association addAssociation(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName)
            throws Exception {
        return addAssociation(hostAddress, hostPort, peerAddress, peerPort, assocName, IpChannelType.SCTP, null);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#addAssociation(java.lang.String, int, java.lang.String, int,
     * java.lang.String, org.mobicents.protocols.api.IpChannelType, java.lang.String[])
     */
    @Override
    public Association addAssociation(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName,
            IpChannelType ipChannelType, String[] extraHostAddresses) throws Exception {

        if (!this.started) {
            throw new Exception(String.format("Management=%s not started", this.name));
        }

        if (hostAddress == null) {
            throw new Exception("Host address cannot be null");
        }

        if (hostPort < 0) {
            throw new Exception("Host port cannot be less than 0");
        }

        if (peerAddress == null) {
            throw new Exception("Peer address cannot be null");
        }

        if (peerPort < 1) {
            throw new Exception("Peer port cannot be less than 1");
        }

        if (assocName == null) {
            throw new Exception("Association name cannot be null");
        }

        synchronized (this) {
            for (FastMap.Entry<String, Association> n = this.associations.head(), end = this.associations.tail(); (n = n
                    .getNext()) != end;) {
                Association associationTemp = n.getValue();

                if (assocName.equals(associationTemp.getName())) {
                    throw new Exception(String.format("Already has association=%s", associationTemp.getName()));
                }

                if (peerAddress.equals(associationTemp.getPeerAddress()) && associationTemp.getPeerPort() == peerPort) {
                    throw new Exception(String.format("Already has association=%s with same peer address=%s and port=%d",
                            associationTemp.getName(), peerAddress, peerPort));
                }

                if (hostAddress.equals(associationTemp.getHostAddress()) && associationTemp.getHostPort() == hostPort) {
                    throw new Exception(String.format("Already has association=%s with same host address=%s and port=%d",
                            associationTemp.getName(), hostAddress, hostPort));
                }

            }

            NettyAssociationImpl association = new NettyAssociationImpl(hostAddress, hostPort, peerAddress, peerPort,
                    assocName, ipChannelType, extraHostAddresses);
            association.setManagement(this);

            NettyAssociationMap<String, Association> newAssociations = new NettyAssociationMap<String, Association>();
            newAssociations.putAll(this.associations);
            newAssociations.put(assocName, association);
            this.associations = newAssociations;
            // associations.put(assocName, association);

            this.store();

            for (ManagementEventListener lstr : managementEventListeners) {
                try {
                    lstr.onAssociationAdded(association);
                } catch (Throwable ee) {
                    logger.error("Exception while invoking onAssociationAdded", ee);
                }
            }

            if (logger.isInfoEnabled()) {
                logger.info(String.format("Added Associoation=%s of type=%s", association.getName(),
                        association.getAssociationType()));
            }

            return association;
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#removeAssociation(java.lang.String )
     */
    @Override
    public void removeAssociation(String assocName) throws Exception {
        if (!this.started) {
            throw new Exception(String.format("Management=%s not started", this.name));
        }

        if (assocName == null) {
            throw new Exception("Association name cannot be null");
        }

        synchronized (this) {
            Association association = this.associations.get(assocName);

            if (association == null) {
                throw new Exception(String.format("No Association found for name=%s", assocName));
            }

            if (association.isStarted()) {
                throw new Exception(String.format("Association name=%s is started. Stop before removing", assocName));
            }

            NettyAssociationMap<String, Association> newAssociations = new NettyAssociationMap<String, Association>();
            newAssociations.putAll(this.associations);
            newAssociations.remove(assocName);
            this.associations = newAssociations;
            // this.associations.remove(assocName);

            if (((NettyAssociationImpl) association).getAssociationType() == AssociationType.SERVER) {
                for (FastList.Node<Server> n = this.servers.head(), end = this.servers.tail(); (n = n.getNext()) != end;) {
                    Server serverTemp = n.getValue();
                    if (serverTemp.getName().equals(association.getServerName())) {
                        FastList<String> newAssociations2 = new FastList<String>();
                        newAssociations2.addAll(((NettyServerImpl) serverTemp).associations);
                        newAssociations2.remove(assocName);
                        ((NettyServerImpl) serverTemp).associations = newAssociations2;
                        break;
                    }
                }
            }

            this.store();

            for (ManagementEventListener lstr : managementEventListeners) {
                try {
                    lstr.onAssociationRemoved(association);
                } catch (Throwable ee) {
                    logger.error("Exception while invoking onAssociationRemoved", ee);
                }
            }
        }

    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#getAssociation(java.lang.String)
     */
    @Override
    public Association getAssociation(String assocName) throws Exception {
        if (assocName == null) {
            throw new Exception("Association name cannot be null");
        }
        Association associationTemp = this.associations.get(assocName);

        if (associationTemp == null) {
            throw new Exception(String.format("No Association found for name=%s", assocName));
        }
        return associationTemp;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#getAssociations()
     */
    @Override
    public Map<String, Association> getAssociations() {
        Map<String, Association> routeTmp = new HashMap<String, Association>();
        routeTmp.putAll(this.associations);
        return routeTmp;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#startAssociation(java.lang.String)
     */
    @Override
    public void startAssociation(String assocName) throws Exception {
        if (!this.started) {
            throw new Exception(String.format("Management=%s not started", this.name));
        }

        if (assocName == null) {
            throw new Exception("Association name cannot be null");
        }

        Association associationTemp = this.associations.get(assocName);

        if (associationTemp == null) {
            throw new Exception(String.format("No Association found for name=%s", assocName));
        }

        if (associationTemp.isStarted()) {
            throw new Exception(String.format("Association=%s is already started", assocName));
        }

        ((NettyAssociationImpl) associationTemp).start();
        this.store();

    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#stopAssociation(java.lang.String)
     */
    @Override
    public void stopAssociation(String assocName) throws Exception {
        if (!this.started) {
            throw new Exception(String.format("Management=%s not started", this.name));
        }

        if (assocName == null) {
            throw new Exception("Association name cannot be null");
        }

        Association association = this.associations.get(assocName);

        if (association == null) {
            throw new Exception(String.format("No Association found for name=%s", assocName));
        }

        ((NettyAssociationImpl) association).stop();
        this.store();

    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#getConnectDelay()
     */
    @Override
    public int getConnectDelay() {
        return this.connectDelay;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#setConnectDelay(int)
     */
    @Override
    public void setConnectDelay(int connectDelay) throws Exception {
        if (!this.started)
            throw new Exception("ConnectDelay parameter can be updated only when SCTP stack is running");

        this.connectDelay = connectDelay;

        this.store();
    }

    @Override
    public double getCongControl_DelayThreshold_1() {
        return congControl_DelayThreshold[0];
    }

    @Override
    public double getCongControl_DelayThreshold_2() {
        return congControl_DelayThreshold[1];
    }

    @Override
    public double getCongControl_DelayThreshold_3() {
        return congControl_DelayThreshold[2];
    }

    @Override
    public void setCongControl_DelayThreshold_1(double val) throws Exception {
        if (!this.started)
            throw new Exception("CongControl_DelayThreshold parameter can be updated only when SCTP stack is running");

        congControl_DelayThreshold[0] = val;

        this.store();
    }

    @Override
    public void setCongControl_DelayThreshold_2(double val) throws Exception {
        if (!this.started)
            throw new Exception("CongControl_DelayThreshold parameter can be updated only when SCTP stack is running");

        congControl_DelayThreshold[1] = val;

        this.store();
    }

    @Override
    public void setCongControl_DelayThreshold_3(double val) throws Exception {
        if (!this.started)
            throw new Exception("CongControl_DelayThreshold parameter can be updated only when SCTP stack is running");

        congControl_DelayThreshold[2] = val;

        this.store();
    }

    @Override
    public double getCongControl_BackToNormalDelayThreshold_1() {
        return congControl_BackToNormalDelayThreshold[0];
    }

    @Override
    public double getCongControl_BackToNormalDelayThreshold_2() {
        return congControl_BackToNormalDelayThreshold[1];
    }

    @Override
    public double getCongControl_BackToNormalDelayThreshold_3() {
        return congControl_BackToNormalDelayThreshold[2];
    }

    @Override
    public void setCongControl_BackToNormalDelayThreshold_1(double val) throws Exception {
        if (!this.started)
            throw new Exception(
                    "CongControl_BackToNormalDelayThreshold parameter can be updated only when SCTP stack is running");

        congControl_BackToNormalDelayThreshold[0] = val;

        this.store();
    }

    @Override
    public void setCongControl_BackToNormalDelayThreshold_2(double val) throws Exception {
        if (!this.started)
            throw new Exception(
                    "CongControl_BackToNormalDelayThreshold parameter can be updated only when SCTP stack is running");

        congControl_BackToNormalDelayThreshold[1] = val;

        this.store();
    }

    @Override
    public void setCongControl_BackToNormalDelayThreshold_3(double val) throws Exception {
        if (!this.started)
            throw new Exception(
                    "CongControl_BackToNormalDelayThreshold parameter can be updated only when SCTP stack is running");

        congControl_BackToNormalDelayThreshold[2] = val;

        this.store();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#getWorkerThreads()
     */
    @Override
    public int getWorkerThreads() {
        return 1;
//        return this.workerThreads;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#setWorkerThreads(int)
     */
    @Override
    public void setWorkerThreads(int workerThreads) throws Exception {
//        if (this.started)
//            throw new Exception("WorkerThreads parameter can be updated only when SCTP stack is NOT running");
//
//        if (workerThreads < 1) {
//            workerThreads = DEFAULT_IO_THREADS;
//        }
//        this.workerThreads = workerThreads;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#isSingleThread()
     */
    @Override
    public boolean isSingleThread() {
        return true;
//        return this.singleThread;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mobicents.protocols.api.Management#setSingleThread(boolean)
     */
    @Override
    public void setSingleThread(boolean singleThread) throws Exception {
//        if (this.started)
//            throw new Exception("SingleThread parameter can be updated only when SCTP stack is NOT running");
//
//        this.singleThread = singleThread;

    }

    @Override
    public Boolean getOptionSctpDisableFragments() {
        return optionSctpDisableFragments;
    }

    @Override
    public void setOptionSctpDisableFragments(Boolean optionSctpDisableFragments) {
        this.optionSctpDisableFragments = optionSctpDisableFragments;
    }

    @Override
    public Integer getOptionSctpFragmentInterleave() {
        return optionSctpFragmentInterleave;
    }

    @Override
    public void setOptionSctpFragmentInterleave(Integer optionSctpFragmentInterleave) {
        this.optionSctpFragmentInterleave = optionSctpFragmentInterleave;
    }

    public InitMaxStreams getOptionSctpInitMaxstreams() {
        if (optionSctpInitMaxstreams_MaxInStreams != null && optionSctpInitMaxstreams_MaxOutStreams != null) {
            return SctpStandardSocketOptions.InitMaxStreams.create(optionSctpInitMaxstreams_MaxInStreams,
                    optionSctpInitMaxstreams_MaxOutStreams);
        } else {
            return null;
        }
    }

    @Override
    public Integer getOptionSctpInitMaxstreams_MaxOutStreams() {
        return optionSctpInitMaxstreams_MaxOutStreams;
    }

    @Override
    public Integer getOptionSctpInitMaxstreams_MaxInStreams() {
        return optionSctpInitMaxstreams_MaxInStreams;
    }

    @Override
    public void setOptionSctpInitMaxstreams_MaxOutStreams(Integer val) {
        this.optionSctpInitMaxstreams_MaxOutStreams = val;
    }

    @Override
    public void setOptionSctpInitMaxstreams_MaxInStreams(Integer val) {
        this.optionSctpInitMaxstreams_MaxInStreams = val;
    }

    @Override
    public Boolean getOptionSctpNodelay() {
        return optionSctpNodelay;
    }

    @Override
    public void setOptionSctpNodelay(Boolean optionSctpNodelay) {
        this.optionSctpNodelay = optionSctpNodelay;
    }

    @Override
    public Integer getOptionSoSndbuf() {
        return optionSoSndbuf;
    }

    @Override
    public void setOptionSoSndbuf(Integer optionSoSndbuf) {
        this.optionSoSndbuf = optionSoSndbuf;
    }

    @Override
    public Integer getOptionSoRcvbuf() {
        return optionSoRcvbuf;
    }

    @Override
    public void setOptionSoRcvbuf(Integer optionSoRcvbuf) {
        this.optionSoRcvbuf = optionSoRcvbuf;
    }

    @Override
    public Integer getOptionSoLinger() {
        return optionSoLinger;
    }

    @Override
    public void setOptionSoLinger(Integer optionSoLinger) {
        this.optionSoLinger = optionSoLinger;
    }

    protected FastList<ManagementEventListener> getManagementEventListeners() {
        return managementEventListeners;
    }

    protected FastList<CongestionListener> getCongestionListeners() {
        return congestionListeners;
    }

    @SuppressWarnings("unchecked")
    protected void load() throws FileNotFoundException {
        XMLObjectReader reader = null;
        try {
            reader = XMLObjectReader.newInstance(new FileInputStream(persistFile.toString()));
            reader.setBinding(binding);
            load(reader);
            

        } catch (XMLStreamException ex) {
            // this.logger.info(
            // "Error while re-creating Linksets from persisted file", ex);
        }
    }
    
    protected void load(XMLObjectReader reader) throws XMLStreamException
    {
    	try {
            Integer vali = reader.read(CONNECT_DELAY_PROP, Integer.class);
            if (vali != null)
                this.connectDelay = vali;
            // this.workerThreads = reader.read(WORKER_THREADS_PROP, Integer.class);
            // this.singleThread = reader.read(SINGLE_THREAD_PROP, Boolean.class);
            vali = reader.read(WORKER_THREADS_PROP, Integer.class);
            Boolean valb = reader.read(SINGLE_THREAD_PROP, Boolean.class);
        } catch (java.lang.NullPointerException npe) {
            // ignore.
            // For backward compatibility we can ignore if these values are not defined
        }

        Double valTH1 = reader.read(CONG_CONTROL_DELAY_THRESHOLD_1, Double.class);
        Double valTH2 = reader.read(CONG_CONTROL_DELAY_THRESHOLD_2, Double.class);
        Double valTH3 = reader.read(CONG_CONTROL_DELAY_THRESHOLD_3, Double.class);
        Double valTB1 = reader.read(CONG_CONTROL_BACK_TO_NORMAL_DELAY_THRESHOLD_1, Double.class);
        Double valTB2 = reader.read(CONG_CONTROL_BACK_TO_NORMAL_DELAY_THRESHOLD_2, Double.class);
        Double valTB3 = reader.read(CONG_CONTROL_BACK_TO_NORMAL_DELAY_THRESHOLD_3, Double.class);
        if (valTH1 != null && valTH2 != null && valTH3 != null && valTB1 != null && valTB2 != null && valTB3 != null) {
            this.congControl_DelayThreshold = new double[3];
            this.congControl_DelayThreshold[0] = valTH1;
            this.congControl_DelayThreshold[1] = valTH2;
            this.congControl_DelayThreshold[2] = valTH3;
            this.congControl_BackToNormalDelayThreshold = new double[3];
            this.congControl_BackToNormalDelayThreshold[0] = valTB1;
            this.congControl_BackToNormalDelayThreshold[1] = valTB2;
            this.congControl_BackToNormalDelayThreshold[2] = valTB3;
        }

        // TODO: add storing of parameters
//        Boolean valB = reader.read(OPTION_SCTP_DISABLE_FRAGMENTS, Boolean.class);
//        if (valB != null)
//            this.optionSctpDisableFragments = valB;
//        Integer valI = reader.read(OPTION_SCTP_FRAGMENT_INTERLEAVE, Integer.class);
//        if (valI != null)
//            this.optionSctpFragmentInterleave = valI;
//        Integer valI_In = reader.read(OPTION_SCTP_INIT_MAXSTREAMS_IN, Integer.class);
//        Integer valI_Out = reader.read(OPTION_SCTP_INIT_MAXSTREAMS_OUT, Integer.class);
//        if (valI_In != null && valI_Out != null) {
//            this.optionSctpInitMaxstreams = SctpStandardSocketOptions.InitMaxStreams.create(valI_In, valI_Out);
//        }
//        valB = reader.read(OPTION_SCTP_NODELAY, Boolean.class);
//        if (valB != null)
//            this.optionSctpNodelay = valB;
//        valI = reader.read(OPTION_SO_SNDBUF, Integer.class);
//        if (valI != null)
//            this.optionSoSndbuf = valI;
//        valI = reader.read(OPTION_SO_RCVBUF, Integer.class);
//        if (valI != null)
//            this.optionSoRcvbuf = valI;
//        valI = reader.read(OPTION_SO_LINGER, Integer.class);
//        if (valI != null)
//            this.optionSoLinger = valI;

        this.servers = reader.read(SERVERS, FastList.class);

        for (FastList.Node<Server> n = this.servers.head(), end = this.servers.tail(); (n = n.getNext()) != end;) {
            Server serverTemp = n.getValue();
            ((NettyServerImpl) serverTemp).setManagement(this);
            if (serverTemp.isStarted()) {
                try {
                    ((NettyServerImpl) serverTemp).start();
                } catch (Exception e) {
                    logger.error(String.format("Error while initiating Server=%s", serverTemp.getName()), e);
                }
            }
        }

        this.associations = reader.read(ASSOCIATIONS, NettyAssociationMap.class);
        for (FastMap.Entry<String, Association> n = this.associations.head(), end = this.associations.tail(); (n = n
                .getNext()) != end;) {
            NettyAssociationImpl associationTemp = (NettyAssociationImpl) n.getValue();
            associationTemp.setManagement(this);
        }
    }

    public void store() {
        try {
            XMLObjectWriter writer = XMLObjectWriter.newInstance(new FileOutputStream(persistFile.toString()));
            writer.setBinding(binding);
            // Enables cross-references.
            // writer.setReferenceResolver(new XMLReferenceResolver());
            writer.setIndentation(TAB_INDENT);

            writer.write(this.connectDelay, CONNECT_DELAY_PROP, Integer.class);
            // writer.write(this.workerThreads, WORKER_THREADS_PROP, Integer.class);
            // writer.write(this.singleThread, SINGLE_THREAD_PROP, Boolean.class);

            if (this.congControl_DelayThreshold != null && this.congControl_DelayThreshold.length == 3) {
                writer.write(this.congControl_DelayThreshold[0], CONG_CONTROL_DELAY_THRESHOLD_1, Double.class);
                writer.write(this.congControl_DelayThreshold[1], CONG_CONTROL_DELAY_THRESHOLD_2, Double.class);
                writer.write(this.congControl_DelayThreshold[2], CONG_CONTROL_DELAY_THRESHOLD_3, Double.class);
            }
            if (this.congControl_BackToNormalDelayThreshold != null && this.congControl_BackToNormalDelayThreshold.length == 3) {
                writer.write(this.congControl_BackToNormalDelayThreshold[0], CONG_CONTROL_BACK_TO_NORMAL_DELAY_THRESHOLD_1, Double.class);
                writer.write(this.congControl_BackToNormalDelayThreshold[1], CONG_CONTROL_BACK_TO_NORMAL_DELAY_THRESHOLD_2, Double.class);
                writer.write(this.congControl_BackToNormalDelayThreshold[2], CONG_CONTROL_BACK_TO_NORMAL_DELAY_THRESHOLD_3, Double.class);
            }

            // TODO: add storing of parameters
//            if (this.optionSctpDisableFragments != null) {
//                writer.write(this.optionSctpDisableFragments, OPTION_SCTP_DISABLE_FRAGMENTS, Boolean.class);
//            }
//            if (this.optionSctpFragmentInterleave != null) {
//                writer.write(this.optionSctpFragmentInterleave, OPTION_SCTP_FRAGMENT_INTERLEAVE, Integer.class);
//            }
//            if (this.optionSctpInitMaxstreams != null) {
//                writer.write(this.optionSctpInitMaxstreams.maxInStreams(), OPTION_SCTP_INIT_MAXSTREAMS_IN, Integer.class);
//                writer.write(this.optionSctpInitMaxstreams.maxOutStreams(), OPTION_SCTP_INIT_MAXSTREAMS_OUT, Integer.class);
//            }
//            if (this.optionSctpNodelay != null) {
//                writer.write(this.optionSctpNodelay, OPTION_SCTP_NODELAY, Boolean.class);
//            }
//            if (this.optionSoSndbuf != null) {
//                writer.write(this.optionSoSndbuf, OPTION_SO_SNDBUF, Integer.class);
//            }
//            if (this.optionSoRcvbuf != null) {
//                writer.write(this.optionSoRcvbuf, OPTION_SO_RCVBUF, Integer.class);
//            }
//            if (this.optionSoLinger != null) {
//                writer.write(this.optionSoLinger, OPTION_SO_LINGER, Integer.class);
//            }

            writer.write(this.servers, SERVERS, FastList.class);
            writer.write(this.associations, ASSOCIATIONS, NettyAssociationMap.class);

            writer.close();
        } catch (Exception e) {
            logger.error("Error while persisting the Rule state in file", e);
        }
    }

    @Override
    public int getBufferSize() {
        // this parameter is only needed for non-netty version
        return 0;
    }

    @Override
    public void setBufferSize(int bufferSize) throws Exception {
        // this parameter is only needed for non-netty version
    }

	@Override
	public void modifyServer(String serverName, String hostAddress, Integer port, IpChannelType ipChannelType, Boolean acceptAnonymousConnections, Integer maxConcurrentConnectionsCount, String[] extraHostAddresses)
			throws Exception {

		if (!this.started) {
			throw new Exception(String.format("Management=%s MUST be started", this.name));
		}

		if (serverName == null) {
			throw new Exception("Server name cannot be null");
		}

		if (port !=null && (port < 1 || port > 65535)) {
			throw new Exception("Server host port cannot be less than 1 or more than 65535. But was : " + port);
		}

		synchronized (this) {
			Server modifyServer = null;
			for (FastList.Node<Server> n = this.servers.head(), end = this.servers.tail(); (n = n.getNext()) != end;) {
				NettyServerImpl currServer = (NettyServerImpl)n.getValue();

				if (serverName.equals(currServer.getName())) {

					if (currServer.isStarted()) {
						throw new Exception(String.format("Server=%s is started. Stop the server before modifying", serverName));
					}

					if(hostAddress != null)
						currServer.setHostAddress(hostAddress);
					if(port != null)
						currServer.setHostport(port);
					if(ipChannelType != null)
						currServer.setIpChannelType(ipChannelType);
					if(acceptAnonymousConnections != null)
						currServer.setAcceptAnonymousConnections(acceptAnonymousConnections);
					if(maxConcurrentConnectionsCount != null)
						currServer.setMaxConcurrentConnectionsCount(maxConcurrentConnectionsCount);
					if(extraHostAddresses!=null)
						currServer.setExtraHostAddresses(extraHostAddresses);

					modifyServer = currServer;
					break;
				}
			}

			if (modifyServer == null) {
				throw new Exception(String.format("No Server found for modifying with name=%s", serverName));
			}

			this.store();

			for (ManagementEventListener lstr : managementEventListeners) {
				try {
					lstr.onServerModified(modifyServer);
				} catch (Throwable ee) {
					logger.error("Exception while invoking onServerModified", ee);
				}
			}
		}

	}

	@Override
	public void modifyServerAssociation(String assocName, String peerAddress, Integer peerPort, String serverName, IpChannelType ipChannelType)	throws Exception {
		if (!this.started) {
			throw new Exception(String.format("Management=%s not started", this.name));
		}

		if (assocName == null) {
			throw new Exception("Association name cannot be null");
		}

		if (peerPort != null && (peerPort < 1 || peerPort > 65535)) {
			throw new Exception("Peer port cannot be less than 1 or more than 65535. But was : " + peerPort);
		}

		synchronized (this) {
			NettyAssociationImpl association = (NettyAssociationImpl) this.associations.get(assocName);

			if (association == null) {
				throw new Exception(String.format("No Association found for name=%s", assocName));
			}

			for (FastMap.Entry<String, Association> n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) {
				Association associationTemp = n.getValue();
				if(assocName.equals(associationTemp.getName()))
                    continue;

				if (peerAddress != null && peerAddress.equals(associationTemp.getPeerAddress()) && associationTemp.getPeerPort() == peerPort) {
					throw new Exception(String.format("Already has association=%s with same peer address=%s and port=%d", associationTemp.getName(),
							peerAddress, peerPort));
				}
			}

			if(peerAddress!=null)
				association.setPeerAddress(peerAddress);
			if(peerPort!= null)
				association.setPeerPort(peerPort);

			if(serverName!=null && !serverName.equals(association.getServerName()))
			{
				Server newServer = null;

				for (FastList.Node<Server> n = this.servers.head(), end = this.servers.tail(); (n = n.getNext()) != end;) {
					Server serverTemp = n.getValue();
					if (serverTemp.getName().equals(serverName)) {
						newServer = serverTemp;
					}
				}

				if (newServer == null) {
					throw new Exception(String.format("No Server found for name=%s", serverName));
				}

				if ((ipChannelType!=null && newServer.getIpChannelType() != ipChannelType)||(ipChannelType==null && newServer.getIpChannelType() != association.getIpChannelType()))
					throw new Exception(String.format("Server and Accociation has different IP channel type"));

				//remove association from current server
				for (FastList.Node<Server> n = this.servers.head(), end = this.servers.tail(); (n = n.getNext()) != end;) {
					Server serverTemp = n.getValue();
					if (serverTemp.getName().equals(association.getServerName())) {
						FastList<String> newAssociations2 = new FastList<String>();
						newAssociations2.addAll(((NettyServerImpl) serverTemp).associations);
						newAssociations2.remove(assocName);
						((NettyServerImpl) serverTemp).associations = newAssociations2;
						break;
					}
				}

				//add association name to server
				FastList<String> newAssociations2 = new FastList<String>();
				newAssociations2.addAll(((NettyServerImpl) newServer).associations);
				newAssociations2.add(assocName);
				((NettyServerImpl) newServer).associations = newAssociations2;

				association.setServerName(serverName);
			}
			else
			{
				if(ipChannelType!=null)
				{
					for (FastList.Node<Server> n = this.servers.head(), end = this.servers.tail(); (n = n.getNext()) != end;) {
						Server serverTemp = n.getValue();
						if (serverTemp.getName().equals(association.getServerName())) {
							if (serverTemp.getIpChannelType() != ipChannelType)
								throw new Exception(String.format("Server and Accociation has different IP channel type"));
						}
					}

					association.setIpChannelType(ipChannelType);
				}

			}

			this.store();

			for (ManagementEventListener lstr : managementEventListeners) {
				try {
					lstr.onAssociationModified((Association)association);
				} catch (Throwable ee) {
					logger.error("Exception while invoking onAssociationModified", ee);
				}
			}
		}
	}

	@Override
	public void modifyAssociation(String hostAddress, Integer hostPort, String peerAddress, Integer peerPort, String assocName,	IpChannelType ipChannelType, String[] extraHostAddresses) throws Exception {

		boolean isModified = false;
		if (!this.started) {
			throw new Exception(String.format("Management=%s not started", this.name));
		}

		if (hostPort != null && (hostPort < 1 || hostPort > 65535)) {
			throw new Exception("Host port cannot be less than 1 or more than 65535. But was : " + hostPort);
		}

		if (peerPort != null && (peerPort < 1 || peerPort > 65535)) {
			throw new Exception("Peer port cannot be less than 1 or more than 65535. But was : " + peerPort);
		}

		if (assocName == null) {
			throw new Exception("Association name cannot be null");
		}
		synchronized (this) {
			for (FastMap.Entry<String, Association> n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) {
				Association associationTemp = n.getValue();
				if(assocName.equals(associationTemp.getName()))
				    continue;

				if (peerAddress !=null && peerAddress.equals(associationTemp.getPeerAddress()) && associationTemp.getPeerPort() == peerPort) {
					throw new Exception(String.format("Already has association=%s with same peer address=%s and port=%d", associationTemp.getName(),
							peerAddress, peerPort));
				}

				if (hostAddress !=null && hostAddress.equals(associationTemp.getHostAddress()) && associationTemp.getHostPort() == hostPort) {
					throw new Exception(String.format("Already has association=%s with same host address=%s and port=%d", associationTemp.getName(),
							hostAddress, hostPort));
				}

			}

			NettyAssociationImpl association = (NettyAssociationImpl) this.associations.get(assocName);

			if(hostAddress!=null)
			{
				association.setHostAddress(hostAddress);
				isModified = true;
			}

			if(hostPort!= null)
			{
				association.setHostPort(hostPort);
				isModified = true;
			}

			if(peerAddress!=null)
			{
				association.setPeerAddress(peerAddress);
				isModified = true;
			}

			if(peerPort!= null)
			{
				association.setPeerPort(peerPort);
				isModified = true;
			}

			if(ipChannelType!=null)
			{
				association.setIpChannelType(ipChannelType);
				isModified = true;
			}

			if(extraHostAddresses!=null)
			{
				association.setExtraHostAddresses(extraHostAddresses);
				isModified = true;
			}

			if(association.isConnected() && isModified)
			{
				association.stop();
				association.start();
			}

			this.store();

			for (ManagementEventListener lstr : managementEventListeners) {
				try {
					lstr.onAssociationModified((Association)association);
				} catch (Throwable ee) {
					logger.error("Exception while invoking onAssociationModified", ee);
				}
			}
		}
	}
}