/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alipay.remoting;

import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;

import com.alipay.remoting.exception.RemotingException;
import com.alipay.remoting.log.BoltLoggerFactory;
import com.alipay.remoting.util.RemotingUtil;

import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;

/**
 * Base remoting capability.
 *
 * @author jiangping
 * @version $Id: BaseRemoting.java, v 0.1 Mar 4, 2016 12:09:56 AM tao Exp $
 */
public abstract class BaseRemoting {
    /** logger */
    private static final Logger logger = BoltLoggerFactory.getLogger("CommonDefault");

    protected CommandFactory    commandFactory;

    public BaseRemoting(CommandFactory commandFactory) {
        this.commandFactory = commandFactory;
    }

    /**
     * Synchronous invocation
     *
     * @param conn
     * @param request
     * @param timeoutMillis
     * @return
     * @throws InterruptedException
     * @throws RemotingException
     */
    protected RemotingCommand invokeSync(final Connection conn, final RemotingCommand request,
                                         final int timeoutMillis) throws RemotingException,
                                                                 InterruptedException {
        final InvokeFuture future = createInvokeFuture(request, request.getInvokeContext());
        conn.addInvokeFuture(future);
        final int requestId = request.getId();
        try {
            conn.getChannel().writeAndFlush(request).addListener(new ChannelFutureListener() {

                @Override
                public void operationComplete(ChannelFuture f) throws Exception {
                    if (!f.isSuccess()) {
                        conn.removeInvokeFuture(requestId);
                        future.putResponse(commandFactory.createSendFailedResponse(
                            conn.getRemoteAddress(), f.cause()));
                        logger.error("Invoke send failed, id={}", requestId, f.cause());
                    }
                }

            });
        } catch (Exception e) {
            conn.removeInvokeFuture(requestId);
            future.putResponse(commandFactory.createSendFailedResponse(conn.getRemoteAddress(), e));
            logger.error("Exception caught when sending invocation, id={}", requestId, e);
        }
        RemotingCommand response = future.waitResponse(timeoutMillis);

        if (response == null) {
            conn.removeInvokeFuture(requestId);
            response = this.commandFactory.createTimeoutResponse(conn.getRemoteAddress());
            logger.warn("Wait response, request id={} timeout!", requestId);
        }

        return response;
    }

    /**
     * Invocation with callback.
     *
     * @param conn
     * @param request
     * @param invokeCallback
     * @param timeoutMillis
     * @throws InterruptedException
     */
    protected void invokeWithCallback(final Connection conn, final RemotingCommand request,
                                      final InvokeCallback invokeCallback, final int timeoutMillis) {
        final InvokeFuture future = createInvokeFuture(conn, request, request.getInvokeContext(),
            invokeCallback);
        conn.addInvokeFuture(future);
        final int requestId = request.getId();
        try {
            Timeout timeout = TimerHolder.getTimer().newTimeout(new TimerTask() {
                @Override
                public void run(Timeout timeout) throws Exception {
                    InvokeFuture future = conn.removeInvokeFuture(requestId);
                    if (future != null) {
                        future.putResponse(commandFactory.createTimeoutResponse(conn
                            .getRemoteAddress()));
                        future.tryAsyncExecuteInvokeCallbackAbnormally();
                    }
                }

            }, timeoutMillis, TimeUnit.MILLISECONDS);
            future.addTimeout(timeout);
            conn.getChannel().writeAndFlush(request).addListener(new ChannelFutureListener() {

                @Override
                public void operationComplete(ChannelFuture cf) throws Exception {
                    if (!cf.isSuccess()) {
                        InvokeFuture f = conn.removeInvokeFuture(requestId);
                        if (f != null) {
                            f.cancelTimeout();
                            f.putResponse(commandFactory.createSendFailedResponse(
                                conn.getRemoteAddress(), cf.cause()));
                            f.tryAsyncExecuteInvokeCallbackAbnormally();
                        }
                        logger.error("Invoke send failed. The address is {}",
                            RemotingUtil.parseRemoteAddress(conn.getChannel()), cf.cause());
                    }
                }

            });
        } catch (Exception e) {
            InvokeFuture f = conn.removeInvokeFuture(requestId);
            if (f != null) {
                f.cancelTimeout();
                f.putResponse(commandFactory.createSendFailedResponse(conn.getRemoteAddress(), e));
                f.tryAsyncExecuteInvokeCallbackAbnormally();
            }
            logger.error("Exception caught when sending invocation. The address is {}",
                RemotingUtil.parseRemoteAddress(conn.getChannel()), e);
        }
    }

    /**
     * Invocation with future returned.
     *
     * @param conn
     * @param request
     * @param timeoutMillis
     * @return
     */
    protected InvokeFuture invokeWithFuture(final Connection conn, final RemotingCommand request,
                                            final int timeoutMillis) {

        final InvokeFuture future = createInvokeFuture(request, request.getInvokeContext());
        conn.addInvokeFuture(future);
        final int requestId = request.getId();
        try {
            Timeout timeout = TimerHolder.getTimer().newTimeout(new TimerTask() {
                @Override
                public void run(Timeout timeout) throws Exception {
                    InvokeFuture future = conn.removeInvokeFuture(requestId);
                    if (future != null) {
                        future.putResponse(commandFactory.createTimeoutResponse(conn
                            .getRemoteAddress()));
                    }
                }

            }, timeoutMillis, TimeUnit.MILLISECONDS);
            future.addTimeout(timeout);

            conn.getChannel().writeAndFlush(request).addListener(new ChannelFutureListener() {

                @Override
                public void operationComplete(ChannelFuture cf) throws Exception {
                    if (!cf.isSuccess()) {
                        InvokeFuture f = conn.removeInvokeFuture(requestId);
                        if (f != null) {
                            f.cancelTimeout();
                            f.putResponse(commandFactory.createSendFailedResponse(
                                conn.getRemoteAddress(), cf.cause()));
                        }
                        logger.error("Invoke send failed. The address is {}",
                            RemotingUtil.parseRemoteAddress(conn.getChannel()), cf.cause());
                    }
                }

            });
        } catch (Exception e) {
            InvokeFuture f = conn.removeInvokeFuture(requestId);
            if (f != null) {
                f.cancelTimeout();
                f.putResponse(commandFactory.createSendFailedResponse(conn.getRemoteAddress(), e));
            }
            logger.error("Exception caught when sending invocation. The address is {}",
                RemotingUtil.parseRemoteAddress(conn.getChannel()), e);
        }
        return future;
    }

    /**
     * Oneway invocation.
     *
     * @param conn
     * @param request
     * @throws InterruptedException
     */
    protected void oneway(final Connection conn, final RemotingCommand request) {
        try {
            conn.getChannel().writeAndFlush(request).addListener(new ChannelFutureListener() {

                @Override
                public void operationComplete(ChannelFuture f) throws Exception {
                    if (!f.isSuccess()) {
                        logger.error("Invoke send failed. The address is {}",
                            RemotingUtil.parseRemoteAddress(conn.getChannel()), f.cause());
                    }
                }

            });
        } catch (Exception e) {
            if (null == conn) {
                logger.error("Conn is null");
            } else {
                logger.error("Exception caught when sending invocation. The address is {}",
                    RemotingUtil.parseRemoteAddress(conn.getChannel()), e);
            }
        }
    }

    /**
     * Create invoke future with {@link InvokeContext}.
     * @param request
     * @param invokeContext
     * @return
     */
    protected abstract InvokeFuture createInvokeFuture(final RemotingCommand request,
                                                       final InvokeContext invokeContext);

    /**
     * Create invoke future with {@link InvokeContext}.
     * @param conn
     * @param request
     * @param invokeContext
     * @param invokeCallback
     * @return
     */
    protected abstract InvokeFuture createInvokeFuture(final Connection conn,
                                                       final RemotingCommand request,
                                                       final InvokeContext invokeContext,
                                                       final InvokeCallback invokeCallback);

    protected CommandFactory getCommandFactory() {
        return commandFactory;
    }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alipay.remoting.config.switches;

import java.util.BitSet;

/**
 * Switches used in protocol, this is runtime switch.
 *
 * @author tsui
 * @version $Id: RpcProtocolSwitch.java, v 0.1 2017-09-31 15:50 tsui Exp $
 */
public class ProtocolSwitch implements Switch {

    // switche index
    public static final int     CRC_SWITCH_INDEX         = 0x000;

    // default value
    public static final boolean CRC_SWITCH_DEFAULT_VALUE = true;

    /** protocol switches */
    private BitSet              bs                       = new BitSet();

    // ~~~ public methods

    @Override
    public void turnOn(int index) {
        this.bs.set(index);
    }

    @Override
    public void turnOff(int index) {
        this.bs.clear(index);
    }

    @Override
    public boolean isOn(int index) {
        return this.bs.get(index);
    }

    /**
     * generate byte value according to the bit set in ProtocolSwitchStatus
     */
    public byte toByte() {
        return toByte(this.bs);
    }

    //~~~ static methods

    /**
     * check switch status whether on according to specified value
     *
     * @param switchIndex
     * @param value
     * @return
     */
    public static boolean isOn(int switchIndex, int value) {
        return toBitSet(value).get(switchIndex);
    }

