com.whizzosoftware.wzwave.channel.inbound.TransactionInboundHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.whizzosoftware.wzwave.channel.inbound.TransactionInboundHandler.java

Source

/*
 *******************************************************************************
 * Copyright (c) 2013 Whizzo Software, LLC.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************
*/
package com.whizzosoftware.wzwave.channel.inbound;

import com.whizzosoftware.wzwave.channel.TransactionTimeoutHandler;
import com.whizzosoftware.wzwave.channel.event.*;
import com.whizzosoftware.wzwave.frame.*;
import com.whizzosoftware.wzwave.frame.transaction.NodeInclusionTransaction;
import com.whizzosoftware.wzwave.frame.transaction.DataFrameTransaction;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.concurrent.ScheduledFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

/**
 * Handler for all Z-Wave frame transactions. Responsible for tracking the state of the current transaction
 * including successes, failures and timeouts.
 *
 * @author Dan Noguerol
 */
public class TransactionInboundHandler extends ChannelInboundHandlerAdapter {
    private static final Logger logger = LoggerFactory.getLogger(TransactionInboundHandler.class);

    private static final int MAX_SEND_COUNT = 2;

    private ChannelHandlerContext handlerContext;
    private DataFrameTransaction currentDataFrameTransaction;
    private ScheduledFuture timeoutFuture;

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        this.handlerContext = ctx;
    }

    /**
     * Called when data is read from the Z-Wave network.
     *
     * @param ctx the handler context
     * @param msg the message that was read
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        if (msg instanceof Frame) {
            Frame frame = (Frame) msg;
            if (hasCurrentTransaction()) {
                String tid = currentDataFrameTransaction.getId();
                logger.trace("Received frame within transaction ({}) context: {}", tid, frame);

                // give new frame to current transaction
                if (currentDataFrameTransaction.addFrame(frame)) {
                    if (currentDataFrameTransaction.isComplete()) {
                        // cancel the timeout callback
                        if (timeoutFuture != null) {
                            timeoutFuture.cancel(true);
                            timeoutFuture = null;
                        }

                        if (!currentDataFrameTransaction.hasError()) {
                            DataFrame finalFrame = currentDataFrameTransaction.getFinalFrame();
                            logger.trace("*** Data frame transaction ({}) completed with final frame: {}", tid,
                                    finalFrame);
                            logger.trace("");

                            // if there's an ApplicationUpdate with no node ID (e.g. when there's a app update failure), attempt
                            // to set the node ID based on the request frame that triggered it
                            if (finalFrame instanceof ApplicationUpdate) {
                                ApplicationUpdate update = (ApplicationUpdate) finalFrame;
                                if ((update.getNodeId() == null || update.getNodeId() == 0)
                                        && currentDataFrameTransaction.getStartFrame() instanceof RequestNodeInfo) {
                                    update.setNodeId(((RequestNodeInfo) currentDataFrameTransaction.getStartFrame())
                                            .getNodeId());
                                }
                            }

                            clearTransaction();
                            ctx.fireUserEventTriggered(new TransactionCompletedEvent(tid, finalFrame));
                        } else if (currentDataFrameTransaction.shouldRetry()) {
                            attemptResend(ctx);
                        } else {
                            logger.trace("*** Data frame transaction ({}) failed", tid);
                            logger.trace("");
                            clearTransaction();
                            ctx.fireUserEventTriggered(new TransactionFailedEvent(tid));
                        }
                    }
                    // if transaction didn't consume frame, then pass it down the pipeline
                } else {
                    logger.trace("Transaction ignored frame so passing it along");
                    ctx.fireChannelRead(msg);
                }
            } else if (msg instanceof AddNodeToNetwork) {
                logger.trace("Received ADD_NODE_STATUS_NODE_FOUND; starting transaction");
                currentDataFrameTransaction = new NodeInclusionTransaction((DataFrame) msg);
            } else {
                logger.trace("Received frame outside of transaction context so passing it along: {}", frame);
                ctx.fireChannelRead(msg);
            }
        }
    }

    public DataFrameTransaction getCurrentTransaction() {
        return currentDataFrameTransaction;
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        logger.trace("User event received: {}", evt);
        if (evt instanceof DataFrameSentEvent) {
            DataFrameSentEvent dfse = (DataFrameSentEvent) evt;
            logger.trace("Detected data frame write event: {}", dfse.getDataFrame());
            if (!hasCurrentTransaction()) {
                currentDataFrameTransaction = dfse.getDataFrame().createWrapperTransaction(dfse.isListeningNode());
                if (currentDataFrameTransaction != null) {
                    logger.trace("*** Data frame transaction started for {} with ID {}", dfse.getDataFrame(),
                            currentDataFrameTransaction.getId());
                    // start timeout timer
                    if (currentDataFrameTransaction.getTimeout() > 0 && handlerContext != null
                            && handlerContext.executor() != null) {
                        timeoutFuture = handlerContext.executor()
                                .schedule(
                                        new TransactionTimeoutHandler(currentDataFrameTransaction.getId(),
                                                handlerContext, this),
                                        currentDataFrameTransaction.getTimeout(), TimeUnit.MILLISECONDS);
                    } else {
                        logger.warn("Unable to schedule transaction timeout callback");
                    }
                    ctx.fireUserEventTriggered(new TransactionStartedEvent(currentDataFrameTransaction.getId()));
                }
            } else {
                logger.trace("Wrote a data frame with a current transaction: {}", dfse.getDataFrame());
            }
        } else if (evt instanceof TransactionTimeoutEvent) {
            TransactionTimeoutEvent tte = (TransactionTimeoutEvent) evt;
            if (tte.getId().equals(currentDataFrameTransaction.getId())) {
                logger.trace("Detected transaction timeout");
                if (currentDataFrameTransaction.shouldRetry()) {
                    attemptResend(ctx);
                } else {
                    clearTransaction();
                    ctx.fireUserEventTriggered(new TransactionFailedEvent(tte.getId()));
                }
            } else {
                logger.error("Received timeout event for unknown transaction: {}", tte.getId());
            }
        } else {
            ctx.fireUserEventTriggered(evt);
        }
    }

    /**
     * Indicated whether there is an active transaction.
     *
     * @return a boolean
     */
    public boolean hasCurrentTransaction() {
        return (currentDataFrameTransaction != null && !currentDataFrameTransaction.isComplete());
    }

    private void clearTransaction() {
        currentDataFrameTransaction = null;
    }

    private void attemptResend(ChannelHandlerContext ctx) {
        DataFrame startFrame = currentDataFrameTransaction.getStartFrame();
        if (startFrame.getSendCount() < MAX_SEND_COUNT) {
            logger.debug("Transaction has failed - will reset and resend initial request");
            currentDataFrameTransaction.reset();
            // if a CAN was received, then we decrement the send count by one so this attempt doesn't count
            // towards the maximum resend count
            ctx.channel().writeAndFlush(
                    new OutboundDataFrame(startFrame, currentDataFrameTransaction.isListeningNode()));
        } else {
            logger.debug("Transaction has failed and has exceeded max resends");
            String id = currentDataFrameTransaction.getId();
            clearTransaction();
            ctx.fireUserEventTriggered(new TransactionFailedEvent(id));
        }
    }
}