package automately.core.services.sdk;
import automately.core.services.core.AutomatelyService;
import com.hazelcast.core.IMap;
import io.jsync.Async;
import io.jsync.AsyncResult;
import io.jsync.Handler;
import io.jsync.app.core.Cluster;
import io.jsync.app.core.Config;
import io.jsync.app.core.Logger;
import io.jsync.eventbus.EventBus;
import io.jsync.eventbus.Message;
import io.jsync.json.JsonArray;
import io.jsync.json.JsonObject;
import io.jsync.sockjs.EventBusBridgeHook;
import io.jsync.sockjs.SockJSServer;
import io.jsync.sockjs.SockJSSocket;
import java.util.*;
/**
* The SdkSockJSServer is the default SockJS server for handling any SDK Operations
*/
public class SdkSockJSServer extends AutomatelyService {
// TODO Complete JavaDocs
// We want to set the sessions
private io.jsync.http.HttpServer httpServer;
private Logger logger;
@Override
public void start(Cluster owner) {
Async async = owner.async();
Config config = owner.config();
if (!coreConfig().containsField("sdk_httpserver_host")) coreConfig().putValue("sdk_httpserver_host", config.clusterHost());
if (!coreConfig().containsField("sdk_httpserver_port")) coreConfig().putValue("sdk_httpserver_port", 9080);
config.save();
this.logger = owner.logger();
httpServer = async.createHttpServer();
JsonObject bridgeConfig = new JsonObject();
bridgeConfig.putString("auth_address", "automately.sdk.auth.authorise");
// Increased auth_timeout
bridgeConfig.putNumber("auth_timeout", 24 * 60 * 1000);
// We are increasing the amount to 25.
bridgeConfig.putNumber("max_handlers_per_socket", 25);
// Handlers we can send messages to from the client side..
JsonArray confInbound = new JsonArray();
// Handlers we can bind to from the client side..
JsonArray confOutbound = new JsonArray();
JsonObject sdkAuth = new JsonObject();
sdkAuth.putString("address_re", "automately.sdk.auth..+");
confInbound.add(sdkAuth);
// Used to retrieve info about jobs and such
JsonObject sdkJob = new JsonObject();
sdkJob.putString("address_re", "automately.sdk.job..+");
sdkJob.putBoolean("requires_auth", true);
confInbound.add(sdkJob);
// Allow messageBus messages to be sent to public handlers
JsonObject sdkMessageBusOutbound = new JsonObject();
sdkMessageBusOutbound.putString("address", "automately.sdk.messageBus");
sdkMessageBusOutbound.putBoolean("requires_auth", true);
confInbound.add(sdkMessageBusOutbound);
JsonObject sdkMessageBusInbound = new JsonObject();
sdkMessageBusInbound.putString("address_re", "automately.sdk.messageBus..+");
sdkMessageBusInbound.putBoolean("requires_auth", true);
confOutbound.add(sdkMessageBusInbound);
SockJSServer sockJSServer = owner.async().createSockJSServer(httpServer);
// We utilize a hook so we can intercept all handlers that need to be mapped to the client
sockJSServer.setHook(new EventBusBridgeHook() {
private IMap<String, String> clientMessageBusHandlers = cluster().data().getMap("sdk.messageBus.handlers");
private HashMap<SockJSSocket, List<Map.Entry<String, Map.Entry<String, Handler<Message>>>>> registeredHandlers = new HashMap<>();
private EventBus eventBus = cluster().eventBus();
public boolean handleSocketCreated(SockJSSocket sock) {
return true;
}
@Override
public void handleSocketClosed(SockJSSocket sock) {
List<Map.Entry<String, Map.Entry<String, Handler<Message>>>> handlers = registeredHandlers.get(sock);
if (handlers != null) {
for (Map.Entry<String, Map.Entry<String, Handler<Message>>> entry : handlers) {
Map.Entry<String, Handler<Message>> entry1 = entry.getValue();
eventBus.unregisterHandler(entry1.getKey(), entry1.getValue());
}
}
registeredHandlers.remove(sock);
}
@Override
public boolean handleSendOrPub(SockJSSocket sock, boolean send, JsonObject msg, String address) {
return true;
}
@Override
public boolean handlePreRegister(SockJSSocket sock, String address) {
if (address.startsWith("automately.sdk.messageBus.")) {
// If this address is not stored in the map then
// we will not allow it to be registered.. This means
// people could spoof thingies..
if (!clientMessageBusHandlers.containsKey(address)) {
return false;
}
String internalIdentifier = clientMessageBusHandlers.get(address);
Handler<Message> handler = event -> eventBus.send(address, event.body());
eventBus.registerHandler(internalIdentifier, handler);
List<Map.Entry<String, Map.Entry<String, Handler<Message>>>> list = registeredHandlers.get(sock);
if (list == null) {
registeredHandlers.put(sock, list = new ArrayList<>());
}
list.add(new AbstractMap.SimpleEntry<>(address, new AbstractMap.SimpleEntry<>(internalIdentifier, handler)));
clientMessageBusHandlers.remove(address);
return true;
}
// It may seem weird that we assume it's okay to register a handler... It's generally not.
// So the best idea is to not trust the handlers registered on the SockJS
return true;
}
@Override
public void handlePostRegister(SockJSSocket sock, String address) {
}
@Override
public boolean handleUnregister(SockJSSocket sock, String address) {
List<Map.Entry<String, Map.Entry<String, Handler<Message>>>> handlers = registeredHandlers.get(sock);
if (handlers != null) {
for (Map.Entry<String, Map.Entry<String, Handler<Message>>> entry : handlers) {
if (entry.getKey().equals(address)) {
Map.Entry<String, Handler<Message>> routeHandler = entry.getValue();
eventBus.unregisterHandler(routeHandler.getKey(), routeHandler.getValue());
}
}
}
return true;
}
@Override
public boolean handleAuthorise(JsonObject message, String sessionID, Handler<AsyncResult<Boolean>> handler) {
return false;
}
});
sockJSServer.bridge(new JsonObject().putString("prefix", "/automately.sdk.eventBus"), confInbound, confOutbound, bridgeConfig);
// Optimization.
httpServer.setMaxWebSocketFrameSize(262144);
// Done with our exposing..
httpServer.listen(coreConfig().getValue("sdk_httpserver_port"), coreConfig().getValue("sdk_httpserver_host"), asyncResult -> {
if (!asyncResult.succeeded()) {
logger.fatal("Could not start DefaultHttpServer on " + coreConfig().getValue("sdk_httpserver_host") + ":" + coreConfig().getValue("sdk_httpserver_port"));
return;
}
logger.info("SdkSockJSServer Started on " + coreConfig().getValue("sdk_httpserver_host") + ":" + coreConfig().getValue("sdk_httpserver_port"));
});
}
@Override
public void stop() {
httpServer.close(asyncResult -> {
if (asyncResult.succeeded()) {
logger.info("SdkSockJSServer Stopped");
}
});
}
@Override
public String name() {
return getClass().getCanonicalName();
}
}