    /**
     * create an instance of {@link ProtocolSwitch} according to byte value
     *
     * @param value
     * @return ProtocolSwitchStatus with initialized bit set.
     */
    public static ProtocolSwitch create(int value) {
        ProtocolSwitch status = new ProtocolSwitch();
        status.setBs(toBitSet(value));
        return status;
    }

    /**
     * create an instance of {@link ProtocolSwitch} according to switch index
     *
     * @param index the switch index which you want to set true
     * @return ProtocolSwitchStatus with initialized bit set.
     */
    public static ProtocolSwitch create(int[] index) {
        ProtocolSwitch status = new ProtocolSwitch();
        for (int i = 0; i < index.length; ++i) {
            status.turnOn(index[i]);
        }
        return status;
    }

    /**
     * from bit set to byte
     * @param bs
     * @return byte represent the bit set
     */
    public static byte toByte(BitSet bs) {
        int value = 0;
        for (int i = 0; i < bs.length(); ++i) {
            if (bs.get(i)) {
                value += 1 << i;
            }
        }
        if (bs.length() > 7) {
            throw new IllegalArgumentException("The byte value " + value
                                               + " generated according to bit set " + bs
                                               + " is out of range, should be limited between ["
                                               + Byte.MIN_VALUE + "] to [" + Byte.MAX_VALUE + "]");
        }
        return (byte) value;
    }

    /**
     * from byte to bit set
     * @param value
     * @return bit set represent the byte
     */
    public static BitSet toBitSet(int value) {
        if (value > Byte.MAX_VALUE || value < Byte.MIN_VALUE) {
            throw new IllegalArgumentException(
                "The value " + value + " is out of byte range, should be limited between ["
                        + Byte.MIN_VALUE + "] to [" + Byte.MAX_VALUE + "]");
        }
        BitSet bs = new BitSet();
        int index = 0;
        while (value != 0) {
            if (value % 2 != 0) {
                bs.set(index);
            }
            ++index;
            value = (byte) (value >> 1);
        }
        return bs;
    }

    // ~~~ getter and setters

    /**
     * Setter method for property <tt>bs<tt>.
     *
     * @param bs value to be assigned to property bs
     */
    public void setBs(BitSet bs) {
        this.bs = bs;
    }

}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alipay.remoting.rpc;

import com.alipay.remoting.CommandFactory;
import com.alipay.remoting.Connection;
import com.alipay.remoting.DefaultConnectionManager;
import com.alipay.remoting.InvokeCallback;
import com.alipay.remoting.InvokeContext;
import com.alipay.remoting.RemotingAddressParser;
import com.alipay.remoting.RemotingCommand;
import com.alipay.remoting.Url;
import com.alipay.remoting.exception.RemotingException;
import com.alipay.remoting.util.RemotingUtil;

/**
 * Rpc client remoting
 *
 * @author xiaomin.cxm
 * @version $Id: RpcClientRemoting.java, v 0.1 Apr 14, 2016 11:58:56 AM xiaomin.cxm Exp $
 */
public class RpcClientRemoting extends RpcRemoting {

    public RpcClientRemoting(CommandFactory commandFactory, RemotingAddressParser addressParser,
                             DefaultConnectionManager connectionManager) {
        super(commandFactory, addressParser, connectionManager);
    }

    /**
     * @see com.alipay.remoting.rpc.RpcRemoting#oneway(com.alipay.remoting.Url, java.lang.Object, InvokeContext)
     */
    @Override
    public void oneway(Url url, Object request, InvokeContext invokeContext)
                                                                            throws RemotingException,
                                                                            InterruptedException {
        final Connection conn = getConnectionAndInitInvokeContext(url, invokeContext);
        this.connectionManager.check(conn);
        this.oneway(conn, request, invokeContext);
    }

    /**
     * @see com.alipay.remoting.rpc.RpcRemoting#invokeSync(com.alipay.remoting.Url, java.lang.Object, InvokeContext, int)
     */
    @Override
    public Object invokeSync(Url url, Object request, InvokeContext invokeContext, int timeoutMillis)
                                                                                throws RemotingException,
                                                                                InterruptedException {
        final Connection conn = getConnectionAndInitInvokeContext(url, invokeContext);
        this.connectionManager.check(conn);
        return this.invokeSync(conn, request, invokeContext, timeoutMillis);
    }

    /**
     * @see com.alipay.remoting.rpc.RpcRemoting#invokeWithFuture(com.alipay.remoting.Url, java.lang.Object, InvokeContext, int)
     */
    @Override
    public RpcResponseFuture invokeWithFuture(Url url, Object request, InvokeContext invokeContext,
                                              int timeoutMillis) throws RemotingException,
                                                                InterruptedException {
        final Connection conn = getConnectionAndInitInvokeContext(url, invokeContext);
        this.connectionManager.check(conn);
        return this.invokeWithFuture(conn, request, invokeContext, timeoutMillis);
    }

    /**
     * @see com.alipay.remoting.rpc.RpcRemoting#invokeWithCallback(com.alipay.remoting.Url, java.lang.Object, InvokeContext, com.alipay.remoting.InvokeCallback, int)
     */
    @Override
    public void invokeWithCallback(Url url, Object request, InvokeContext invokeContext,
                                   InvokeCallback invokeCallback, int timeoutMillis)
                                                                                throws RemotingException,
                                                                                InterruptedException {
        final Connection conn = getConnectionAndInitInvokeContext(url, invokeContext);
        this.connectionManager.check(conn);
        this.invokeWithCallback(conn, request, invokeContext, invokeCallback, timeoutMillis);
    }

    /**
     * @see RpcRemoting#preProcessInvokeContext(InvokeContext, RemotingCommand, Connection)
     */
    @Override
    protected void preProcessInvokeContext(InvokeContext invokeContext, RemotingCommand cmd,
                                           Connection connection) {
        if (null != invokeContext) {
            invokeContext.putIfAbsent(InvokeContext.CLIENT_LOCAL_IP,
                RemotingUtil.parseLocalIP(connection.getChannel()));
            invokeContext.putIfAbsent(InvokeContext.CLIENT_LOCAL_PORT,
                RemotingUtil.parseLocalPort(connection.getChannel()));
            invokeContext.putIfAbsent(InvokeContext.CLIENT_REMOTE_IP,
                RemotingUtil.parseRemoteIP(connection.getChannel()));
            invokeContext.putIfAbsent(InvokeContext.CLIENT_REMOTE_PORT,
                RemotingUtil.parseRemotePort(connection.getChannel()));
            invokeContext.putIfAbsent(InvokeContext.BOLT_INVOKE_REQUEST_ID, cmd.getId());
        }
    }

    /**
     * Get connection and set init invokeContext if invokeContext not {@code null}
     *
     * @param url target url
     * @param invokeContext invoke context to set
     * @return connection
     */
    protected Connection getConnectionAndInitInvokeContext(Url url, InvokeContext invokeContext)
                                                                                throws RemotingException,
                                                                                InterruptedException {
        long start = System.currentTimeMillis();
        Connection conn;
        try {
            conn = this.connectionManager.getAndCreateIfAbsent(url);
        } finally {
            if (null != invokeContext) {
                invokeContext.putIfAbsent(InvokeContext.CLIENT_CONN_CREATETIME,
                    (System.currentTimeMillis() - start));
            }
        }
        return conn;
    }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alipay.remoting;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;

import org.slf4j.Logger;

import com.alipay.remoting.exception.RemotingException;
import com.alipay.remoting.log.BoltLoggerFactory;

/**
 * Reconnect manager.
 *
 * @author yunliang.shi
 * @version $Id: ReconnectManager.java, v 0.1 Mar 11, 2016 5:20:50 PM yunliang.shi Exp $
 */
public class ReconnectManager {
    private static final Logger logger = BoltLoggerFactory.getLogger("CommonDefault");

    class ReconnectTask {
        Url url;
    }

    private final LinkedBlockingQueue<ReconnectTask> tasks                  = new LinkedBlockingQueue<ReconnectTask>();

    protected final List<Url/* url */>              canceled               = new CopyOnWriteArrayList<Url>();
    private volatile boolean                         started;

    private int                                      healConnectionInterval = 1000;

    private final Thread                             healConnectionThreads;

    private ConnectionManager                        connectionManager;

    public ReconnectManager(ConnectionManager connectionManager) {
        this.connectionManager = connectionManager;
        this.healConnectionThreads = new Thread(new HealConnectionRunner());
        this.healConnectionThreads.start();
        this.started = true;
    }

    private void doReconnectTask(ReconnectTask task) throws InterruptedException, RemotingException {
        connectionManager.createConnectionAndHealIfNeed(task.url);
    }

    private void addReconnectTask(ReconnectTask task) {
        tasks.add(task);
    }

    public void addCancelUrl(Url url) {
        canceled.add(url);
    }

    public void removeCancelUrl(Url url) {
        canceled.remove(url);
    }

    /**
     * add reconnect task
     *
     * @param url
     */
    public void addReconnectTask(Url url) {
        ReconnectTask task = new ReconnectTask();
        task.url = url;
        tasks.add(task);
    }

