
org.graphwalker.websocket.WebSocketServer Maven / Gradle / Ivy
package org.graphwalker.websocket;
/*
* #%L
* GraphWalker As A Service
* %%
* Copyright (C) 2005 - 2014 GraphWalker
* %%
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* #L%
*/
import org.graphwalker.core.event.EventType;
import org.graphwalker.core.event.Observer;
import org.graphwalker.core.machine.Context;
import org.graphwalker.core.machine.Machine;
import org.graphwalker.core.machine.SimpleMachine;
import org.graphwalker.core.model.Element;
import org.graphwalker.io.factory.gw3.GW3ContextFactory;
import org.graphwalker.modelchecker.ContextChecker;
import org.graphwalker.modelchecker.ContextsChecker;
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.util.*;
/**
* A WebSocketServer with an API for working with GraphWalker as a service.
*
* The websocket API has the following methods:
*
* -
loadModel
* Loads a model into the service. The model must use JSON notation for a GraphWalker model.
* The notation has the following format:
*
* {
* "command":"loadModel",
* "model":{
* "name":"Small model",
* "id":"m1",
* "generator":"random(edge_coverage(100))",
* "startElementId":"e0",
* "vertices":[
* {
* "name":"v_VerifySomeAction",
* "id":"n0",
* "requirements":[
* "UC01 2.2.1"
* ]
* },
* {
* "name":"v_VerifySomeOtherAction",
* "id":"n1"
* }
* ],
* "edges":[
* {
* "name":"e_FirstAction",
* "id":"e0",
* "actions":[
* "index = 0;",
* "str = '';"
* ],
* "targetVertexId":"n0"
* },
* {
* "name":"e_AnotherAction",
* "id":"e1",
* "guard":"index <= 3",
* "sourceVertexId":"n0",
* "targetVertexId":"n1"
* },
* {
* "name":"e_SomeOtherAction",
* "id":"e2",
* "actions":[
* "index++;"
* ],
* "sourceVertexId":"n1",
* "targetVertexId":"n1"
* },
* {
* "id":"e3",
* "sourceVertexId":"n1",
* "targetVertexId":"n0"
* }
* ]
* }
* }
*
*
* -
getModel
* Will return the model with the given id modelId
from the service.
*
* {
* "command":"getModel",
* "modelId":"someId"
* }
*
*
* -
start
* Tells the service to get the machine ready to execute the model(s).
*
* {
* "command":"start"
* }
*
*
* -
getNext
* Asks the service for the next element to be executed.
*
* {
* "command":"getNext"
* }
*
*
* -
hasNext
* Asks the service if all conditions for all generators has been met or not.
*
* {
* "command":"hasNext"
* }
*
*
* -
restart
* Requests the service to reset the execution of the the models to the initial state.
*
* {
* "command":"restart"
* }
*
*
* -
getData
* Asks the service for the value of the given attribute.
*
* {
* "command":"getData"
* }
*
*
* -
addModel
* Asks the service to create a new empty model with the given id
.
*
* {
* "command": "addModel",
* "id": "someModelId"
* }
*
*
* -
removeModel
* Removes the model with the given modelId from the service.
*
* {
* "command":"removeModel"
* }
*
*
* -
addVertex
* Adds a vertex to the model with the given modelId
and
* vertexId
to the service.
*
* {
* "command": "addVertex",
* "modelId": "somModelId",
* "vertexId": "someVertexId"
* }
*
*
* -
addEdge
* Adds an edge to the model with the given modelId to the service.
*
* {
* "command":"addEdge"
* }
*
*
* -
updateVertex
* Updates attribute(s) to the vertex with given id and modelId from the service.
*
* {
* "command":"updateVertex"
* }
*
*
* -
updateEdge
* Updates attribute(s) to the edge with given id and modelId from the service.
*
* {
* "command":"updateEdge"
* }
*
*
* -
removeVertex
* Removes the vertex with the given id from and modelId from the service.
*
* {
* "command":"removeVertex"
* }
*
*
* -
removeEdge
* Removes the edge with the given id from and modelId from the service.
*
* {
* "command":"removeEdge"
* }
*
*
*
*/
public class WebSocketServer extends org.java_websocket.server.WebSocketServer implements Observer {
private static final Logger logger = LoggerFactory.getLogger(WebSocketServer.class);
private Set sockets;
private Map machines;
public WebSocketServer(int port) {
super(new InetSocketAddress(port));
sockets = new HashSet<>();
machines = new HashMap<>();
}
public WebSocketServer(InetSocketAddress address) {
super(address);
sockets = new HashSet<>();
machines = new HashMap<>();
}
@Override
public void onOpen(WebSocket socket, ClientHandshake handshake) {
sockets.add(socket);
machines.put(socket, null);
logger.info(socket.getRemoteSocketAddress().getAddress().getHostAddress() + " is now connected");
}
@Override
public void onClose(WebSocket socket, int code, String reason, boolean remote) {
sockets.remove(socket);
machines.remove(socket);
logger.info(socket.getRemoteSocketAddress().getAddress().getHostAddress() + " has disconnected");
}
@Override
public void onMessage(WebSocket socket, String message) {
logger.debug("Received message from: "
+ socket.getRemoteSocketAddress().getAddress().getHostAddress()
+ " : "
+ message);
JSONObject response = new JSONObject();
JSONObject root;
try {
root = new JSONObject(message);
} catch (JSONException e) {
logger.error(e.getMessage());
response.put("message", "Unknown command: " + e.getMessage());
response.put("success", false);
socket.send(response.toString());
return;
}
String command = root.getString("command").toUpperCase();
switch (command) {
case "START":
response.put("command", "start");
response.put("success", false);
List gw3Contexts = null;
try {
gw3Contexts = new GW3ContextFactory().createMultiple(root.getJSONObject("gw3").toString());
Machine machine = new SimpleMachine(gw3Contexts);
machine.addObserver(this);
machines.put(socket, machine);
response.put("success", true);
} catch (Exception e) {
logger.error(e.getMessage());
List issues = checkContexts(socket, gw3Contexts);
issues.add(e.getMessage());
sendIssues(socket, issues);
}
break;
case "GETNEXT": {
response.put("command", "getNext");
response.put("success", false);
Machine machine = machines.get(socket);
if (machine != null) {
try {
machine.getNextStep();
response.put("modelId", machine.getCurrentContext().getModel().getId());
response.put("elementId", machine.getCurrentContext().getCurrentElement().getId());
response.put("name", machine.getCurrentContext().getCurrentElement().getName());
response.put("success", true);
} catch (Exception e) {
logger.error(e.getMessage());
List issues = checkContexts(socket, machine.getContexts());
issues.add(e.getMessage());
sendIssues(socket, issues);
}
} else {
response.put("message", "The GraphWalker state machine is not initiated. Is a model loaded, and started?");
}
break;
}
case "HASNEXT": {
response.put("command", "hasNext");
response.put("success", false);
Machine machine = machines.get(socket);
try {
if (machine == null) {
response.put("message", "The GraphWalker state machine is not initiated. Is a model loaded, and started?");
} else if (machine.hasNextStep()) {
response.put("success", true);
response.put("hasNext", true);
} else {
response.put("success", true);
response.put("hasNext", false);
}
} catch (Exception e) {
logger.error(e.getMessage());
List issues = checkContexts(socket, machine.getContexts());
issues.add(e.getMessage());
sendIssues(socket, issues);
}
break;
}
case "GETDATA": {
response.put("command", "getData");
response.put("success", false);
Machine machine = machines.get(socket);
if (machine != null) {
JSONObject obj = new JSONObject();
try {
JSONObject data = new JSONObject();
for (Map.Entry k : machine.getCurrentContext().getKeys().entrySet()) {
data.put(k.getKey(), k.getValue());
}
obj.put("modelId", machine.getCurrentContext().getModel().getId());
obj.put("data", data);
obj.put("result", "ok");
response.put("data", data);
response.put("success", true);
} catch (Exception e) {
logger.error(e.getMessage());
List issues = checkContexts(socket, machine.getContexts());
issues.add(e.getMessage());
sendIssues(socket, issues);
}
} else {
response.put("message", "The GraphWalker state machine is not initiated. Is a model loaded, and started?");
}
break;
}
default:
response.put("message", "Unknown command");
response.put("success", false);
break;
}
logger.debug("Sending response to: "
+ socket.getRemoteSocketAddress().getAddress().getHostAddress()
+ " : "
+ response.toString());
socket.send(response.toString());
}
private List checkContexts(WebSocket socket, List contexts) {
if (contexts == null) {
return new ArrayList<>();
}
return ContextsChecker.hasIssues(contexts);
}
private void sendIssues(WebSocket socket, List issues) {
if (!issues.isEmpty()) {
JSONObject jsonIssue = new JSONObject();
jsonIssue.put("command", "issues");
JSONArray jsonIssues = new JSONArray();
for (String issue : issues) {
jsonIssues.put(issue);
}
jsonIssue.put("issues", jsonIssues);
logger.debug("Sending response to: "
+ socket.getRemoteSocketAddress().getAddress().getHostAddress()
+ " : "
+ jsonIssue.toString());
socket.send(jsonIssue.toString());
} else {
JSONObject jsonIssue = new JSONObject();
jsonIssue.put("command", "noIssues");
logger.debug("Sending response to: "
+ socket.getRemoteSocketAddress().getAddress().getHostAddress()
+ " : "
+ jsonIssue.toString());
socket.send(jsonIssue.toString());
}
}
@Override
public void onError(WebSocket socket, Exception ex) {
ex.printStackTrace();
}
@Override
public void update(Machine machine, Element element, EventType type) {
logger.info("Received an update from a GraphWalker machine");
Iterator it = machines.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pairs = (Map.Entry) it.next();
if (machine == pairs.getValue()) {
logger.info("Event: " + type);
WebSocket conn = (WebSocket) pairs.getKey();
if (type == EventType.AFTER_ELEMENT) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("command", "visitedElement");
jsonObject.put("modelId", machine.getCurrentContext().getModel().getId());
jsonObject.put("elementId", element.getId());
jsonObject.put("visitedCount", machine.getProfiler().getVisitCount(element));
jsonObject.put("totalCount", machine.getProfiler().getTotalVisitCount());
jsonObject.put("stopConditionFulfillment", machine.getCurrentContext().getPathGenerator().getStopCondition().getFulfilment());
JSONObject data = new JSONObject();
for (Map.Entry k : machine.getCurrentContext().getKeys().entrySet()) {
data.put(k.getKey(), k.getValue());
}
jsonObject.put("data", data);
conn.send(jsonObject.toString());
}
}
}
}
public void startService() {
start();
logger.info("GraphWalkerServer started on port: " + getPort());
// Shutdown event
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
System.out.println();
System.out.println("GraphWalkerServer shutting down");
System.out.println();
logger.info("GraphWalkerServer shutting down");
}
}));
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException i) {
break;
}
}
}
public Set getSockets() {
return sockets;
}
public Map getMachines() {
return machines;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy