1 /*
2 * Copyright 2012 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package org.jboss.netty.handler.codec.protobuf;
17
18 import com.google.protobuf.ExtensionRegistry;
19 import com.google.protobuf.Message;
20 import com.google.protobuf.MessageLite;
21 import org.jboss.netty.buffer.ChannelBuffer;
22 import org.jboss.netty.channel.Channel;
23 import org.jboss.netty.channel.ChannelHandler.Sharable;
24 import org.jboss.netty.channel.ChannelHandlerContext;
25 import org.jboss.netty.channel.ChannelPipeline;
26 import org.jboss.netty.channel.MessageEvent;
27 import org.jboss.netty.handler.codec.frame.FrameDecoder;
28 import org.jboss.netty.handler.codec.frame.LengthFieldBasedFrameDecoder;
29 import org.jboss.netty.handler.codec.frame.LengthFieldPrepender;
30 import org.jboss.netty.handler.codec.oneone.OneToOneDecoder;
31
32 /**
33 * Decodes a received {@link ChannelBuffer} into a
34 * <a href="http://code.google.com/p/protobuf/">Google Protocol Buffers</a>
35 * {@link Message} and {@link MessageLite}. Please note that this decoder must
36 * be used with a proper {@link FrameDecoder} such as {@link ProtobufVarint32FrameDecoder}
37 * or {@link LengthFieldBasedFrameDecoder} if you are using a stream-based
38 * transport such as TCP/IP. A typical setup for TCP/IP would be:
39 * <pre>
40 * {@link ChannelPipeline} pipeline = ...;
41 *
42 * // Decoders
43 * pipeline.addLast("frameDecoder",
44 * new {@link LengthFieldBasedFrameDecoder}(1048576, 0, 4, 0, 4));
45 * pipeline.addLast("protobufDecoder",
46 * new {@link ProtobufDecoder}(MyMessage.getDefaultInstance()));
47 *
48 * // Encoder
49 * pipeline.addLast("frameEncoder", new {@link LengthFieldPrepender}(4));
50 * pipeline.addLast("protobufEncoder", new {@link ProtobufEncoder}());
51 * </pre>
52 * and then you can use a {@code MyMessage} instead of a {@link ChannelBuffer}
53 * as a message:
54 * <pre>
55 * void messageReceived({@link ChannelHandlerContext} ctx, {@link MessageEvent} e) {
56 * MyMessage req = (MyMessage) e.getMessage();
57 * MyMessage res = MyMessage.newBuilder().setText(
58 * "Did you say '" + req.getText() + "'?").build();
59 * ch.write(res);
60 * }
61 * </pre>
62 *
63 * @apiviz.landmark
64 */
65 @Sharable
66 public class ProtobufDecoder extends OneToOneDecoder {
67 private static final boolean HAS_PARSER;
68
69 static {
70 boolean hasParser = false;
71 try {
72 // MessageLite.getParsetForType() is not available until protobuf 2.5.0.
73 MessageLite.class.getDeclaredMethod("getParserForType");
74 hasParser = true;
75 } catch (Throwable t) {
76 // Ignore
77 }
78
79 HAS_PARSER = hasParser;
80 }
81 private final MessageLite prototype;
82 private final ExtensionRegistry extensionRegistry;
83
84 /**
85 * Creates a new instance.
86 */
87 public ProtobufDecoder(MessageLite prototype) {
88 this(prototype, null);
89 }
90
91 public ProtobufDecoder(MessageLite prototype, ExtensionRegistry extensionRegistry) {
92 if (prototype == null) {
93 throw new NullPointerException("prototype");
94 }
95 this.prototype = prototype.getDefaultInstanceForType();
96 this.extensionRegistry = extensionRegistry;
97 }
98
99 @Override
100 protected Object decode(
101 ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
102 if (!(msg instanceof ChannelBuffer)) {
103 return msg;
104 }
105
106 ChannelBuffer buf = (ChannelBuffer) msg;
107 final byte[] array;
108 final int offset;
109 final int length = buf.readableBytes();
110
111 if (buf.hasArray()) {
112 array = buf.array();
113 offset = buf.arrayOffset() + buf.readerIndex();
114 } else {
115 array = new byte[length];
116 buf.getBytes(buf.readerIndex(), array, 0, length);
117 offset = 0;
118 }
119
120 if (extensionRegistry == null) {
121 if (HAS_PARSER) {
122 return prototype.getParserForType().parseFrom(array, offset, length);
123 } else {
124 return prototype.newBuilderForType().mergeFrom(array, offset, length).build();
125 }
126 } else {
127 if (HAS_PARSER) {
128 return prototype.getParserForType().parseFrom(array, offset, length, extensionRegistry);
129 } else {
130 return prototype.newBuilderForType().mergeFrom(array, offset, length, extensionRegistry).build();
131 }
132 }
133 }
134 }