    /**
     * Check task whether is valid, if canceled, is not valid
     *
     * @param task
     * @return
     */
    private boolean isValidTask(ReconnectTask task) {
        return !canceled.contains(task.url);
    }

    /**
     * stop reconnect thread
     */
    public void stop() {
        if (!this.started) {
            return;
        }
        this.started = false;
        healConnectionThreads.interrupt();
        this.tasks.clear();
        this.canceled.clear();
    }

    /**
     * heal connection thread
     *
     * @author yunliang.shi
     * @version $Id: ReconnectManager.java, v 0.1 Mar 11, 2016 5:24:08 PM yunliang.shi Exp $
     */
    private final class HealConnectionRunner implements Runnable {
        private long lastConnectTime = -1;

        @Override
        public void run() {
            while (ReconnectManager.this.started) {
                long start = -1;
                ReconnectTask task = null;
                try {
                    if (this.lastConnectTime > 0
                        && this.lastConnectTime < ReconnectManager.this.healConnectionInterval
                        || this.lastConnectTime < 0) {
                        Thread.sleep(ReconnectManager.this.healConnectionInterval);
                    }
                    try {
                        task = ReconnectManager.this.tasks.take();
                    } catch (InterruptedException e) {
                        // ignore
                    }

                    start = System.currentTimeMillis();
                    if (ReconnectManager.this.isValidTask(task)) {
                        try {
                            ReconnectManager.this.doReconnectTask(task);
                        } catch (InterruptedException e) {
                            throw e;
                        }
                    } else {
                        logger.warn("Invalid reconnect request task {}, cancel list size {}",
                            task.url, canceled.size());
                    }
                    this.lastConnectTime = System.currentTimeMillis() - start;
                } catch (Exception e) {
                    retryWhenException(start, task, e);
                }
            }
        }

        private void retryWhenException(long start, ReconnectTask task, Exception e) {
            if (start != -1) {
                this.lastConnectTime = System.currentTimeMillis() - start;
            }
            if (task != null) {
                logger.warn("reconnect target: {} failed.", task.url, e);
                ReconnectManager.this.addReconnectTask(task);
            }
        }
    }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alipay.remoting.rpc;

import org.slf4j.Logger;

import com.alipay.remoting.BaseRemoting;
import com.alipay.remoting.CommandFactory;
import com.alipay.remoting.Connection;
import com.alipay.remoting.DefaultConnectionManager;
import com.alipay.remoting.InvokeCallback;
import com.alipay.remoting.InvokeContext;
import com.alipay.remoting.InvokeFuture;
import com.alipay.remoting.RemotingAddressParser;
import com.alipay.remoting.RemotingCommand;
import com.alipay.remoting.Url;
import com.alipay.remoting.config.switches.ProtocolSwitch;
import com.alipay.remoting.exception.CodecException;
import com.alipay.remoting.exception.RemotingException;
import com.alipay.remoting.exception.SerializationException;
import com.alipay.remoting.log.BoltLoggerFactory;
import com.alipay.remoting.rpc.protocol.RpcProtocolManager;
import com.alipay.remoting.rpc.protocol.RpcRequestCommand;
import com.alipay.remoting.util.RemotingUtil;

/**
 * Rpc remoting capability.
 *
 * @author jiangping
 * @version $Id: RpcRemoting.java, v 0.1 Mar 6, 2016 9:09:48 PM tao Exp $
 */
public abstract class RpcRemoting extends BaseRemoting {
    static {
        RpcProtocolManager.initProtocols();
    }
    /** logger */
    private static final Logger        logger = BoltLoggerFactory.getLogger("RpcRemoting");

    /** address parser to get custom args */
    protected RemotingAddressParser    addressParser;

    /** connection manager */
    protected DefaultConnectionManager connectionManager;

    /**
     * default constructor
     */
    public RpcRemoting(CommandFactory commandFactory) {
        super(commandFactory);
    }

    /**
     * @param addressParser
     * @param connectionManager
     */
    public RpcRemoting(CommandFactory commandFactory, RemotingAddressParser addressParser,
                       DefaultConnectionManager connectionManager) {
        this(commandFactory);
        this.addressParser = addressParser;
        this.connectionManager = connectionManager;
    }

    /**
     * Oneway rpc invocation.

     * Notice! DO NOT modify the request object concurrently when this method is called.
     *
     * @param addr
     * @param request
     * @param invokeContext
     * @throws RemotingException
     * @throws InterruptedException
     */
    public void oneway(final String addr, final Object request, final InvokeContext invokeContext)
                                                                                throws RemotingException,
                                                                                InterruptedException {
        Url url = this.addressParser.parse(addr);
        this.oneway(url, request, invokeContext);
    }

    /**
     * Oneway rpc invocation.

     * Notice! DO NOT modify the request object concurrently when this method is called.
     *
     * @param url
     * @param request
     * @param invokeContext
     * @throws RemotingException
     * @throws InterruptedException
     */
    public abstract void oneway(final Url url, final Object request,
                                final InvokeContext invokeContext) throws RemotingException,
                                                                  InterruptedException;

    /**
     * Oneway rpc invocation.

     * Notice! DO NOT modify the request object concurrently when this method is called.
     *
     * @param conn
     * @param request
     * @param invokeContext
     * @throws RemotingException
     */
    public void oneway(final Connection conn, final Object request,
                       final InvokeContext invokeContext) throws RemotingException {
        RequestCommand requestCommand = (RequestCommand) toRemotingCommand(request, conn,
            invokeContext, -1);
        requestCommand.setType(RpcCommandType.REQUEST_ONEWAY);
        preProcessInvokeContext(invokeContext, requestCommand, conn);
        super.oneway(conn, requestCommand);
    }

    /**
     * Synchronous rpc invocation.

     * Notice! DO NOT modify the request object concurrently when this method is called.
     *
     * @param addr
     * @param request
     * @param invokeContext
     * @param timeoutMillis
     * @return
     * @throws RemotingException
     * @throws InterruptedException
     */
    public Object invokeSync(final String addr, final Object request,
                             final InvokeContext invokeContext, final int timeoutMillis)
                                                                                throws RemotingException,
                                                                                InterruptedException {
        Url url = this.addressParser.parse(addr);
        return this.invokeSync(url, request, invokeContext, timeoutMillis);
    }

    /**
     * Synchronous rpc invocation.

     * Notice! DO NOT modify the request object concurrently when this method is called.
     *
     * @param url
     * @param request
     * @param invokeContext
     * @param timeoutMillis
     * @return
     * @throws RemotingException
     * @throws InterruptedException
     */
    public abstract Object invokeSync(final Url url, final Object request,
                                      final InvokeContext invokeContext, final int timeoutMillis)
                                                                                throws RemotingException,
                                                                                InterruptedException;

    /**
     * Synchronous rpc invocation.

     * Notice! DO NOT modify the request object concurrently when this method is called.
     *
     * @param conn
     * @param request
     * @param invokeContext
     * @param timeoutMillis
     * @return
     * @throws RemotingException
     * @throws InterruptedException
     */
    public Object invokeSync(final Connection conn, final Object request,
                             final InvokeContext invokeContext, final int timeoutMillis)
                                                                                throws RemotingException,
                                                                                InterruptedException {
        RemotingCommand requestCommand = toRemotingCommand(request, conn, invokeContext,
            timeoutMillis);
        preProcessInvokeContext(invokeContext, requestCommand, conn);
        ResponseCommand responseCommand = (ResponseCommand) super.invokeSync(conn, requestCommand,
            timeoutMillis);
        responseCommand.setInvokeContext(invokeContext);

        Object responseObject = RpcResponseResolver.resolveResponseObject(responseCommand,
            RemotingUtil.parseRemoteAddress(conn.getChannel()));
        return responseObject;
    }

    /**
     * Rpc invocation with future returned.

     * Notice! DO NOT modify the request object concurrently when this method is called.
     *
     * @param addr
     * @param request
     * @param invokeContext
     * @param timeoutMillis
     * @return
     * @throws RemotingException
     * @throws InterruptedException
     */
    public RpcResponseFuture invokeWithFuture(final String addr, final Object request,
                                              final InvokeContext invokeContext, int timeoutMillis)
                                                                                throws RemotingException,
                                                                                InterruptedException {
        Url url = this.addressParser.parse(addr);
        return this.invokeWithFuture(url, request, invokeContext, timeoutMillis);
    }

    /**
     * Rpc invocation with future returned.

     * Notice! DO NOT modify the request object concurrently when this method is called.
     *
     * @param url
     * @param request
     * @param invokeContext
     * @param timeoutMillis
     * @return
     * @throws RemotingException
     * @throws InterruptedException
     */
    public abstract RpcResponseFuture invokeWithFuture(final Url url, final Object request,
                                                       final InvokeContext invokeContext,
                                                       final int timeoutMillis)
                                                                               throws RemotingException,
                                                                               InterruptedException;

