package com.ibm.streamsx.inet.wsserver; /* # Licensed Materials - Property of IBM # Copyright IBM Corp. 2013, 2014 */ import java.util.Iterator; import java.util.Queue; import org.apache.log4j.Logger; import com.ibm.json.java.JSONArray; import com.ibm.json.java.JSONObject; import com.ibm.streams.operator.OperatorContext; import com.ibm.streams.operator.encoding.EncodingFactory; import com.ibm.streams.operator.encoding.JSONEncoding; import com.ibm.streams.operator.log4j.TraceLevel; import com.ibm.streams.operator.metrics.Metric; import com.ibm.streams.operator.metrics.Metric.Kind; import com.ibm.streams.operator.model.CustomMetric; import com.ibm.streams.operator.model.InputPortSet; import com.ibm.streams.operator.model.InputPortSet.WindowMode; import com.ibm.streams.operator.model.InputPorts; import com.ibm.streams.operator.model.Libraries; import com.ibm.streams.operator.model.Parameter; import com.ibm.streams.operator.model.PrimitiveOperator; import com.ibm.streams.operator.model.Icons; import com.ibm.streams.operator.samples.patterns.TupleConsumer; /** * A Sink class operator that is connected to multiple websocket clients, * messages received on the input port are sent out as json messages. * * Clients can drop and connect at anytime. Message are only sent to those * that are connected at the time that the tuple arrives on the input port. * */ @PrimitiveOperator( description=WebSocketSend.primDesc) @InputPorts({@InputPortSet(description=WebSocketSend.parmPortDesc, cardinality=1, optional=false, windowingMode=WindowMode.NonWindowed)}) @Libraries("opt/wssupport/Java-WebSocket-1.3.0.jar") @Icons(location32="icons/WebSocketSend_32.gif", location16="icons/WebSocketSend_16.gif") public class WebSocketSend extends TupleConsumer { final static String primDesc = "Operator transmits tuples recieved on the input port via WebSocket protocol to connected clients." + "Upon startup, starts WebSocket Server. As tuple arrives on the input port they're converted into" + "JSON formatted messages " + "and transmitted to all currently connected clients. Clients can connect and disconnect at anytime."; final static String parmPortDesc = "Port that clients connect to and tuples formatted as JSON message are transmitted over."; static final String CLASS_NAME="com.ibm.streamsx.inet.wsserver"; private static Logger trace = Logger.getLogger(CLASS_NAME); private WSServer wsServer; private int portNum; private Metric nMessagesSent; private Metric nClientsConnected; @CustomMetric(description="Number of messages sent using WebSocket", kind=Kind.COUNTER) public void setnMessagesSent(Metric nPostRequests) { this.nMessagesSent = nPostRequests; } public Metric getnMessagesSent() { return nMessagesSent; } @CustomMetric(description="Number of clients currently using WebSocket", kind=Kind.GAUGE) public void setnClientsConnected(Metric nClientsConnected) { this.nClientsConnected = nClientsConnected; } public Metric getnClientsConnected() { return nClientsConnected; } private boolean active = false; // Mandatory port @Parameter(name="port", optional=false, description=parmPortDesc) public void setPort(int portNum) { this.portNum = portNum; } public int getPort() { return this.portNum; } /** * Initialize this operator. Called once before any tuples are processed. * @param context OperatorContext for this operator. * @throws Exception Operator failure, will cause the enclosing PE to terminate. */ @Override public synchronized void initialize(OperatorContext context) throws Exception { super.initialize(context); // we will be batching.... setBatchSize(getBatchSize()); // Setup connection // TODO what to do if you have error here wsServer = new WSServer(portNum); active = true; wsServer.setWebSocketSink(this); } /** * Notification that initialization is complete and all input and output ports * are connected and ready to receive and submit tuples. * @throws Exception Operator failure, will cause the enclosing PE to terminate. */ @Override public synchronized void allPortsReady() throws Exception { OperatorContext context = getOperatorContext(); trace.log(TraceLevel.INFO,"allPortsReady(): Operator " + context.getName() + " all ports are ready in PE: " + context.getPE().getPEId() + " in Job: " + context.getPE().getJobId() + " starting WebServer."); // Start the server, it will wait for new client connections - send tuples to all connected clients. wsServer.start(); } @Override protected final boolean processBatch(Queue<BatchedTuple> batch) throws Exception { JSONEncoding jsonEncoding = EncodingFactory.getJSONEncoding(); int tuplesInRequest = 0; if (trace.isEnabledFor(TraceLevel.TRACE)) { trace.log(TraceLevel.TRACE, "processBatch() : batchSize:" + getBatchSize()); } JSONArray tuples = new JSONArray(); for (Iterator<BatchedTuple> iter = batch.iterator(); iter.hasNext(); ) { if (tuplesInRequest++ == getBatchSize()) break; BatchedTuple item = iter.next(); iter.remove(); JSONObject tuple = new JSONObject(); //tuple.put("tuple", jsonEncoding.encodeAsString(item.getTuple())); tuple.put("tuple", jsonEncoding.encodeTuple(item.getTuple())); //tuple.put("tuple", item.getTuple()); tuples.add(tuple); } JSONObject message = new JSONObject(); message.put("tuples", tuples); if (trace.isEnabledFor(TraceLevel.TRACE)) { trace.log(TraceLevel.TRACE, "processBatch() : sending tuplesInRequest:" + tuplesInRequest); } int sentCount = wsServer.sendToAll(message); getnClientsConnected().setValue(sentCount); getnMessagesSent().setValue(wsServer.getTotalSentCount()); return true; } /** * Shutdown this operator. * @throws Exception Operator failure, will cause the enclosing PE to terminate. */ @Override public synchronized void shutdown() throws Exception { super.shutdown(); OperatorContext context = getOperatorContext(); trace.log(TraceLevel.INFO,"shutdown():Operator " + context.getName() + " shutting down in PE: " + context.getPE().getPEId() + " in Job: " + context.getPE().getJobId() + " stopping WebServer."); active = false; wsServer.stop(); } }