/** * 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.wsclient; import java.net.SocketAddress; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.Executor; import org.helios.apmrouter.util.SimpleLogger; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelHandler.Sharable; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelState; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.DownstreamMessageEvent; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelHandler; import org.jboss.netty.channel.UpstreamMessageEvent; import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame; import org.json.JSONObject; /** * <p>Title: JsonCodec</p> * <p>Description: Coder/Encoder for JSON requests</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.wsclient.JsonCodec</code></p> */ @Sharable public class JsonCodec extends SimpleChannelHandler { /** A set of web socket event listeners */ protected final Set<WebSocketEventListener> eventListeners = new CopyOnWriteArraySet<WebSocketEventListener>(); /** A thread pool to execute listener notifications in */ protected final Executor threadPool; /** The elading string in the response containing the sessionId returned by the server */ public static final String SESSION_SIGNATURE = "{\"sessionid\":"; /** * Creates a new JsonCodec * @param threadPool The application thread pool */ public JsonCodec(Executor threadPool) { this.threadPool = threadPool; } /** * Registers the passed WebSocketEventListener to be notified of events on any websocket * @param listener the listener to register */ public void addWebSocketEventListener(WebSocketEventListener listener) { if(listener!=null) { eventListeners.add(listener); } } /** * Unregisters the passed WebSocketEventListener * @param listener the listener to unregister */ public void removeWebSocketEventListener(WebSocketEventListener listener) { if(listener!=null) { eventListeners.remove(listener); } } /** * Executes the {@link WebSocketEventListener#onMessage(SocketAddress, JSONObject)} event to all registered listeners. * @param sa The remote address of the server the event came from * @param json The message received from the server */ protected void fireJsonEvent(final SocketAddress sa, final JSONObject json) { threadPool.execute(new Runnable(){ @Override public void run() { for(WebSocketEventListener listener: eventListeners) { listener.onMessage(sa, json); } } }); } /** * Executes the {@link WebSocketEventListener#onError(SocketAddress, Throwable)} event to all registered listeners. * @param sa The remote address of the server the event came from * @param exception The exception thrown from the pipeline */ protected void fireExceptionEvent(final SocketAddress sa, final Throwable exception) { threadPool.execute(new Runnable(){ @Override public void run() { for(WebSocketEventListener listener: eventListeners) { listener.onError(sa, exception); } } }); } /** * Executes the {@link WebSocketEventListener#onClose(SocketAddress) } event to all registered listeners. * @param sa The remote address of the server connected to */ protected void fireCloseEvent(final SocketAddress sa) { threadPool.execute(new Runnable(){ @Override public void run() { for(WebSocketEventListener listener: eventListeners) { listener.onClose(sa); } } }); } /** * Executes the {@link WebSocketEventListener#onConnect(SocketAddress) } event to all registered listeners. * @param sa The remote address of the server connected to */ protected void fireConnectedEvent(final SocketAddress sa) { threadPool.execute(new Runnable(){ @Override public void run() { for(WebSocketEventListener listener: eventListeners) { listener.onConnect(sa); } } }); } /** * {@inheritDoc} * @see org.jboss.netty.channel.SimpleChannelHandler#messageReceived(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.MessageEvent) */ @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { //SimpleLogger.debug("JsonCodec: Received from [", e.getRemoteAddress() , "]-->[", e.getMessage(), "]" ); Object event = e.getMessage(); if(event instanceof TextWebSocketFrame) { TextWebSocketFrame textFrame = (TextWebSocketFrame)event; String message = textFrame.getText(); if(message!=null && message.indexOf(SESSION_SIGNATURE)!=-1) { super.messageReceived(ctx, new UpstreamMessageEvent(e.getChannel(), "" + new JSONObject(message).get("sessionid"), e.getRemoteAddress())); } else { ChannelHandlerContext synchContext = ctx.getPipeline().getContext("Synch" + ctx.getChannel().getId()); if(synchContext!=null && synchContext.canHandleUpstream()) { ctx.getPipeline().getContext(this).sendUpstream(new UpstreamMessageEvent(e.getChannel(), new JSONObject(textFrame.getText()), e.getRemoteAddress())); } else { fireJsonEvent(e.getRemoteAddress(), new JSONObject(textFrame.getText())); } } } } /** * {@inheritDoc} * @see org.jboss.netty.channel.SimpleChannelHandler#exceptionCaught(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.ExceptionEvent) */ @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { WebSocketClientHandler wsc = (WebSocketClientHandler)ctx.getPipeline().get("ws-handler"); SocketAddress sa = wsc.getRemoteSocketAddress(); fireExceptionEvent(sa, e.getCause()); super.exceptionCaught(ctx, e); } /** * {@inheritDoc} * @see org.jboss.netty.channel.SimpleChannelHandler#channelClosed(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.ChannelStateEvent) */ @Override public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { if(e.getState().equals(ChannelState.OPEN) && e.getValue().equals(false)) { fireCloseEvent(e.getChannel().getRemoteAddress()); } super.channelClosed(ctx, e); } /** * {@inheritDoc} * @see org.jboss.netty.channel.SimpleChannelHandler#channelConnected(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.ChannelStateEvent) */ @Override public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { if(e.getState().equals(ChannelState.CONNECTED)) { fireConnectedEvent(e.getChannel().getRemoteAddress()); } super.channelConnected(ctx, e); } /** * {@inheritDoc} * @see org.jboss.netty.channel.SimpleChannelHandler#writeRequested(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.MessageEvent) */ @Override public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) throws Exception { Object msg = e.getMessage(); final Channel channel = e.getChannel(); if(msg instanceof JSONObject) { byte[] payload = msg.toString().getBytes(); super.writeRequested(ctx, new DownstreamMessageEvent(channel, Channels.future(channel), new TextWebSocketFrame(ChannelBuffers.wrappedBuffer(payload)), channel.getRemoteAddress())); return; } else if(msg instanceof CharSequence) { super.writeRequested(ctx, new DownstreamMessageEvent(channel, Channels.future(channel), new TextWebSocketFrame(msg.toString()), channel.getRemoteAddress())); return; } super.writeRequested(ctx, e); } }