    /**
     * Rpc invocation with future returned.

     * Notice! DO NOT modify the request object concurrently when this method is called.
     *
     * @param conn
     * @param request
     * @param invokeContext
     * @param timeoutMillis
     * @return
     * @throws RemotingException
     */
    public RpcResponseFuture invokeWithFuture(final Connection conn, final Object request,
                                              final InvokeContext invokeContext,
                                              final int timeoutMillis) throws RemotingException {

        RemotingCommand requestCommand = toRemotingCommand(request, conn, invokeContext,
            timeoutMillis);

        preProcessInvokeContext(invokeContext, requestCommand, conn);
        InvokeFuture future = super.invokeWithFuture(conn, requestCommand, timeoutMillis);
        return new RpcResponseFuture(RemotingUtil.parseRemoteAddress(conn.getChannel()), future);
    }

    /**
     * Rpc invocation with callback.

     * Notice! DO NOT modify the request object concurrently when this method is called.
     *
     * @param addr
     * @param request
     * @param invokeContext
     * @param invokeCallback
     * @param timeoutMillis
     * @throws RemotingException
     * @throws InterruptedException
     */
    public void invokeWithCallback(String addr, Object request, final InvokeContext invokeContext,
                                   InvokeCallback invokeCallback, int timeoutMillis)
                                                                                throws RemotingException,
                                                                                InterruptedException {
        Url url = this.addressParser.parse(addr);
        this.invokeWithCallback(url, request, invokeContext, invokeCallback, timeoutMillis);
    }

    /**
     * Rpc invocation with callback.

     * Notice! DO NOT modify the request object concurrently when this method is called.
     *
     * @param url
     * @param request
     * @param invokeContext
     * @param invokeCallback
     * @param timeoutMillis
     * @throws RemotingException
     * @throws InterruptedException
     */
    public abstract void invokeWithCallback(final Url url, final Object request,
                                            final InvokeContext invokeContext,
                                            final InvokeCallback invokeCallback,
                                            final int timeoutMillis) throws RemotingException,
                                                                    InterruptedException;

    /**
     * Rpc invocation with callback.

     * Notice! DO NOT modify the request object concurrently when this method is called.
     *
     * @param conn
     * @param request
     * @param invokeContext
     * @param invokeCallback
     * @param timeoutMillis
     * @throws RemotingException
     */
    public void invokeWithCallback(final Connection conn, final Object request,
                                   final InvokeContext invokeContext,
                                   final InvokeCallback invokeCallback, final int timeoutMillis)
                                                                                throws RemotingException {
        RemotingCommand requestCommand = toRemotingCommand(request, conn, invokeContext,
            timeoutMillis);
        preProcessInvokeContext(invokeContext, requestCommand, conn);
        super.invokeWithCallback(conn, requestCommand, invokeCallback, timeoutMillis);
    }

    /**
     * Convert application request object to remoting request command.
     *
     * @param request
     * @param conn
     * @param timeoutMillis
     * @return
     * @throws CodecException
     */
    protected RemotingCommand toRemotingCommand(Object request, Connection conn,
                                                InvokeContext invokeContext, int timeoutMillis)
                                                                                throws SerializationException {
        RpcRequestCommand command = this.getCommandFactory().createRequestCommand(request);

        if (null != invokeContext) {
            // set client custom serializer for request command if not null
            Object clientCustomSerializer = invokeContext.get(InvokeContext.BOLT_CUSTOM_SERIALIZER);
            if (null != clientCustomSerializer) {
                try {
                    command.setSerializer((Byte) clientCustomSerializer);
                } catch (ClassCastException e) {
                    throw new IllegalArgumentException(
                        "Illegal custom serializer [" + clientCustomSerializer
                                + "], the type of value should be [byte], but now is ["
                                + clientCustomSerializer.getClass().getName() + "].");
                }
            }

            // enable crc by default, user can disable by set invoke context `false` for key `InvokeContext.BOLT_CRC_SWITCH`
            Boolean crcSwitch = invokeContext.get(InvokeContext.BOLT_CRC_SWITCH,
                ProtocolSwitch.CRC_SWITCH_DEFAULT_VALUE);
            if (null != crcSwitch && crcSwitch) {
                command.setProtocolSwitch(ProtocolSwitch
                    .create(new int[] { ProtocolSwitch.CRC_SWITCH_INDEX }));
            }
        } else {
            // enable crc by default, if there is no invoke context.
            command.setProtocolSwitch(ProtocolSwitch
                .create(new int[] { ProtocolSwitch.CRC_SWITCH_INDEX }));
        }
        command.setTimeout(timeoutMillis);
        command.setRequestClass(request.getClass().getName());
        command.setInvokeContext(invokeContext);
        command.serialize();
        logDebugInfo(command);
        return command;
    }

    protected abstract void preProcessInvokeContext(InvokeContext invokeContext,
                                                    RemotingCommand cmd, Connection connection);

    /**
     * @param requestCommand
     */
    private void logDebugInfo(RemotingCommand requestCommand) {
        if (logger.isDebugEnabled()) {
            logger.debug("Send request, requestId=" + requestCommand.getId());
        }
    }

    /**
     * @see com.alipay.remoting.BaseRemoting#createInvokeFuture(com.alipay.remoting.RemotingCommand, com.alipay.remoting.InvokeContext)
     */
    @Override
    protected InvokeFuture createInvokeFuture(RemotingCommand request, InvokeContext invokeContext) {
        return new DefaultInvokeFuture(request.getId(), null, null, request.getProtocolCode()
            .getFirstByte(), this.getCommandFactory(), invokeContext);
    }

    /**
     * @see com.alipay.remoting.BaseRemoting#createInvokeFuture(Connection, RemotingCommand, InvokeContext, InvokeCallback)
     */
    @Override
    protected InvokeFuture createInvokeFuture(Connection conn, RemotingCommand request,
                                              InvokeContext invokeContext,
                                              InvokeCallback invokeCallback) {
        return new DefaultInvokeFuture(request.getId(), new RpcInvokeCallbackListener(
            RemotingUtil.parseRemoteAddress(conn.getChannel())), invokeCallback, request
            .getProtocolCode().getFirstByte(), this.getCommandFactory(), invokeContext);
    }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alipay.remoting;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;

import com.alipay.remoting.config.ConfigManager;
import com.alipay.remoting.config.Configs;
import com.alipay.remoting.log.BoltLoggerFactory;
import com.alipay.remoting.util.FutureTaskUtil;
import com.alipay.remoting.util.RemotingUtil;
import com.alipay.remoting.util.RunStateRecordedFutureTask;

/**
 * An implemented strategy to monitor connections:
 *   <lu>
 *
each time scheduled, filter connections with {@link Configs#CONN_SERVICE_STATUS_OFF} at first.

 *
then close connections.

 *   </lu>
 *
 * @author tsui
 * @version $Id: ScheduledDisconnectStrategy.java, v 0.1 2017-02-21 14:14 tsui Exp $
 */
public class ScheduledDisconnectStrategy implements ConnectionMonitorStrategy {
    private static final Logger     logger                 = BoltLoggerFactory
                                                               .getLogger("CommonDefault");

    /** the connections threshold of each {@link Url#uniqueKey} */
    private static final int        CONNECTION_THRESHOLD   = ConfigManager.conn_threshold();

    /** fresh select connections to be closed */
    private Map<String, Connection> freshSelectConnections = new ConcurrentHashMap<String, Connection>();

    /** Retry detect period for ScheduledDisconnectStrategy*/
    private static int              RETRY_DETECT_PERIOD    = ConfigManager.retry_detect_period();

    /** random */
    private Random                  random                 = new Random();

    /**
     * Filter connections to monitor
     *
     * @param connections
     */
    @Override
    public Map<String, List<Connection>> filter(List<Connection> connections) {
        List<Connection> serviceOnConnections = new ArrayList<Connection>();
        List<Connection> serviceOffConnections = new ArrayList<Connection>();
        Map<String, List<Connection>> filteredConnections = new ConcurrentHashMap<String, List<Connection>>();

        for (Connection connection : connections) {
            String serviceStatus = (String) connection.getAttribute(Configs.CONN_SERVICE_STATUS);
            if (serviceStatus != null) {
                if (connection.isInvokeFutureMapFinish()
                    && !freshSelectConnections.containsValue(connection)) {
                    serviceOffConnections.add(connection);
                }
            } else {
                serviceOnConnections.add(connection);
            }
        }

        filteredConnections.put(Configs.CONN_SERVICE_STATUS_ON, serviceOnConnections);
        filteredConnections.put(Configs.CONN_SERVICE_STATUS_OFF, serviceOffConnections);
        return filteredConnections;
    }

