package ws.wamplay.controllers; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import com.fasterxml.jackson.databind.JsonNode; import play.Logger; import play.Logger.ALogger; import play.libs.F.Callback; import play.libs.F.Callback0; import play.mvc.Controller; import play.mvc.WebSocket; import ws.wamplay.annotations.URIPrefix; import ws.wamplay.callbacks.PubSubCallback; import ws.wamplay.controllers.messageHandlers.HandlerFactory; import ws.wamplay.controllers.messageHandlers.MessageHandler; import ws.wamplay.controllers.messageHandlers.PublishHandler; import ws.wamplay.models.PubSub; import ws.wamplay.models.RPC; import ws.wamplay.models.WAMPlayClient; public class WAMPlayServer extends Controller { public static String VERSION = "WAMPlay/0.1.6"; public static int PROTOCOL_VERSION = 1; public static WAMPlayClient lastClient; static ALogger log = Logger.of(WAMPlayServer.class); static Map<String, WAMPlayClient> clients = Collections .unmodifiableMap(new HashMap<String, WAMPlayClient>()); /** * Handle the websocket. */ public static WebSocket<JsonNode> connect() { return new WebSocket<JsonNode>() { // Called when the Websocket Handshake is done. public void onReady(WebSocket.In<JsonNode> in, final WebSocket.Out<JsonNode> out) { final WAMPlayClient client = new WAMPlayClient(out); WAMPlayServer.addClient(client); if (in != null) { // null if testing. in.onClose(new Callback0() { @Override public void invoke() throws Throwable { WAMPlayServer.removeClient(client); } }); in.onMessage(new Callback<JsonNode>() { @Override public void invoke(JsonNode request) throws Throwable { handleRequest(client, request); } }); } handleRequest(client, null); lastClient = client; } }; } /** * Sends a raw WAMP message to the correct controller. Method is public for * easier testing. Do not use in your application. * * @param Raw * WAMP JSON request. * @param Originating * client. */ public static void handleRequest(WAMPlayClient client, JsonNode request) { try { MessageHandler handler = HandlerFactory.get(request); handler.process(client, request); } catch (IllegalArgumentException e) { e.printStackTrace(); } } private static synchronized void addClient(WAMPlayClient client) { Map<String, WAMPlayClient> clientsNew = new HashMap<String, WAMPlayClient>(); clientsNew.putAll(clients); clientsNew.put(client.getSessionID(), client); clients = Collections.unmodifiableMap(clientsNew); log.debug("WAMPClient: " + client.getSessionID() + " connected."); } private static synchronized void removeClient(WAMPlayClient client) { Map<String, WAMPlayClient> clientsNew = new HashMap<String, WAMPlayClient>(); clientsNew.putAll(clients); clientsNew.remove(client.getSessionID()); clients = Collections.unmodifiableMap(clientsNew); log.debug("WAMPClient: " + client.getSessionID() + " disconnected."); } /** * Gets a connected client with a ID. Can be used to send arbitrary JSON to * a specific client. * * @param sessionID * Client's session ID as a string. * @return Connected WAMP client. Returns null if there is no client. */ public static WAMPlayClient getClient(String clientID) { return clients.get(clientID); } /** * Gets a copy of the map of all the currently connected clients. This map * is immutable. * * @return A map of the currently connected WAMP clients. */ public static Map<String, WAMPlayClient> getClients() { return clients; } /** * Add a PubSub topic and callbacks for clients to interact with. Topics * must be specifically added or clients could kill the server by filling it * up with useless topics. Adding topics through a controller's @onPublish * or @onSubscribe annotation is the preferred method. * * @param topicURI * @param pubSubCallback */ public static void addTopic(String topicURI, PubSubCallback pubSubCallback) { PubSub.addTopic(topicURI, pubSubCallback); } /** * Remove a topic from the server. Clients will no longer be able to publish * or subscribe to this topic. * * @param topicURI */ public static void removeTopic(String topicURI) { PubSub.removeTopic(topicURI); } /** * Add a PubSub topic with no callbacks for clients to interact with. Topics * must be specifically added or clients could kill the server by filling it * up with useless topics. Adding topics through a controller's @onPublish * or @onSubscribe annotation is the preferred method. * * @param topicURI */ public static void addTopic(String topicURI) { PubSub.addTopic(topicURI); } /** * Checks if topicURI is a valid (subscribable) topic. * * @param topicURI * @return if the URI points to a valid topic */ public static boolean isTopic(String topicURI) { return PubSub.getPubSubCallback(topicURI) == null ? false : true; } /** * Registers a controller for RPC and/or PubSub. Only one onPublish or * onSubscribe annotation is needed to add a topic. * * @param controller */ public static void addController(WAMPlayContoller controller) { String prefix = ""; if (controller.getClass().isAnnotationPresent(URIPrefix.class)) { prefix = controller.getClass().getAnnotation(URIPrefix.class) .value(); } PubSub.addController(prefix, controller); RPC.addController(prefix, controller); } /** * Resets WAMPlayServer's state. Removes all controllers, topics, and * clients. */ public static synchronized void reset() { clients = Collections .unmodifiableMap(new HashMap<String, WAMPlayClient>()); PubSub.reset(); RPC.reset(); } /** * Publish an event to all clients with sessionIDs not in the exclude * collection. * * @param topicURI * @param event * @param exclude * , Collection of sessionIDs to exclude. */ public static void publishExclude(String topicURI, JsonNode event, Collection<String> exclude) { PublishHandler.publish(topicURI, event, exclude, null); } /** * Publish an event to all clients with sessionIDs in the eligible * collection. * * @param topicURI * @param event * @param exclude * , Collection of eligible sessionIDs. */ public static void publishEligible(String topicURI, JsonNode event, Collection<String> eligible) { PublishHandler.publish(topicURI, event, null, eligible); } /** * Publish an event to all clients. * * @param topicURI * @param event */ public static void publish(String topicURI, JsonNode event) { PublishHandler.publish(topicURI, event, null, null); } }