From 3d2579b8cbf0e7117fcca1e52cdf89aa87f9c74a Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Thu, 10 Sep 2009 04:25:05 +0000 Subject: [PATCH] More documentation on state management --- .../netty/channel/ChannelHandlerContext.java | 10 +- .../channel/ChannelPipelineCoverage.java | 96 +++++++++++++++++++ 2 files changed, 104 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jboss/netty/channel/ChannelHandlerContext.java b/src/main/java/org/jboss/netty/channel/ChannelHandlerContext.java index d1cca5280f..2804a720ba 100644 --- a/src/main/java/org/jboss/netty/channel/ChannelHandlerContext.java +++ b/src/main/java/org/jboss/netty/channel/ChannelHandlerContext.java @@ -15,6 +15,8 @@ */ package org.jboss.netty.channel; +import java.util.concurrent.ConcurrentMap; + /** * Provides the properties and operations which are specific to a * {@link ChannelHandler} and the {@link ChannelPipeline} it belongs to. @@ -149,7 +151,9 @@ public interface ChannelHandlerContext { * Retrieves an object which is {@link #setAttachment(Object) attached} to * this context. *

- * As an alternative, you might want to use a {@link ChannelLocal} variable. + * As an alternative, you might want to use a {@link ChannelLocal} variable + * or a {@link ConcurrentMap} whose key is {@link ChannelHandlerContext}. + * Please refer to {@link ChannelPipelineCoverage} for the detailed examples. * * @return {@code null} if no object was attached or * {@code null} was attached @@ -161,7 +165,9 @@ public interface ChannelHandlerContext { * specific to the {@link ChannelHandler} which is associated with this * context. *

- * As an alternative, you might want to use a {@link ChannelLocal} variable. + * As an alternative, you might want to use a {@link ChannelLocal} variable + * or a {@link ConcurrentMap} whose key is {@link ChannelHandlerContext}. + * Please refer to {@link ChannelPipelineCoverage} for the detailed examples. */ void setAttachment(Object attachment); } diff --git a/src/main/java/org/jboss/netty/channel/ChannelPipelineCoverage.java b/src/main/java/org/jboss/netty/channel/ChannelPipelineCoverage.java index fb7c3bf8e6..bc098887c0 100644 --- a/src/main/java/org/jboss/netty/channel/ChannelPipelineCoverage.java +++ b/src/main/java/org/jboss/netty/channel/ChannelPipelineCoverage.java @@ -21,6 +21,7 @@ import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.concurrent.ConcurrentMap; /** * Specifies if the same instance of the annotated {@link ChannelHandler} type @@ -62,6 +63,16 @@ import java.lang.annotation.Target; * } * * + * Please note that the handler annotated with {@code "all"} can even be added + * to the same pipeline more than once: + * + *

+ * ChannelPipeline p = ...;
+ * StatelessHandler h = ...;
+ * p.addLast("handler1", h);
+ * p.addLast("handler2", h);
+ * 
+ * *

{@code ChannelPipelineCoverage("one")}

* * {@code "one"} means you must create a new instance of the annotated handler @@ -105,6 +116,91 @@ import java.lang.annotation.Target; * } * * + *

Writing a stateful handler with the {@code "all"} coverage

+ * + * Although it's recommended to write a stateful handler with the {@code "one"} + * coverage, you might sometimes want to write it with the {@code "all"} + * coverage for some reason. In such a case, you need to use either + * {@link ChannelHandlerContext#setAttachment(Object) ChannelHandlerContext.attachment} + * property or a {@link ConcurrentMap} whose key is {@link ChannelHandlerContext}. + *

+ * The following is an example that uses the context attachment: + *

+ * public class StatefulHandler extends SimpleChannelHandler {
+ *
+ *     public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) {
+ *         // Initialize the message ID counter.
+ *         // Please note that the attachment (the counter in this case) will be
+ *         // dereferenced and marked for garbage collection automatically on
+ *         // disconnection.
+ *         ctx.setAttachment(Integer.valueOf(0));
+ *     }
+ *
+ *     public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
+ *         // Fetch the current message ID.
+ *         int messageId = ((Integer) ctx.getAttachment()).intValue();
+ *
+ *         // Prepend a message ID and length field to the message.
+ *         ChannelBuffer body = (ChannelBuffer) e.getMessage();
+ *         ChannelBuffer header = ChannelBuffers.buffer(8);
+ *         header.writeInt(messageId);
+ *         header.writeInt(body.readableBytes());
+ *
+ *         // Update the stateful property.
+ *         ctx.setAttachment(Integer.valueOf(messageId + 1));
+ *
+ *         // Create a message prepended with the header and send a new event.
+ *         ChannelBuffer message = ChannelBuffers.wrappedBuffer(header, body);
+ *         Channels.fireMessageReceived(ctx, message, e.getRemoteAddress());
+ *     }
+ *     ...
+ * }
+ * 
+ * + * and here's another example that uses a map: + *
+ * public class StatefulHandler extends SimpleChannelHandler {
+ *
+ *     private final ConcurrentMap<ChannelHandlerContext, Integer> messageIds =
+ *             new ConcurrentHashMap<ChannelHandlerContext, Integer>();
+ *
+ *     public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) {
+ *         // Initialize the message ID counter.
+ *         messageIds.put(ctx, Integer.valueOf(0));
+ *     }
+ *
+ *     public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) {
+ *         // Remove the message ID counter from the map.
+ *         // Please note that the context attachment does not need this step.
+ *         messageIds.remove(ctx);
+ *     }
+ *
+ *     public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
+ *         // Fetch the current message ID.
+ *         int messageId = messageIds.get(ctx).intValue();
+ *
+ *         // Prepend a message ID and length field to the message.
+ *         ChannelBuffer body = (ChannelBuffer) e.getMessage();
+ *         ChannelBuffer header = ChannelBuffers.buffer(8);
+ *         header.writeInt(messageId);
+ *         header.writeInt(body.readableBytes());
+ *
+ *         // Update the stateful property.
+ *         messageIds.put(ctx, Integer.valueOf(messageId + 1));
+ *
+ *         // Create a message prepended with the header and send a new event.
+ *         ChannelBuffer message = ChannelBuffers.wrappedBuffer(header, body);
+ *         Channels.fireMessageReceived(ctx, message, e.getRemoteAddress());
+ *     }
+ *     ...
+ * }
+ * 
+ * + * Please note that the examples above in this section assume that the handlers + * are added before the {@code channelOpen} event and removed after the + * {@code channelClosed} event. The initialization and removal of the message + * ID property could have been more complicated otherwise. + * * @author The Netty Project (netty-dev@lists.jboss.org) * @author Trustin Lee (tlee@redhat.com) *