/** * Copyright 2007-2015, Kaazing Corporation. All rights reserved. * * 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 org.kaazing.k3po.driver.internal.behavior.handler.event; import static java.lang.Boolean.TRUE; import static java.lang.String.format; import static java.util.EnumSet.complementOf; import static java.util.EnumSet.of; import static org.kaazing.k3po.driver.internal.behavior.handler.event.AbstractEventHandler.ChannelEventKind.BOUND; import static org.kaazing.k3po.driver.internal.behavior.handler.event.AbstractEventHandler.ChannelEventKind.CHILD_CLOSED; import static org.kaazing.k3po.driver.internal.behavior.handler.event.AbstractEventHandler.ChannelEventKind.CHILD_OPEN; import static org.kaazing.k3po.driver.internal.behavior.handler.event.AbstractEventHandler.ChannelEventKind.CLOSED; import static org.kaazing.k3po.driver.internal.behavior.handler.event.AbstractEventHandler.ChannelEventKind.CONNECTED; import static org.kaazing.k3po.driver.internal.behavior.handler.event.AbstractEventHandler.ChannelEventKind.DISCONNECTED; import static org.kaazing.k3po.driver.internal.behavior.handler.event.AbstractEventHandler.ChannelEventKind.EXCEPTION; import static org.kaazing.k3po.driver.internal.behavior.handler.event.AbstractEventHandler.ChannelEventKind.FLUSHED; import static org.kaazing.k3po.driver.internal.behavior.handler.event.AbstractEventHandler.ChannelEventKind.IDLE_STATE; import static org.kaazing.k3po.driver.internal.behavior.handler.event.AbstractEventHandler.ChannelEventKind.INPUT_SHUTDOWN; import static org.kaazing.k3po.driver.internal.behavior.handler.event.AbstractEventHandler.ChannelEventKind.INTEREST_OPS; import static org.kaazing.k3po.driver.internal.behavior.handler.event.AbstractEventHandler.ChannelEventKind.MESSAGE; import static org.kaazing.k3po.driver.internal.behavior.handler.event.AbstractEventHandler.ChannelEventKind.OPEN; import static org.kaazing.k3po.driver.internal.behavior.handler.event.AbstractEventHandler.ChannelEventKind.OUTPUT_SHUTDOWN; import static org.kaazing.k3po.driver.internal.behavior.handler.event.AbstractEventHandler.ChannelEventKind.UNBOUND; import static org.kaazing.k3po.driver.internal.behavior.handler.event.AbstractEventHandler.ChannelEventKind.UNKNOWN; import static org.kaazing.k3po.driver.internal.behavior.handler.event.AbstractEventHandler.ChannelEventKind.WRITE_COMPLETED; import java.util.EnumSet; import java.util.Set; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelEvent; import org.jboss.netty.channel.ChannelException; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.ChildChannelStateEvent; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.WriteCompletionEvent; import org.jboss.netty.handler.timeout.IdleStateEvent; import org.kaazing.k3po.driver.internal.behavior.ScriptProgressException; import org.kaazing.k3po.driver.internal.behavior.handler.ExecutionHandler; import org.kaazing.k3po.driver.internal.netty.channel.FlushEvent; import org.kaazing.k3po.driver.internal.netty.channel.ShutdownInputEvent; import org.kaazing.k3po.driver.internal.netty.channel.ShutdownOutputEvent; public abstract class AbstractEventHandler extends ExecutionHandler { protected static final EnumSet<ChannelEventKind> DEFAULT_INTERESTED_EVENTS = complementOf(of(CHILD_OPEN, CHILD_CLOSED, WRITE_COMPLETED, INTEREST_OPS, EXCEPTION, IDLE_STATE, OUTPUT_SHUTDOWN, FLUSHED, UNKNOWN)); public enum ChannelEventKind { CHILD_OPEN, CHILD_CLOSED, OPEN, BOUND, CONNECTED, MESSAGE, WRITE_COMPLETED, DISCONNECTED, UNBOUND, CLOSED, EXCEPTION, INTEREST_OPS, IDLE_STATE, INPUT_SHUTDOWN, OUTPUT_SHUTDOWN, FLUSHED, UNKNOWN, ABORTED }; private final Set<ChannelEventKind> interestEvents; private final Set<ChannelEventKind> expectedEvents; protected AbstractEventHandler(Set<ChannelEventKind> expectedEvents) { this(DEFAULT_INTERESTED_EVENTS, expectedEvents); } protected AbstractEventHandler(Set<ChannelEventKind> interestEvents, Set<ChannelEventKind> expectedEvents) { this.interestEvents = interestEvents; this.expectedEvents = expectedEvents; } @Override protected void handleUpstream1(ChannelHandlerContext ctx, ChannelEvent evt) throws Exception { ChannelEventKind eventAsKind = asEventKind(evt); ChannelFuture handlerFuture = getHandlerFuture(); assert handlerFuture != null; if (handlerFuture.isDone() || !interestEvents.contains(eventAsKind)) { // Skip events not deemed interesting, such as write // completion events ctx.sendUpstream(evt); } else if (!expectedEvents.contains(eventAsKind)) { handleUnexpectedEvent(ctx, evt); } else { super.handleUpstream1(ctx, evt); } } protected void handleUnexpectedEvent(ChannelHandlerContext ctx, ChannelEvent evt) throws Exception { ChannelEventKind eventAsKind = asEventKind(evt); // Treat interesting but unexpected events as failure try { switch (eventAsKind) { case OPEN: throw new ScriptProgressException(getRegionInfo(), "opened"); case BOUND: throw new ScriptProgressException(getRegionInfo(), "bound"); case CONNECTED: throw new ScriptProgressException(getRegionInfo(), "connected"); case DISCONNECTED: throw new ScriptProgressException(getRegionInfo(), "disconnected"); case UNBOUND: throw new ScriptProgressException(getRegionInfo(), "unbound"); case CLOSED: throw new ScriptProgressException(getRegionInfo(), "closed"); case FLUSHED: throw new ScriptProgressException(getRegionInfo(), "flushed"); case MESSAGE: throw new ScriptProgressException(getRegionInfo(), "read ..."); case INPUT_SHUTDOWN: throw new ScriptProgressException(getRegionInfo(), "read closed"); case OUTPUT_SHUTDOWN: throw new ScriptProgressException(getRegionInfo(), "write closed"); case CHILD_OPEN: throw new ScriptProgressException(getRegionInfo(), "child opened"); case CHILD_CLOSED: throw new ScriptProgressException(getRegionInfo(), "child closed"); case EXCEPTION: default: String message = format("Unexpected event |%s| for handler %s", eventAsKind, getClass()); ChannelException exception = new ChannelException(message); exception.fillInStackTrace(); if (evt instanceof ExceptionEvent) { Throwable cause = ((ExceptionEvent) evt).getCause(); exception.initCause(cause); } getHandlerFuture().setFailure(exception); break; } } catch (ScriptProgressException e) { getHandlerFuture().setFailure(e); } } private static ChannelEventKind asEventKind(ChannelEvent evt) { if (evt instanceof ShutdownInputEvent) { return INPUT_SHUTDOWN; } if (evt instanceof ShutdownOutputEvent) { return OUTPUT_SHUTDOWN; } if (evt instanceof FlushEvent) { return FLUSHED; } if (evt instanceof MessageEvent) { return MESSAGE; } if (evt instanceof WriteCompletionEvent) { return WRITE_COMPLETED; } if (evt instanceof ChannelStateEvent) { ChannelStateEvent cse = (ChannelStateEvent) evt; Object value = cse.getValue(); switch (cse.getState()) { case OPEN: return TRUE.equals(value) ? OPEN : CLOSED; case BOUND: return value != null ? BOUND : UNBOUND; case CONNECTED: return value != null ? CONNECTED : DISCONNECTED; case INTEREST_OPS: return INTEREST_OPS; } } if (evt instanceof ChildChannelStateEvent) { ChildChannelStateEvent ccse = (ChildChannelStateEvent) evt; Channel child = ccse.getChildChannel(); return child.isOpen() ? CHILD_OPEN : CHILD_CLOSED; } if (evt instanceof ExceptionEvent) { return EXCEPTION; } if (evt instanceof IdleStateEvent) { return IDLE_STATE; } return UNKNOWN; } }