/**
* Helios, OpenSource Monitoring
* Brought to you by the Helios Development Group
*
* Copyright 2007, Helios Development Group and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
*/
package org.helios.apmrouter.server.unification.pipeline2;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import org.helios.apmrouter.ref.RunnableReferenceQueue;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.UpstreamMessageEvent;
/**
* <p>Title: ProtocolSwitchContext</p>
* <p>Description: Some contextual state retained on behalf of a channel during the port protocol switch.</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.server.unification.pipeline2.ProtocolSwitchContext</code></p>
*/
public class ProtocolSwitchContext {
/** A set of protocol initiators which have definitively failed matching for this context */
protected final Set<Initiator> pInitiators = new HashSet<Initiator>();
/** Static class logger */
protected static final Logger LOG = Logger.getLogger(ProtocolSwitchContext.class);
/** The ReplayingDecoderBuffer class */
protected static final Class<?> REPLAY_BUFFER_CLASS;
/** The replay decoeer buffer replay error instance */
protected static final Error REPLAY_ERROR;
static {
try {
REPLAY_BUFFER_CLASS = Class.forName("org.jboss.netty.handler.codec.replay.ReplayingDecoderBuffer", true, Channel.class.getClassLoader());
Field f = REPLAY_BUFFER_CLASS.getDeclaredField("REPLAY");
f.setAccessible(true);
REPLAY_ERROR = (Error)f.get(null);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/** The channel this context is created for */
protected final Channel channel;
/** The channel's pipeline */
protected final ChannelPipeline pipeline;
/** The current channel handler context */
protected ChannelHandlerContext ctx;
/** The currently processing buffer */
protected ChannelBuffer buffer = null;
/** The current buffer's currently readable bytes */
protected int readableBytes = -1;
/** The current switch phase */
protected SwitchPhase phase = null;
/** The number of bytes provided in the prior {@link ProtocolSwitchDecoder#decode(ChannelHandlerContext, Channel, ChannelBuffer, SwitchPhase)} call */
protected int priorReadBytes = 0;
/** The current initiator to be called for a given state */
protected final Map<SwitchPhase, Initiator> nextInitiators = new EnumMap<SwitchPhase, Initiator>(SwitchPhase.class);
/** Indicates if aggregation has taken place within the scope of this context */
protected boolean aggregationHasOccured = false;
/**
* Sends the current buffer upstream
* @param handlerName the name of the handler to which the upstream event should be targetted
*/
public void sendCurrentBufferUpstream(String handlerName) {
ChannelBuffer cb = unReplayChannelBuffer();
UpstreamMessageEvent evt = new UpstreamMessageEvent(channel, cb, channel.getRemoteAddress());
pipeline.getContext(handlerName).sendUpstream(evt);
}
/**
* Sends the current buffer upstream
*/
public void sendCurrentBufferUpstream() {
ChannelBuffer cb = unReplayChannelBuffer();
UpstreamMessageEvent evt = new UpstreamMessageEvent(channel, cb, channel.getRemoteAddress());
pipeline.sendUpstream(evt);
}
/**
* Returns the current buffer in an upstream message event
* @return an upstream message event
*/
public UpstreamMessageEvent getUpstreamEvent() {
ChannelBuffer cb = unReplayChannelBuffer();
return new UpstreamMessageEvent(channel, cb, channel.getRemoteAddress());
}
/**
* Converts a replay channel buffer to a regular direct buffer
* @return a regular direct buffer
*/
public ChannelBuffer unReplayChannelBuffer() {
ChannelBuffer cb = null;
if(REPLAY_BUFFER_CLASS.isInstance(buffer)) {
cb = ChannelBuffers.directBuffer(buffer.order(), readableBytes);
cb.writeBytes(buffer);
this.buffer = cb;
} else {
cb = buffer;
}
return cb;
}
/**
* Throws the replay exception causing the replay decoder to re-submit
*/
public void replay() {
throw REPLAY_ERROR;
}
/**
* Clears the nextInitiators map
* @return this context
*/
ProtocolSwitchContext clearNextInitiators() {
nextInitiators.clear();
return this;
}
/**
* Sets the next initiator for the passed phases
* @param initiator The initiator to set. If null, the current initiator will be cleared
* @param phases The phases to set the initiator for
* @return this context
*/
ProtocolSwitchContext setNextInitiator(Initiator initiator, SwitchPhase...phases) {
if(phases!=null) {
for(SwitchPhase phase: phases) {
if(phase==null) continue;
if(initiator==null) {
nextInitiators.remove(phase);
} else {
nextInitiators.put(phase, initiator);
}
}
}
return this;
}
/**
* Returns the next initiator for the passed phase
* @param phase The phase to get the initiator for
* @return the next initiator or null if one was not set
*/
Initiator getInitiator(SwitchPhase phase) {
if(phase==null) throw new IllegalArgumentException("The passed phase was null", new Throwable());
return nextInitiators.get(phase);
}
/**
* Creates a new ProtocolSwitchContext
* @param ctx The current channel handler context
* @param channel The channel this context is created for
* @param buffer The currently processing buffer
* @param readableBytes The current buffer's currently readable bytes
// * @param state the initial switch phase
*/
public ProtocolSwitchContext(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, int readableBytes) {
this.channel = channel;
this.pipeline = channel.getPipeline();
this.buffer = buffer;
// this.phase = state;
this.readableBytes = readableBytes;
this.ctx = ctx;
final int channelId = channel.getId();
RunnableReferenceQueue.getInstance().buildWeakReference(this, new Runnable(){
@Override
public void run() {
LOG.info("\n\t*********************\n\t[" + new Date() + "] Enqueued ProtocolSwitchContext for Channel [" + channelId + "]\n\t*********************\n");
}
});
ctx.setAttachment(this);
}
/**
* Clears this context from the channel handler context where it was attached, unless it no longer attached there.
*/
public void clear() {
Object attachment = ctx.getAttachment();
if(attachment!=null) {
if(attachment==this) {
ctx.setAttachment(null);
}
}
}
/**
* Updates the current context for each call to {@link ProtocolSwitchDecoder#decode(ChannelHandlerContext, Channel, ChannelBuffer, SwitchPhase)}
* @param ctx The current channel handler context
* @param buffer The currently processing buffer
* @param readableBytes The current buffer's currently readable bytes
// * @param state the initial switch phase
* @return this context
*/
public ProtocolSwitchContext update(ChannelHandlerContext ctx, ChannelBuffer buffer, int readableBytes) {
this.ctx = ctx;
this.buffer = buffer;
this.priorReadBytes = this.readableBytes;
this.readableBytes = readableBytes;
return this;
}
/**
* Returns the number of bytes provided in the prior {@link ProtocolSwitchDecoder#decode(ChannelHandlerContext, Channel, ChannelBuffer, SwitchPhase)} call
* @return the number of bytes provided in the prior
*/
int getPriorReadBytes() {
return priorReadBytes;
}
/**
* Indicates if this decode call supplied additional bytes beyond the prior call assuming no bytes were read from the buffer.
* @return true if this decode call supplied additional bytes, false if the same size buffer was passed
*/
boolean readMoreBytes() {
return readableBytes > priorReadBytes;
}
/**
* Returns the approriate read more phase for the current phase
* @return a read more phase different from the current phase
*/
SwitchPhase readMore() {
return phase==SwitchPhase.READ_MORE_1 ? SwitchPhase.READ_MORE_2 : SwitchPhase.READ_MORE_1;
}
/**
* Resets the failed protocol initiators
* @return this context
*/
ProtocolSwitchContext resetInitiators() {
pInitiators.clear();
return this;
}
/**
* Fails the passed {@link Initiator} and returns this context
* @param pi the failed Initiator
* @return this context
*/
ProtocolSwitchContext failInitiator(Initiator pi) {
pInitiators.add(pi);
return this;
}
/**
* Determines if this context has failed the passed {@link Initiator}
* @param pi the {@link Initiator} to test
* @return true if failed, false otherwise
*/
boolean hasInitiatorFailed(Initiator pi) {
return pInitiators.contains(pi);
}
/**
* Returns the current channel handler context
* @return the current channel handler context
*/
public ChannelHandlerContext getCtx() {
return ctx;
}
/**
* Returns the channel associated with this context
* @return the channel associated with this context
*/
public Channel getChannel() {
return channel;
}
/**
* Returns the pipeline associated with this context
* @return the pipeline associated with this context
*/
public ChannelPipeline getPipeline() {
return pipeline;
}
/**
* Returns the current buffer being processed
* @return the current buffer being processed
*/
public ChannelBuffer getBuffer() {
return buffer;
}
/**
* Returns the current buffer's currently readable bytes
* @return the current buffer's currently readable bytes
*/
public int getReadableBytes() {
return buffer==null ? 0 : buffer.readableBytes();
}
/**
* Returns the current replaying decoder buffer's currently actual readable bytes
* @return the current replaying decoder buffer's currently actual readable bytes
*/
public int getActualReadableBytes() {
return readableBytes;
}
/**
* Returns the current switch phase
* @return the current switch phase
*/
public SwitchPhase getPhase() {
return phase;
}
/**
* Sets the current switch phase
* @param state the current switch phase
*/
public void setPhase(SwitchPhase state) {
this.phase = state;
}
/**
* Indicates if aggregation has taken place within the scope of this context
* @return true if aggregation has taken place within the scope of this context, false otherwise
*/
public boolean aggregationHasOccured() {
return aggregationHasOccured;
}
/**
* Sets the aggregation-has-occured flag to true
*/
public void setAggregationHasOccured() {
this.aggregationHasOccured = true;
}
}