package com.ibm.streamsx.inet.wsserver;
/*
# Licensed Materials - Property of IBM
# Copyright IBM Corp. 2013, 2014
*/
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Collection;
import org.apache.log4j.Logger;
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import com.ibm.json.java.JSONObject;
import com.ibm.streams.operator.log4j.TraceLevel;
/**
* <P>Simple streams socket server - both receives and sends.<p>
* <p>This code acts as a WS server, applications connect up
* and send to this server or receive messages from it. In the case that
* messages are received via WS the operator utilizing this is a source.
* In the case that the operator is acting as sink, the server is sending
* messages out.</p>
*/
public class WSServer extends WebSocketServer {
static final String CLASS_NAME="com.ibm.streamsx.inet.wsserver";
private static Logger trace = Logger.getLogger(CLASS_NAME);
int count = 0;
long totalSentCount = 0;
private int ackCount = 0;
WebSocketInject wsSource;
WebSocketSend wsSink;
public WSServer( int port ) throws UnknownHostException {
super( new InetSocketAddress( port ) );
}
/**
* Our primary duty is to receive messages from WS and push them onto the stream as tuples.
* @param wsSource
*/
public void setWebSocketSource(WebSocketInject wsSource) {
this.wsSource = wsSource;
}
/**
* Our primary duty is to receive tuples from the streams and send them to clients
* that have connected to us.
* @param wsSink
*/
public void setWebSocketSink(WebSocketSend wsSink) {
this.wsSink = wsSink;
trace.log(TraceLevel.INFO,"setWebSocketSink port : " + wsSink.getPort() );
}
/**
* Do not do this unless you are going to be a client or a server.
* @param address
*/
public WSServer( InetSocketAddress address ) {
super( address );
trace.log(TraceLevel.INFO,"InetSocketAddress : " + address.toString());
}
public int getAckCount() {
return ackCount;
}
public void setAckCount(int ackCount) {
this.ackCount = ackCount;
trace.log(TraceLevel.INFO,"setAckCount() : " + ackCount);
}
@Override
public void onOpen( WebSocket conn, ClientHandshake handshake ) {
this.statusToAll( "OPEN", "R:" + conn.getRemoteSocketAddress().getHostName() + " L:" + conn.getLocalSocketAddress().getHostName());
}
@Override
public void onClose( WebSocket conn, int code, String reason, boolean remote ) {
this.statusToAll("CLOSE", "R:" + conn.getRemoteSocketAddress().getHostName() + " L:" + conn.getLocalSocketAddress().getHostName());
}
@Override
public void onMessage( WebSocket conn, String message ) {
count++;
// send the number of messages received to ALL the senders.
if ((this.ackCount != 0) && (count % this.ackCount) == 0) {
statusToAll("COUNT", String.format("%d",count));
}
try {
if (wsSource != null) {
wsSource.produceTuples(message,conn.getRemoteSocketAddress().toString());
if (trace.isEnabledFor(TraceLevel.TRACE)) { trace.log(TraceLevel.TRACE, "onMessage() source-" + conn.getRemoteSocketAddress().getAddress().getHostAddress() + "::" + message); }
} else {
if (trace.isEnabledFor(TraceLevel.TRACE)) { trace.log(TraceLevel.TRACE, "onMessage() sink-" + conn.getRemoteSocketAddress().getAddress().getHostAddress() + "::" + message); }
}
} catch (Exception e) {
trace.log(TraceLevel.ERROR, "WSServer onMessage(): " + message + " err: " + e.getMessage() );
//e.printStackTrace();
}
}
@Override
public void onError( WebSocket conn, Exception ex ) {
trace.log(TraceLevel.ERROR, "WSServer onError(): client: " + ( conn!=null ? conn.getRemoteSocketAddress().toString() : "unknown" ) + " err: " + ex.getMessage() );
//ex.printStackTrace();
//if( conn != null ) {
// // some errors like port binding failed may not be assignable to a specific websocket
//}
}
/**
* Sends <var>jsonMessage</var> to all currently connected WebSocket clients.
*
* @param jsonMessage to transmit
*
* @throws InterruptedException
* When socket related I/O errors occur.
* @return number of messages sent, thus the number of active connections.
*/
public int sendToAll( JSONObject jsonMessage ) {
Collection<WebSocket> con = connections();
String message = null;
try {
message = jsonMessage.serialize();
if (trace.isEnabledFor(TraceLevel.TRACE)) { trace.log(TraceLevel.TRACE, "sendToAll() : " + message); }
} catch (IOException e) {
trace.log(TraceLevel.ERROR, "WSServer sendToAll(): " + jsonMessage.toString() + " err: " + e.getMessage() );
}
int cnt = 0;
if (message != null) {
synchronized ( con ) {
for( WebSocket c : con ) {
if (trace.isEnabledFor(TraceLevel.TRACE)) { trace.log(TraceLevel.TRACE, "sendToAll()" + c.getRemoteSocketAddress().getAddress().getHostAddress() + "::" + cnt++ + " of " + con.size()); }
c.send( message );
}
}
totalSentCount += cnt;
}
return cnt;
}
/**
* Get total number of messages sent: WSconnections * messages where WSConnections
* varies over time.
*
*/
public long getTotalSentCount() {
return totalSentCount;
}
/**
* Send a status/control message to all, since we have control and data on the same
* interface need a consistent way to send such a message.
* @param status
* @param text
*/
private void statusToAll(String status, String text) {
JSONObject controlMessage = new JSONObject();
JSONObject controlBody = new JSONObject();
controlBody.put("status", status);
controlBody.put("value", text);
controlMessage.put("control", controlBody);
sendToAll(controlMessage);
}
public long getClientCount() {
return connections().size();
}
}