package alien4cloud.it.utils.websocket;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j;
import alien4cloud.rest.utils.JsonUtil;
import io.netty.buffer.ByteBufInputStream;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.stomp.DefaultStompFrame;
import io.netty.handler.codec.stomp.StompCommand;
import io.netty.handler.codec.stomp.StompFrame;
import io.netty.handler.codec.stomp.StompHeaders;
/**
* @author Minh Khang VU
*/
@Slf4j
public class StompClientHandler extends SimpleChannelInboundHandler<Object> {
private Map<String, IStompCallback> handlers = new ConcurrentHashMap<>();
private ChannelPromise connectFuture;
public void beginStomp(Channel channel) throws Exception {
StompFrame connFrame = new DefaultStompFrame(StompCommand.CONNECT);
connFrame.headers().set(StompHeaders.ACCEPT_VERSION, "1.2");
channel.writeAndFlush(connFrame);
if (log.isDebugEnabled()) {
log.debug("Begin web socket connection");
}
}
public ChannelPromise connectFuture() {
return connectFuture;
}
public void connectFuture(ChannelPromise connectFuture) {
this.connectFuture = connectFuture;
}
public void listen(String topic, IStompCallback callback) {
this.handlers.put(topic, callback);
}
@Override
public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
StompFrame frame = (StompFrame) msg;
String destination = null;
if (frame.headers().get(StompHeaders.DESTINATION) != null) {
destination = frame.headers().get(StompHeaders.DESTINATION).toString();
}
if (log.isDebugEnabled()) {
log.debug("Received frame {} from topic {}", toString(frame), destination);
}
IStompCallback callback = null;
if (destination != null) {
callback = handlers.get(destination);
if (callback == null) {
throw new IllegalStateException("Received message for a topic that was never registered before");
}
}
switch (frame.command()) {
case CONNECTED:
connectFuture.setSuccess();
break;
case MESSAGE:
if (String.class == callback.getExpectedDataType()) {
callback.onData(frame.headers().get(StompHeaders.DESTINATION).toString(), frame.content().toString(Charset.forName("UTF-8")));
} else {
callback.onData(frame.headers().get(StompHeaders.DESTINATION).toString(),
JsonUtil.readObject(new ByteBufInputStream(frame.content()), callback.getExpectedDataType()));
}
break;
case ERROR:
String frameText = toString(frame);
log.error("Received stomp error {} for topic {}", frameText, destination);
callback.onError(new StompErrorException("Stomp error for destination " + destination + " :\n" + frameText));
break;
default:
frameText = toString(frame);
log.error("Received unknown frame {} for topic {}", frameText, destination);
callback.onError(new StompUnknownCommandException("Unknown stomp command " + frame.command() + " from frame :\n" + frameText));
break;
}
}
private String toString(StompFrame frame) {
StringBuilder buffer = new StringBuilder();
buffer.append("------------------------------------------------\n");
buffer.append("COMMAND :").append(frame.command()).append("\n");
buffer.append("------------------------------------------------\n");
buffer.append("HEADERS :\n");
Iterator<Entry<CharSequence, CharSequence>> headerIterator = frame.headers().iterator();
while (headerIterator.hasNext()) {
Entry<CharSequence, CharSequence> header = headerIterator.next();
buffer.append(header.getKey()).append(" : ").append(header.getValue()).append("\n");
}
buffer.append("------------------------------------------------\n");
buffer.append("CONTENT :\n");
buffer.append("------------------------------------------------\n");
buffer.append(frame.content().toString(Charset.forName("UTF-8"))).append("\n");
return buffer.toString();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.error("Stomp error", cause);
ctx.close();
for (IStompCallback callback : handlers.values()) {
callback.onError(cause);
}
}
}