com.baidu.jprotobuf.pbrpc.transport.handler.RpcDataPackageDecoder.java Source code

Java tutorial

Introduction

Here is the source code for com.baidu.jprotobuf.pbrpc.transport.handler.RpcDataPackageDecoder.java

Source

/*
 * Copyright 2002-2014 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.baidu.jprotobuf.pbrpc.transport.handler;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.baidu.jprotobuf.pbrpc.data.ProtocolConstant;
import com.baidu.jprotobuf.pbrpc.data.RpcDataPackage;
import com.baidu.jprotobuf.pbrpc.data.RpcHeadMeta;

/**
 * Decode RpcDataPackage from received bytes
 * 
 * @author xiemalin
 * @since 1.0
 * @see RpcDataPackage
 */
public class RpcDataPackageDecoder extends ByteToMessageDecoder {

    /**
     * Default chunk package wait time out check interval
     */
    private static final int DEFAULT_CLEANUP_INTERVAL = 1000;

    private static Logger LOG = Logger.getLogger(RpcDataPackageDecoder.class.getName());

    private static final Map<Long, RpcDataPackage> tempTrunkPackages = new ConcurrentHashMap<Long, RpcDataPackage>();

    private static final AtomicBoolean startChunkPackageCleanUp = new AtomicBoolean(false);

    private ExecutorService es;;
    private boolean stopChunkPackageTimeoutClean = false;

    /**
     * @param chunkPackageTimeout
     */
    public RpcDataPackageDecoder(final int chunkPackageTimeout) {
        if (chunkPackageTimeout <= 0) {
            return;
        }

        // only start once OK
        if (startChunkPackageCleanUp.compareAndSet(false, true)) {
            es = Executors.newSingleThreadExecutor();
            es.execute(new Runnable() {

                public void run() {
                    while (!stopChunkPackageTimeoutClean) {

                        if (!tempTrunkPackages.isEmpty()) {

                            Map<Long, RpcDataPackage> currentCheckPackage;
                            currentCheckPackage = new HashMap<Long, RpcDataPackage>(tempTrunkPackages);

                            Iterator<Entry<Long, RpcDataPackage>> iter = currentCheckPackage.entrySet().iterator();
                            while (iter.hasNext()) {
                                Entry<Long, RpcDataPackage> entry = iter.next();

                                if (entry.getValue().getTimeStamp() + chunkPackageTimeout > System
                                        .currentTimeMillis()) {
                                    // get time out chunk package, do clean action
                                    tempTrunkPackages.remove(entry.getValue());
                                    LOG.log(Level.SEVERE,
                                            "Found chunk package time out long than " + chunkPackageTimeout
                                                    + "(ms) will clean up correlationId:"
                                                    + entry.getValue().getRpcMeta().getCorrelationId());
                                }
                            }
                        }

                        try {
                            Thread.sleep(DEFAULT_CLEANUP_INTERVAL);
                        } catch (Exception e) {
                            LOG.log(Level.SEVERE, e.getMessage(), e);
                        }

                    }

                }
            });
        }

    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        Object decoded = decode(ctx, in);
        if (decoded != null) {
            out.add(decoded);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.jboss.netty.handler.codec.frame.FrameDecoder#decode(org.jboss.netty
     * .channel.ChannelHandlerContext, org.jboss.netty.channel.Channel,
     * org.jboss.netty.buffer.ChannelBuffer)
     */
    protected Object decode(ChannelHandlerContext ctx, ByteBuf buf) throws Exception {

        // Make sure if the length field was received.
        if (buf.readableBytes() < RpcHeadMeta.SIZE) {
            // The length field was not received yet - return null.
            // This method will be invoked again when more packets are
            // received and appended to the buffer.
            return null;
        }

        // The length field is in the buffer.

        // Mark the current buffer position before reading the length field
        // because the whole frame might not be in the buffer yet.
        // We will reset the buffer position to the marked position if
        // there's not enough bytes in the buffer.
        buf.markReaderIndex();

        // Read the RPC head
        long rpcMessageDecoderStart = System.nanoTime();
        ByteBuffer buffer = buf.nioBuffer(buf.readerIndex(), RpcHeadMeta.SIZE);

        buffer.order(ByteOrder.LITTLE_ENDIAN);
        byte[] bytes = new byte[RpcHeadMeta.SIZE];
        buffer.get(bytes);

        RpcHeadMeta headMeta = new RpcHeadMeta();
        headMeta.read(bytes);

        // get total message size
        int messageSize = headMeta.getMessageSize() + RpcHeadMeta.SIZE;

        // Make sure if there's enough bytes in the buffer.
        if (buf.readableBytes() < messageSize) {
            // The whole bytes were not received yet - return null.
            // This method will be invoked again when more packets are
            // received and appended to the buffer.

            // Reset to the marked position to read the length field again
            // next time.
            buf.resetReaderIndex();

            return null;
        }

        // check magic code
        String magicCode = headMeta.getMagicCodeAsString();
        if (!ProtocolConstant.MAGIC_CODE.equals(magicCode)) {
            throw new Exception("Error magic code:" + magicCode);
        }
        // There's enough bytes in the buffer. Read it.
        byte[] totalBytes = new byte[messageSize];
        buf.readBytes(totalBytes, 0, messageSize);

        RpcDataPackage rpcDataPackage = new RpcDataPackage();
        rpcDataPackage.setTimeStamp(System.currentTimeMillis());
        rpcDataPackage.read(totalBytes);

        // check if a chunk package
        if (rpcDataPackage.isChunkPackage()) {

            Long chunkStreamId = rpcDataPackage.getChunkStreamId();

            RpcDataPackage chunkDataPackage = tempTrunkPackages.get(chunkStreamId);
            if (chunkDataPackage == null) {
                chunkDataPackage = rpcDataPackage;
                tempTrunkPackages.put(chunkStreamId, rpcDataPackage);
            } else {
                chunkDataPackage.mergeData(rpcDataPackage.getData());
            }

            if (rpcDataPackage.isFinalPackage()) {
                chunkDataPackage.chunkInfo(chunkStreamId, -1);
                tempTrunkPackages.remove(chunkStreamId);

                return chunkDataPackage;
            }

            return null;
        }

        long rpcMessageDecoderEnd = System.nanoTime();
        LOG.log(Level.FINE,
                "[profiling] nshead decode cost : " + (rpcMessageDecoderEnd - rpcMessageDecoderStart) / 1000);

        return rpcDataPackage;
    }

    /**
     * 
     */
    public void close() {
        stopChunkPackageTimeoutClean = true;
        if (es != null) {
            es.shutdown();
        }

    }

}