/*
* Copyright 2014 Netflix, Inc.
*
* 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 io.reactivex.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.reactivex.netty.pipeline.PipelineConfigurator;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RxEventPipelineConfigurator implements PipelineConfigurator<RemoteRxEvent, RemoteRxEvent>{
private static final Logger logger = LoggerFactory.getLogger(RxEventPipelineConfigurator.class);
private static final byte PROTOCOL_VERSION = 1;
@Override
public void configureNewPipeline(ChannelPipeline pipeline) {
pipeline.addLast(new ChannelDuplexHandler(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
boolean handled = false;
if (ByteBuf.class.isAssignableFrom(msg.getClass())) {
ByteBuf byteBuf = (ByteBuf) msg;
if (byteBuf.isReadable()) {
int protocolVersion = byteBuf.readByte();
if (protocolVersion != PROTOCOL_VERSION){
throw new RuntimeException("Unsupported protocol version: "+protocolVersion);
}
int observableNameLength = byteBuf.readByte();
String observableName = null;
if (observableNameLength > 0){
// read name
observableName = new String(byteBuf.readBytes(observableNameLength).array());
}
int operation = byteBuf.readByte();
RemoteRxEvent.Type type = null;
Map<String,String> subscribeParams = null;
byte[] valueData = null;
if (operation == 1){
logger.debug("READ request for RemoteRxEvent: next");
type = RemoteRxEvent.Type.next;
valueData = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(valueData);
}else if (operation == 2){
logger.debug("READ request for RemoteRxEvent: error");
type = RemoteRxEvent.Type.error;
valueData = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(valueData);
}else if (operation == 3){
logger.debug("READ request for RemoteRxEvent: completed");
type = RemoteRxEvent.Type.completed;
}else if (operation == 4){
logger.debug("READ request for RemoteRxEvent: subscribed");
type = RemoteRxEvent.Type.subscribed;
// read subscribe parameters
int subscribeParamsLength = byteBuf.readInt();
if (subscribeParamsLength > 0){
// read byte into map
byte[] subscribeParamsBytes = new byte[subscribeParamsLength];
byteBuf.readBytes(subscribeParamsBytes);
subscribeParams = fromBytesToMap(subscribeParamsBytes);
}
}else if (operation == 5){
logger.debug("READ request for RemoteRxEvent: unsubscribed");
type = RemoteRxEvent.Type.unsubscribed;
}else{
throw new RuntimeException("operation: "+operation+" not support.");
}
handled = true;
byteBuf.release();
ctx.fireChannelRead(new RemoteRxEvent(observableName, type, valueData, subscribeParams));
}
}
if (!handled){
super.channelRead(ctx, msg);
}
}
@Override
public void write(ChannelHandlerContext ctx, Object msg,
ChannelPromise promise) throws Exception {
if (msg instanceof RemoteRxEvent){
ByteBuf buf = ctx.alloc().buffer();
buf.writeByte(PROTOCOL_VERSION);
RemoteRxEvent event = (RemoteRxEvent) msg;
String observableName = event.getName();
if (observableName != null && !observableName.isEmpty()){
// write length
int nameLength = observableName.length();
if (nameLength < 127){
buf.writeByte(nameLength);
buf.writeBytes(observableName.getBytes());
}else{
throw new RuntimeException("observableName "+observableName+
" exceeds max limit of 127 characters");
}
}else{
// no name provided, write 0 bytes for name length
buf.writeByte(0);
}
if (event.getType() == RemoteRxEvent.Type.next){
logger.debug("WRITE request for RemoteRxEvent: next");
buf.writeByte(1);
buf.writeBytes(event.getData());
super.write(ctx, buf, promise);
}else if (event.getType() == RemoteRxEvent.Type.error){
logger.debug("WRITE request for RemoteRxEvent: error");
buf.writeByte(2);
buf.writeBytes(event.getData());
super.write(ctx, buf, promise);
}else if (event.getType() == RemoteRxEvent.Type.completed){
logger.debug("WRITE request for RemoteRxEvent: completed");
buf.writeByte(3);
super.write(ctx, buf, promise);
super.flush(ctx); // TODO why do I need to explicitly call flush for this to work??? empty data??
}else if (event.getType() == RemoteRxEvent.Type.subscribed){
logger.debug("WRITE request for RemoteRxEvent: subscribed");
buf.writeByte(4);
Map<String,String> subscribeParameters = event.getSubscribeParameters();
if (subscribeParameters != null && !subscribeParameters.isEmpty()){
byte[] subscribeBytes = fromMapToBytes(subscribeParameters);
buf.writeInt(subscribeBytes.length); // write int for length
buf.writeBytes(subscribeBytes); // write data
}else{
buf.writeInt(0); // no data
}
super.write(ctx, buf, promise);
super.flush(ctx);
}else if (event.getType() == RemoteRxEvent.Type.unsubscribed){
logger.debug("WRITE request for RemoteRxEvent: unsubscribed");
buf.writeByte(5);
super.write(ctx, buf, promise);
super.flush(ctx); // TODO why do I need to explicitly call flush for this to work??? empty data??
}
}else{
super.write(ctx, msg, promise);
}
}
});
}
@SuppressWarnings("unchecked")
static Map<String,String> fromBytesToMap(byte[] bytes){
Map<String,String> map = null;
ByteArrayInputStream bis = null;
ObjectInput in = null;
try{
bis = new ByteArrayInputStream(bytes);
in = new ObjectInputStream(bis);
map = (Map<String,String>) in.readObject();
}catch(IOException e){
throw new RuntimeException(e);
}catch(ClassNotFoundException e1){
throw new RuntimeException(e1);
}finally{
try {
if (bis != null){bis.close();}
if (in != null){in.close();}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return map;
}
static byte[] fromMapToBytes(Map<String,String> map){
ByteArrayOutputStream baos = null;
ObjectOutput out = null;
try{
baos = new ByteArrayOutputStream();
out = new ObjectOutputStream(baos);
out.writeObject(map);
}catch(IOException e){
throw new RuntimeException(e);
}finally{
try{
if (out != null){out.close();}
if (baos != null){baos.close();}
}catch(IOException e1){
e1.printStackTrace();
throw new RuntimeException(e1);
}
}
return baos.toByteArray();
}
}