    /**
     * Monitor connections and close connections with status is off
     *
     * @param connPools
     */
    @Override
    public void monitor(Map<String, RunStateRecordedFutureTask<ConnectionPool>> connPools) {
        try {
            if (null != connPools && !connPools.isEmpty()) {
                Iterator<Map.Entry<String, RunStateRecordedFutureTask<ConnectionPool>>> iter = connPools
                    .entrySet().iterator();

                while (iter.hasNext()) {
                    Map.Entry<String, RunStateRecordedFutureTask<ConnectionPool>> entry = iter
                        .next();
                    String poolKey = entry.getKey();
                    ConnectionPool pool = FutureTaskUtil.getFutureTaskResult(entry.getValue(),
                        logger);

                    List<Connection> connections = pool.getAll();
                    Map<String, List<Connection>> filteredConnectons = this.filter(connections);
                    List<Connection> serviceOnConnections = filteredConnectons
                        .get(Configs.CONN_SERVICE_STATUS_ON);
                    List<Connection> serviceOffConnections = filteredConnectons
                        .get(Configs.CONN_SERVICE_STATUS_OFF);
                    if (serviceOnConnections.size() > CONNECTION_THRESHOLD) {
                        Connection freshSelectConnect = serviceOnConnections.get(random
                            .nextInt(serviceOnConnections.size()));
                        freshSelectConnect.setAttribute(Configs.CONN_SERVICE_STATUS,
                            Configs.CONN_SERVICE_STATUS_OFF);

                        Connection lastSelectConnect = freshSelectConnections.remove(poolKey);
                        freshSelectConnections.put(poolKey, freshSelectConnect);

                        closeFreshSelectConnections(lastSelectConnect, serviceOffConnections);

                    } else {
                        if (freshSelectConnections.containsKey(poolKey)) {
                            Connection lastSelectConnect = freshSelectConnections.remove(poolKey);
                            closeFreshSelectConnections(lastSelectConnect, serviceOffConnections);
                        }
                        if (logger.isInfoEnabled()) {
                            logger
                                .info(
                                    "the size of serviceOnConnections [{}] reached CONNECTION_THRESHOLD [{}].",
                                    serviceOnConnections.size(), CONNECTION_THRESHOLD);
                        }
                    }

                    for (Connection offConn : serviceOffConnections) {
                        if (offConn.isFine()) {
                            offConn.close();
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error("ScheduledDisconnectStrategy monitor error", e);
        }
    }

    /**
     * close the connection of the fresh select connections
     *
     * @param lastSelectConnect
     * @param serviceOffConnections
     * @throws InterruptedException
     */
    private void closeFreshSelectConnections(Connection lastSelectConnect,
                                             List<Connection> serviceOffConnections)
                                                                                throws InterruptedException {
        if (null != lastSelectConnect) {
            if (lastSelectConnect.isInvokeFutureMapFinish()) {
                serviceOffConnections.add(lastSelectConnect);
            } else {
                Thread.sleep(RETRY_DETECT_PERIOD);
                if (lastSelectConnect.isInvokeFutureMapFinish()) {
                    serviceOffConnections.add(lastSelectConnect);
                } else {
                    if (logger.isInfoEnabled()) {
                        logger.info("Address={} won't close at this schedule turn",
                            RemotingUtil.parseRemoteAddress(lastSelectConnect.getChannel()));
                    }
                }
            }
        }
    }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alipay.remoting.rpc.protocol;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;

import org.slf4j.Logger;

import com.alipay.remoting.AbstractRemotingProcessor;
import com.alipay.remoting.CommandCode;
import com.alipay.remoting.CommandFactory;
import com.alipay.remoting.CommandHandler;
import com.alipay.remoting.CommonCommandCode;
import com.alipay.remoting.ProcessorManager;
import com.alipay.remoting.RemotingCommand;
import com.alipay.remoting.RemotingContext;
import com.alipay.remoting.RemotingProcessor;
import com.alipay.remoting.ResponseStatus;
import com.alipay.remoting.log.BoltLoggerFactory;
import com.alipay.remoting.rpc.RequestCommand;
import com.alipay.remoting.rpc.ResponseCommand;
import com.alipay.remoting.rpc.RpcCommand;
import com.alipay.remoting.rpc.RpcCommandType;
import com.alipay.remoting.rpc.RpcConfigManager;

import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler.Sharable;

/**
 * Rpc command handler.
 *
 * @author jiangping
 * @version $Id: RpcServerHandler.java, v 0.1 2015-8-31 PM7:43:06 tao Exp $
 */
@Sharable
public class RpcCommandHandler implements CommandHandler {

    private static final Logger logger = BoltLoggerFactory.getLogger("RpcRemoting");
    /** All processors */
    ProcessorManager            processorManager;

    CommandFactory              commandFactory;

    /**
     * Constructor. Initialize the processor manager and register processors.
     */
    public RpcCommandHandler(CommandFactory commandFactory) {
        this.commandFactory = commandFactory;
        this.processorManager = new ProcessorManager();
        //process request
        this.processorManager.registerProcessor(RpcCommandCode.RPC_REQUEST,
            new RpcRequestProcessor(this.commandFactory));
        //process response
        this.processorManager.registerProcessor(RpcCommandCode.RPC_RESPONSE,
            new RpcResponseProcessor());

        this.processorManager.registerProcessor(CommonCommandCode.HEARTBEAT,
            new RpcHeartBeatProcessor());

        this.processorManager
            .registerDefaultProcessor(new AbstractRemotingProcessor<RemotingCommand>() {
                @Override
                public void doProcess(RemotingContext ctx, RemotingCommand msg) throws Exception {
                    logger.error("No processor available for command code {}, msgId {}",
                        msg.getCmdCode(), msg.getId());
                }
            });
    }

    /**
     * @see CommandHandler#handleCommand(RemotingContext, Object)
     */
    @Override
    public void handleCommand(RemotingContext ctx, Object msg) throws Exception {
        this.handle(ctx, msg);
    }

    /*
     * Handle the request(s).
     */
    private void handle(final RemotingContext ctx, final Object msg) {
        try {
            if (msg instanceof List) {
                final Runnable handleTask = new Runnable() {
                    @Override
                    public void run() {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Batch message! size={}", ((List<?>) msg).size());
                        }
                        for (final Object m : (List<?>) msg) {
                            RpcCommandHandler.this.process(ctx, m);
                        }
                    }
                };
                if (RpcConfigManager.dispatch_msg_list_in_default_executor()) {
                    // If msg is list ,then the batch submission to biz threadpool can save io thread.
                    // See com.alipay.remoting.decoder.ProtocolDecoder
                    processorManager.getDefaultExecutor().execute(handleTask);
                } else {
                    handleTask.run();
                }
            } else {
                process(ctx, msg);
            }
        } catch (final Throwable t) {
            processException(ctx, msg, t);
        }
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void process(RemotingContext ctx, Object msg) {
        try {
            final RpcCommand cmd = (RpcCommand) msg;
            final RemotingProcessor processor = processorManager.getProcessor(cmd.getCmdCode());
            processor.process(ctx, cmd, processorManager.getDefaultExecutor());
        } catch (final Throwable t) {
            processException(ctx, msg, t);
        }
    }

    private void processException(RemotingContext ctx, Object msg, Throwable t) {
        if (msg instanceof List) {
            for (final Object m : (List<?>) msg) {
                processExceptionForSingleCommand(ctx, m, t);
            }
        } else {
            processExceptionForSingleCommand(ctx, msg, t);
        }
    }

    /*
     * Return error command if necessary.
     */
    private void processExceptionForSingleCommand(RemotingContext ctx, Object msg, Throwable t) {
        final int id = ((RpcCommand) msg).getId();
        final String emsg = "Exception caught when processing "
                            + ((msg instanceof RequestCommand) ? "request, id=" : "response, id=");
        logger.warn(emsg + id, t);
        if (msg instanceof RequestCommand) {
            final RequestCommand cmd = (RequestCommand) msg;
            if (cmd.getType() != RpcCommandType.REQUEST_ONEWAY) {
                if (t instanceof RejectedExecutionException) {
                    final ResponseCommand response = this.commandFactory.createExceptionResponse(
                        id, ResponseStatus.SERVER_THREADPOOL_BUSY);
                    // RejectedExecutionException here assures no response has been sent back
                    // Other exceptions should be processed where exception was caught, because here we don't known whether ack had been sent back.
                    ctx.getChannelContext().writeAndFlush(response)
                        .addListener(new ChannelFutureListener() {
                            @Override
                            public void operationComplete(ChannelFuture future) throws Exception {
                                if (future.isSuccess()) {
                                    if (logger.isInfoEnabled()) {
                                        logger
                                            .info(
                                                "Write back exception response done, requestId={}, status={}",
                                                id, response.getResponseStatus());
                                    }
                                } else {
                                    logger.error(
                                        "Write back exception response failed, requestId={}", id,
                                        future.cause());
                                }
                            }

                        });
                }
            }
        }
    }

    /**
     * @see CommandHandler#registerProcessor(com.alipay.remoting.CommandCode, RemotingProcessor)
     */
    @Override
    public void registerProcessor(CommandCode cmd,
                                  @SuppressWarnings("rawtypes") RemotingProcessor processor) {
        this.processorManager.registerProcessor(cmd, processor);
    }

    /**
     * @see CommandHandler#registerDefaultExecutor(java.util.concurrent.ExecutorService)
     */
    @Override
    public void registerDefaultExecutor(ExecutorService executor) {
        this.processorManager.registerDefaultExecutor(executor);
    }

    /**
     * @see CommandHandler#getDefaultExecutor()
     */
    @Override
    public ExecutorService getDefaultExecutor() {
        return this.processorManager.getDefaultExecutor();
    }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alipay.remoting.config;

/**
 * get configs through system properties prior to default value
 *
 * @author tsui
 * @version $Id: ConfigManager.java, v 0.1 2017-08-03 19:21 tsui Exp $
 */
public class ConfigManager {
    // ~~~ properties for bootstrap
    public static boolean tcp_nodelay() {
        return getBool(Configs.TCP_NODELAY, Configs.TCP_NODELAY_DEFAULT);
    }

    public static boolean tcp_so_reuseaddr() {
        return getBool(Configs.TCP_SO_REUSEADDR, Configs.TCP_SO_REUSEADDR_DEFAULT);
    }

    public static int tcp_so_backlog() {
        return getInt(Configs.TCP_SO_BACKLOG, Configs.TCP_SO_BACKLOG_DEFAULT);
    }

    public static boolean tcp_so_keepalive() {
        return getBool(Configs.TCP_SO_KEEPALIVE, Configs.TCP_SO_KEEPALIVE_DEFAULT);
    }

    public static int netty_io_ratio() {
        return getInt(Configs.NETTY_IO_RATIO, Configs.NETTY_IO_RATIO_DEFAULT);
    }

    public static boolean netty_buffer_pooled() {
        return getBool(Configs.NETTY_BUFFER_POOLED, Configs.NETTY_BUFFER_POOLED_DEFAULT);
    }

    public static int netty_buffer_low_watermark() {
        return getInt(Configs.NETTY_BUFFER_LOW_WATERMARK,
            Configs.NETTY_BUFFER_LOW_WATERMARK_DEFAULT);
    }

    public static int netty_buffer_high_watermark() {
        return getInt(Configs.NETTY_BUFFER_HIGH_WATERMARK,
            Configs.NETTY_BUFFER_HIGH_WATERMARK_DEFAULT);
    }

    public static boolean netty_epoll() {
        return getBool(Configs.NETTY_EPOLL_SWITCH, Configs.NETTY_EPOLL_SWITCH_DEFAULT);
    }

    public static boolean netty_epoll_lt_enabled() {
        return getBool(Configs.NETTY_EPOLL_LT, Configs.NETTY_EPOLL_LT_DEFAULT);
    }

    // ~~~ properties for idle
    public static boolean tcp_idle_switch() {
        return getBool(Configs.TCP_IDLE_SWITCH, Configs.TCP_IDLE_SWITCH_DEFAULT);
    }

    public static int tcp_idle() {
        return getInt(Configs.TCP_IDLE, Configs.TCP_IDLE_DEFAULT);
    }

    public static int tcp_idle_maxtimes() {
        return getInt(Configs.TCP_IDLE_MAXTIMES, Configs.TCP_IDLE_MAXTIMES_DEFAULT);
    }

    public static int tcp_server_idle() {
        return getInt(Configs.TCP_SERVER_IDLE, Configs.TCP_SERVER_IDLE_DEFAULT);
    }

    // ~~~ properties for connection manager
    public static int conn_create_tp_min_size() {
        return getInt(Configs.CONN_CREATE_TP_MIN_SIZE, Configs.CONN_CREATE_TP_MIN_SIZE_DEFAULT);
    }

    public static int conn_create_tp_max_size() {
        return getInt(Configs.CONN_CREATE_TP_MAX_SIZE, Configs.CONN_CREATE_TP_MAX_SIZE_DEFAULT);
    }

    public static int conn_create_tp_queue_size() {
        return getInt(Configs.CONN_CREATE_TP_QUEUE_SIZE, Configs.CONN_CREATE_TP_QUEUE_SIZE_DEFAULT);
    }

    public static int conn_create_tp_keepalive() {
        return getInt(Configs.CONN_CREATE_TP_KEEPALIVE_TIME,
            Configs.CONN_CREATE_TP_KEEPALIVE_TIME_DEFAULT);
    }

    // ~~~ properties for processor manager
    public static int default_tp_min_size() {
        return getInt(Configs.TP_MIN_SIZE, Configs.TP_MIN_SIZE_DEFAULT);
    }

    public static int default_tp_max_size() {
        return getInt(Configs.TP_MAX_SIZE, Configs.TP_MAX_SIZE_DEFAULT);
    }

    public static int default_tp_queue_size() {
        return getInt(Configs.TP_QUEUE_SIZE, Configs.TP_QUEUE_SIZE_DEFAULT);
    }

    public static int default_tp_keepalive_time() {
        return getInt(Configs.TP_KEEPALIVE_TIME, Configs.TP_KEEPALIVE_TIME_DEFAULT);
    }

    // ~~~ properties for reconnect manager
    public static boolean conn_reconnect_switch() {
        return getBool(Configs.CONN_RECONNECT_SWITCH, Configs.CONN_RECONNECT_SWITCH_DEFAULT);
    }

    // ~~~ properties for connection monitor
    public static boolean conn_monitor_switch() {
        return getBool(Configs.CONN_MONITOR_SWITCH, Configs.CONN_MONITOR_SWITCH_DEFAULT);
    }

    public static long conn_monitor_initial_delay() {
        return getLong(Configs.CONN_MONITOR_INITIAL_DELAY,
            Configs.CONN_MONITOR_INITIAL_DELAY_DEFAULT);
    }

    public static long conn_monitor_period() {
        return getLong(Configs.CONN_MONITOR_PERIOD, Configs.CONN_MONITOR_PERIOD_DEFAULT);
    }

    public static int conn_threshold() {
        return getInt(Configs.CONN_THRESHOLD, Configs.CONN_THRESHOLD_DEFAULT);
    }

    public static int retry_detect_period() {
        return getInt(Configs.RETRY_DETECT_PERIOD, Configs.RETRY_DETECT_PERIOD_DEFAULT);
    }

    // ~~~ properties for serializer
    public static final byte serializer = serializer();

    public static byte serializer() {
        return getByte(Configs.SERIALIZER, Configs.SERIALIZER_DEFAULT);
    }

    // ~~~ public helper methods to retrieve system property
    public static boolean getBool(String key, String defaultValue) {
        return Boolean.parseBoolean(System.getProperty(key, defaultValue));
    }

    public static int getInt(String key, String defaultValue) {
        return Integer.parseInt(System.getProperty(key, defaultValue));
    }

    public static byte getByte(String key, String defaultValue) {
        return Byte.parseByte(System.getProperty(key, defaultValue));
    }

    public static long getLong(String key, String defaultValue) {
        return Long.parseLong(System.getProperty(key, defaultValue));
    }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alipay.remoting.rpc;

import com.alipay.remoting.CommandCode;
import com.alipay.remoting.InvokeContext;
import com.alipay.remoting.ProtocolCode;
import com.alipay.remoting.RemotingCommand;
import com.alipay.remoting.config.ConfigManager;
import com.alipay.remoting.config.switches.ProtocolSwitch;
import com.alipay.remoting.exception.CodecException;
import com.alipay.remoting.exception.DeserializationException;
import com.alipay.remoting.exception.SerializationException;
import com.alipay.remoting.rpc.protocol.RpcDeserializeLevel;
import com.alipay.remoting.rpc.protocol.RpcProtocol;

/**
 * Remoting command.

 * A remoting command stands for a kind of transfer object in the network communication layer.
 *
 * @author jiangping
 * @version $Id: RpcCommand.java, v 0.1 2015-9-6 PM5:26:31 tao Exp $
 */
public abstract class RpcCommand implements RemotingCommand {

    /** For serialization  */
    private static final long serialVersionUID = -3570261012462596503L;

    /**
     * Code which stands for the command.
     */
    private CommandCode       cmdCode;
    /* command version */
    private byte              version          = 0x1;
    private byte              type;
    /**
     * Serializer, see the Configs.SERIALIZER_DEFAULT for the default serializer.
     * Notice: this can not be changed after initialized at runtime.
     */
    private byte              serializer       = ConfigManager.serializer;
    /**
     * protocol switches
     */
    private ProtocolSwitch    protocolSwitch   = new ProtocolSwitch();
    private int               id;
    /** The length of clazz */
    private short             clazzLength      = 0;
    private short             headerLength     = 0;
    private int               contentLength    = 0;
    /** The class of content */
    private byte[]            clazz;
    /** Header is used for transparent transmission. */
    private byte[]            header;
    /** The bytes format of the content of the command. */
    private byte[]            content;
    /** invoke context of each rpc command. */
    private InvokeContext     invokeContext;

    public RpcCommand() {
    }

    public RpcCommand(byte type) {
        this();
        this.type = type;
    }

    public RpcCommand(CommandCode cmdCode) {
        this();
        this.cmdCode = cmdCode;
    }

    public RpcCommand(byte type, CommandCode cmdCode) {
        this(cmdCode);
        this.type = type;
    }

    public RpcCommand(byte version, byte type, CommandCode cmdCode) {
        this(type, cmdCode);
        this.version = version;
    }

    /**
     * Serialize  the class header and content.
     *
     * @throws Exception
     */
    @Override
    public void serialize() throws SerializationException {
        this.serializeClazz();
        this.serializeHeader(this.invokeContext);
        this.serializeContent(this.invokeContext);
    }

    /**
     * Deserialize the class header and content.
     *
     * @throws Exception
     */
    @Override
    public void deserialize() throws DeserializationException {
        this.deserializeClazz();
        this.deserializeHeader(this.invokeContext);
        this.deserializeContent(this.invokeContext);
    }

    /**
     * Deserialize according to mask.
     *

     *
If mask <= {@link RpcDeserializeLevel#DESERIALIZE_CLAZZ}, only deserialize clazz - only one part.

     *
If mask <= {@link RpcDeserializeLevel#DESERIALIZE_HEADER}, deserialize clazz and header - two parts.

     *
If mask <= {@link RpcDeserializeLevel#DESERIALIZE_ALL}, deserialize clazz, header and content - all three parts.

     *

     *
     * @param mask
     * @throws CodecException
     */
    public void deserialize(long mask) throws DeserializationException {
        if (mask <= RpcDeserializeLevel.DESERIALIZE_CLAZZ) {
            this.deserializeClazz();
        } else if (mask <= RpcDeserializeLevel.DESERIALIZE_HEADER) {
            this.deserializeClazz();
            this.deserializeHeader(this.getInvokeContext());
        } else if (mask <= RpcDeserializeLevel.DESERIALIZE_ALL) {
            this.deserialize();
        }
    }

    /**
     * Serialize content class.
     *
     * @throws Exception
     */
    public void serializeClazz() throws SerializationException {

    }

    /**
     * Deserialize the content class.
     *
     * @throws Exception
     */
    public void deserializeClazz() throws DeserializationException {

    }

    /**
     * Serialize the header.
     *
     * @throws Exception
     */
    public void serializeHeader(InvokeContext invokeContext) throws SerializationException {
    }

    /**
     * Serialize the content.
     *
     * @throws Exception
     */
    @Override
    public void serializeContent(InvokeContext invokeContext) throws SerializationException {
    }

    /**
     * Deserialize the header.
     *
     * @throws Exception
     */
    public void deserializeHeader(InvokeContext invokeContext) throws DeserializationException {
    }

    /**
     * Deserialize the content.
     *
     * @throws Exception
     */
    @Override
    public void deserializeContent(InvokeContext invokeContext) throws DeserializationException {
    }

    @Override
    public ProtocolCode getProtocolCode() {
        return ProtocolCode.fromBytes(RpcProtocol.PROTOCOL_CODE);
    }

    @Override
    public CommandCode getCmdCode() {
        return cmdCode;
    }

    @Override
    public InvokeContext getInvokeContext() {
        return invokeContext;
    }

    @Override
    public byte getSerializer() {
        return serializer;
    }

    @Override
    public ProtocolSwitch getProtocolSwitch() {
        return protocolSwitch;
    }

    public void setCmdCode(CommandCode cmdCode) {
        this.cmdCode = cmdCode;
    }

    public byte getVersion() {
        return version;
    }

    public void setVersion(byte version) {
        this.version = version;
    }

    public byte getType() {
        return type;
    }

    public void setType(byte type) {
        this.type = type;
    }

    public void setSerializer(byte serializer) {
        this.serializer = serializer;
    }

    public void setProtocolSwitch(ProtocolSwitch protocolSwitch) {
        this.protocolSwitch = protocolSwitch;
    }

    @Override
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public byte[] getHeader() {
        return header;
    }

    public void setHeader(byte[] header) {
        if (header != null) {
            this.header = header;
            this.headerLength = (short) header.length;
        }
    }

    public byte[] getContent() {
        return content;
    }

    public void setContent(byte[] content) {
        if (content != null) {
            this.content = content;
            this.contentLength = content.length;
        }
    }

    public short getHeaderLength() {
        return headerLength;
    }

    public int getContentLength() {
        return contentLength;
    }

    public short getClazzLength() {
        return clazzLength;
    }

    public byte[] getClazz() {
        return clazz;
    }

    public void setClazz(byte[] clazz) {
        if (clazz != null) {
            this.clazz = clazz;
            this.clazzLength = (short) clazz.length;
        }
    }

    public void setInvokeContext(InvokeContext invokeContext) {
        this.invokeContext = invokeContext;
    }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alipay.remoting.rpc.protocol;

import java.io.UnsupportedEncodingException;

import com.alipay.remoting.CustomSerializer;
import com.alipay.remoting.CustomSerializerManager;
import com.alipay.remoting.InvokeContext;
import com.alipay.remoting.config.Configs;
import com.alipay.remoting.exception.DeserializationException;
import com.alipay.remoting.exception.SerializationException;
import com.alipay.remoting.rpc.RequestCommand;
import com.alipay.remoting.serialization.SerializerManager;
import com.alipay.remoting.util.IDGenerator;

/**
 * Request command for Rpc.
 *
 * @author jiangping
 * @version $Id: RpcRequestCommand.java, v 0.1 2015-9-25 PM2:13:35 tao Exp $
 */
public class RpcRequestCommand extends RequestCommand {
    /** For serialization  */
    private static final long serialVersionUID = -4602613826188210946L;
    private Object            requestObject;
    private String            requestClass;

    private CustomSerializer  customSerializer;
    private Object            requestHeader;

    private transient long    arriveTime       = -1;

    /**
     * create request command without id
     */
    public RpcRequestCommand() {
        super(RpcCommandCode.RPC_REQUEST);
    }

    /**
     * create request command with id and request object
     * @param request request object
     */
    public RpcRequestCommand(Object request) {
        super(RpcCommandCode.RPC_REQUEST);
        this.requestObject = request;
        this.setId(IDGenerator.nextId());
    }

    @Override
    public void serializeClazz() throws SerializationException {
        if (this.requestClass != null) {
            try {
                byte[] clz = this.requestClass.getBytes(Configs.DEFAULT_CHARSET);
                this.setClazz(clz);
            } catch (UnsupportedEncodingException e) {
                throw new SerializationException("Unsupported charset: " + Configs.DEFAULT_CHARSET,
                    e);
            }
        }
    }

    @Override
    public void deserializeClazz() throws DeserializationException {
        if (this.getClazz() != null && this.getRequestClass() == null) {
            try {
                this.setRequestClass(new String(this.getClazz(), Configs.DEFAULT_CHARSET));
            } catch (UnsupportedEncodingException e) {
                throw new DeserializationException("Unsupported charset: "
                                                   + Configs.DEFAULT_CHARSET, e);
            }
        }
    }

    @Override
    public void serializeHeader(InvokeContext invokeContext) throws SerializationException {
        if (this.getCustomSerializer() != null) {
            try {
                this.getCustomSerializer().serializeHeader(this, invokeContext);
            } catch (SerializationException e) {
                throw e;
            } catch (Exception e) {
                throw new SerializationException(
                    "Exception caught when serialize header of rpc request command!", e);
            }
        }
    }

    @Override
    public void deserializeHeader(InvokeContext invokeContext) throws DeserializationException {
        if (this.getHeader() != null && this.getRequestHeader() == null) {
            if (this.getCustomSerializer() != null) {
                try {
                    this.getCustomSerializer().deserializeHeader(this);
                } catch (DeserializationException e) {
                    throw e;
                } catch (Exception e) {
                    throw new DeserializationException(
                        "Exception caught when deserialize header of rpc request command!", e);
                }
            }
        }
    }

    @Override
    public void serializeContent(InvokeContext invokeContext) throws SerializationException {
        if (this.requestObject != null) {
            try {
                if (this.getCustomSerializer() != null
                    && this.getCustomSerializer().serializeContent(this, invokeContext)) {
                    return;
                }

                this.setContent(SerializerManager.getSerializer(this.getSerializer()).serialize(
                    this.requestObject));
            } catch (SerializationException e) {
                throw e;
            } catch (Exception e) {
                throw new SerializationException(
                    "Exception caught when serialize content of rpc request command!", e);
            }
        }
    }

    @Override
    public void deserializeContent(InvokeContext invokeContext) throws DeserializationException {
        if (this.getRequestObject() == null) {
            try {
                if (this.getCustomSerializer() != null
                    && this.getCustomSerializer().deserializeContent(this)) {
                    return;
                }
                if (this.getContent() != null) {
                    this.setRequestObject(SerializerManager.getSerializer(this.getSerializer())
                        .deserialize(this.getContent(), this.requestClass));
                }
            } catch (DeserializationException e) {
                throw e;
            } catch (Exception e) {
                throw new DeserializationException(
                    "Exception caught when deserialize content of rpc request command!", e);
            }
        }
    }

    /**
     * Getter method for property <tt>requestObject</tt>.
     *
     * @return property value of requestObject
     */
    public Object getRequestObject() {
        return requestObject;
    }

    /**
     * Setter method for property <tt>requestObject</tt>.
     *
     * @param requestObject value to be assigned to property requestObject
     */
    public void setRequestObject(Object requestObject) {
        this.requestObject = requestObject;
    }

    /**
     * Getter method for property <tt>requestHeader</tt>.
     *
     * @return property value of requestHeader
     */
    public Object getRequestHeader() {
        return requestHeader;
    }

    /**
     * Setter method for property <tt>requestHeader</tt>.
     *
     * @param requestHeader value to be assigned to property requestHeader
     */
    public void setRequestHeader(Object requestHeader) {
        this.requestHeader = requestHeader;
    }

    /**
     * Getter method for property <tt>requestClass</tt>.
     *
     * @return property value of requestClass
     */
    public String getRequestClass() {
        return requestClass;
    }

    /**
     * Setter method for property <tt>requestClass</tt>.
     *
     * @param requestClass value to be assigned to property requestClass
     */
    public void setRequestClass(String requestClass) {
        this.requestClass = requestClass;
    }

    /**
     * Getter method for property <tt>customSerializer</tt>.
     *
     * @return property value of customSerializer
     */
    public CustomSerializer getCustomSerializer() {
        if (this.customSerializer != null) {
            return customSerializer;
        }
        if (this.requestClass != null) {
            this.customSerializer = CustomSerializerManager.getCustomSerializer(this.requestClass);
        }
        if (this.customSerializer == null) {
            this.customSerializer = CustomSerializerManager.getCustomSerializer(this.getCmdCode());
        }
        return this.customSerializer;
    }

    /**
     * Getter method for property <tt>arriveTime</tt>.
     *
     * @return property value of arriveTime
     */
    public long getArriveTime() {
        return arriveTime;
    }

    /**
     * Setter method for property <tt>arriveTime</tt>.
     *
     * @param arriveTime value to be assigned to property arriveTime
     */
    public void setArriveTime(long arriveTime) {
        this.arriveTime = arriveTime;
    }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alipay.remoting;

import java.net.SocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;

import com.alipay.remoting.config.switches.GlobalSwitch;
import com.alipay.remoting.log.BoltLoggerFactory;
import com.alipay.remoting.util.RemotingUtil;
import com.alipay.remoting.util.StringUtils;

import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.util.Attribute;

/**
 * Log the channel status event.
 *
 * @author jiangping
 * @version $Id: ConnectionEventHandler.java, v 0.1 Oct 10, 2016 2:07:24 PM tao Exp $
 */
@Sharable
public class ConnectionEventHandler extends ChannelDuplexHandler {
    private static final Logger     logger = BoltLoggerFactory.getLogger("ConnectionEvent");

    private ConnectionManager       connectionManager;

    private ConnectionEventListener eventListener;

    private ConnectionEventExecutor eventExecutor;

    private ReconnectManager        reconnectManager;

    private GlobalSwitch            globalSwitch;

    public ConnectionEventHandler() {

    }

    public ConnectionEventHandler(GlobalSwitch globalSwitch) {
        this.globalSwitch = globalSwitch;
    }

    /**
     * @see io.netty.channel.ChannelDuplexHandler#connect(io.netty.channel.ChannelHandlerContext, java.net.SocketAddress, java.net.SocketAddress, io.netty.channel.ChannelPromise)
     */
    @Override
    public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
                        SocketAddress localAddress, ChannelPromise promise) throws Exception {
        if (logger.isInfoEnabled()) {
            final String local = localAddress == null ? null : RemotingUtil
                .parseSocketAddressToString(localAddress);
            final String remote = remoteAddress == null ? "UNKNOWN" : RemotingUtil
                .parseSocketAddressToString(remoteAddress);
            if (local == null) {
                if (logger.isInfoEnabled()) {
                    logger.info("Try connect to {}", remote);
                }
            } else {
                if (logger.isInfoEnabled()) {
                    logger.info("Try connect from {} to {}", local, remote);
                }
            }
        }
        super.connect(ctx, remoteAddress, localAddress, promise);
    }

    /**
     * @see io.netty.channel.ChannelDuplexHandler#disconnect(io.netty.channel.ChannelHandlerContext, io.netty.channel.ChannelPromise)
     */
    @Override
    public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
        infoLog("Connection disconnect to {}", RemotingUtil.parseRemoteAddress(ctx.channel()));
        super.disconnect(ctx, promise);
    }

    /**
     * @see io.netty.channel.ChannelDuplexHandler#close(io.netty.channel.ChannelHandlerContext, io.netty.channel.ChannelPromise)
     */
    @Override
    public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
        infoLog("Connection closed: {}", RemotingUtil.parseRemoteAddress(ctx.channel()));
        final Connection conn = ctx.channel().attr(Connection.CONNECTION).get();
        if (conn != null) {
            conn.onClose();
        }
        super.close(ctx, promise);
    }

    /**
     * @see io.netty.channel.ChannelInboundHandlerAdapter#channelRegistered(io.netty.channel.ChannelHandlerContext)
     */
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        infoLog("Connection channel registered: {}", RemotingUtil.parseRemoteAddress(ctx.channel()));
        super.channelRegistered(ctx);
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        infoLog("Connection channel unregistered: {}",
            RemotingUtil.parseRemoteAddress(ctx.channel()));
        super.channelUnregistered(ctx);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        infoLog("Connection channel active: {}", RemotingUtil.parseRemoteAddress(ctx.channel()));
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        String remoteAddress = RemotingUtil.parseRemoteAddress(ctx.channel());
        infoLog("Connection channel inactive: {}", remoteAddress);
        super.channelInactive(ctx);
        Attribute attr = ctx.channel().attr(Connection.CONNECTION);
        if (null != attr) {
            // add reconnect task
            if (this.globalSwitch != null
                && this.globalSwitch.isOn(GlobalSwitch.CONN_RECONNECT_SWITCH)) {
                Connection conn = (Connection) attr.get();
                if (reconnectManager != null) {
                    reconnectManager.addReconnectTask(conn.getUrl());
                }
            }
            // trigger close connection event
            onEvent((Connection) attr.get(), remoteAddress, ConnectionEventType.CLOSE);
        }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object event) throws Exception {
        if (event instanceof ConnectionEventType) {
            switch ((ConnectionEventType) event) {
                case CONNECT:
                    Channel channel = ctx.channel();
                    if (null != channel) {
                        Connection connection = channel.attr(Connection.CONNECTION).get();
                        this.onEvent(connection, connection.getUrl().getOriginUrl(),
                            ConnectionEventType.CONNECT);
                    } else {
                        logger
                            .warn("channel null when handle user triggered event in ConnectionEventHandler!");
                    }
                    break;
                default:
                    return;
            }
        } else {
            super.userEventTriggered(ctx, event);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        final String remoteAddress = RemotingUtil.parseRemoteAddress(ctx.channel());
        final String localAddress = RemotingUtil.parseLocalAddress(ctx.channel());
        logger
            .warn(
                "ExceptionCaught in connection: local[{}], remote[{}], close the connection! Cause[{}:{}]",
                localAddress, remoteAddress, cause.getClass().getSimpleName(), cause.getMessage());
        ctx.channel().close();
    }

    /**
     *
     * @param conn
     * @param remoteAddress
     * @param type
     */
    private void onEvent(final Connection conn, final String remoteAddress,
                         final ConnectionEventType type) {
        if (this.eventListener != null) {
            this.eventExecutor.onEvent(new Runnable() {
                @Override
                public void run() {
                    ConnectionEventHandler.this.eventListener.onEvent(type, remoteAddress, conn);
                }
            });
        }
    }

    /**
     * Getter method for property <tt>listener</tt>.
     *
     * @return property value of listener
     */
    public ConnectionEventListener getConnectionEventListener() {
        return eventListener;
    }

    /**
     * Setter method for property <tt>listener</tt>.
     *
     * @param listener value to be assigned to property listener
     */
    public void setConnectionEventListener(ConnectionEventListener listener) {
        if (listener != null) {
            this.eventListener = listener;
            if (this.eventExecutor == null) {
                this.eventExecutor = new ConnectionEventExecutor();
            }
        }
    }

    /**
     * Getter method for property <tt>connectionManager</tt>.
     *
     * @return property value of connectionManager
     */
    public ConnectionManager getConnectionManager() {
        return connectionManager;
    }

    /**
     * Setter method for property <tt>connectionManager</tt>.
     *
     * @param connectionManager value to be assigned to property connectionManager
     */
    public void setConnectionManager(ConnectionManager connectionManager) {
        this.connectionManager = connectionManager;
    }

    /**
     * Setter method for property <tt>reconnectManager<tt>.
     *
     * @param reconnectManager value to be assigned to property reconnectManager
     */
    public void setReconnectManager(ReconnectManager reconnectManager) {
        this.reconnectManager = reconnectManager;
    }

    /**
     * Dispatch connection event.
     *
     * @author jiangping
     * @version $Id: ConnectionEventExecutor.java, v 0.1 Mar 4, 2016 9:20:15 PM tao Exp $
     */
    public class ConnectionEventExecutor {
        Logger          logger   = BoltLoggerFactory.getLogger("CommonDefault");
        ExecutorService executor = new ThreadPoolExecutor(1, 1, 60L, TimeUnit.SECONDS,
                                     new LinkedBlockingQueue<Runnable>(10000),
                                     new NamedThreadFactory("Bolt-conn-event-executor", true));

        /**
         * Process event.
         *
         * @param event
         */
        public void onEvent(Runnable event) {
            try {
                executor.execute(event);
            } catch (Throwable t) {
                logger.error("Exception caught when execute connection event!", t);
            }
        }
    }

    /**
     * print info log
     * @param format
     * @param addr
     */
    private void infoLog(String format, String addr) {
        if (logger.isInfoEnabled()) {
            if (StringUtils.isNotEmpty(addr)) {
                logger.info(format, addr);
            } else {
                logger.info(format, "UNKNOWN-ADDR");
            }
        }
    }
}