Search in sources :

Example 1 with WebSocketMessage

use of org.structr.websocket.message.WebSocketMessage in project structr by structr.

the class StructrWebSocket method onWebSocketText.

@Override
public void onWebSocketText(final String data) {
    if (!Services.getInstance().isInitialized()) {
        // send 401 Authentication Required
        send(MessageBuilder.status().code(503).message("System is not initialized yet").build(), true);
    }
    final Services servicesInstance = Services.getInstance();
    // wait for service layer to be initialized
    while (!servicesInstance.isInitialized()) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException iex) {
        }
    }
    if (data == null) {
        logger.warn("Empty text message received.");
        return;
    }
    logger.debug("############################################################ RECEIVED \n{}", data.substring(0, Math.min(data.length(), 1000)));
    // parse web socket data from JSON
    final WebSocketMessage webSocketData = gson.fromJson(data, WebSocketMessage.class);
    final App app = StructrApp.getInstance(securityContext);
    final String command = webSocketData.getCommand();
    final Class type = commandSet.get(command);
    final String sessionIdFromMessage = webSocketData.getSessionId();
    if (type != null) {
        try (final Tx tx = app.tx()) {
            if (sessionIdFromMessage != null) {
                // try to authenticated this connection by sessionId
                authenticate(SessionHelper.getShortSessionId(sessionIdFromMessage), command.equals("PING"));
            }
            // we only permit LOGIN commands if authentication based on sessionId was not successful
            if (!isAuthenticated() && !type.equals(LoginCommand.class)) {
                // send 401 Authentication Required
                send(MessageBuilder.status().code(401).message("").build(), true);
                tx.success();
                return;
            }
            tx.success();
        } catch (DatabaseServiceNotAvailableException dbsnae) {
            logger.warn(dbsnae.getMessage());
        } catch (FrameworkException t) {
            logger.warn("Unable to parse message.", t);
        }
        // process message
        try {
            AbstractCommand abstractCommand = (AbstractCommand) type.newInstance();
            abstractCommand.setWebSocket(this);
            abstractCommand.setSession(session);
            abstractCommand.setCallback(webSocketData.getCallback());
            if (!(abstractCommand instanceof PingCommand)) {
                if (securityContext != null) {
                    final HttpSession session = SessionHelper.getSessionBySessionId(securityContext.getSessionId());
                    if (session != null) {
                        session.setMaxInactiveInterval(Services.getGlobalSessionTimeout());
                        try {
                            // Workaround to update lastAccessedTime() in Jetty's session via reflection
                            final Method accessMethod = ((org.eclipse.jetty.server.session.Session) session).getClass().getDeclaredMethod("access", long.class);
                            accessMethod.setAccessible(true);
                            accessMethod.invoke((org.eclipse.jetty.server.session.Session) session, System.currentTimeMillis());
                        } catch (Exception ex) {
                            logger.error("Access to method Session.access() via reflection failed: ", ex);
                        }
                    }
                }
            }
            // transactions in case of bulk processing commands etc.
            if (abstractCommand.requiresEnclosingTransaction()) {
                try (final Tx tx = app.tx()) {
                    // store authenticated-Flag in webSocketData
                    // so the command can access it
                    webSocketData.setSessionValid(isAuthenticated());
                    abstractCommand.processMessage(webSocketData);
                    // commit transaction
                    tx.success();
                }
            } else {
                try (final Tx tx = app.tx()) {
                    // store authenticated-Flag in webSocketData
                    // so the command can access it
                    webSocketData.setSessionValid(isAuthenticated());
                    // commit transaction
                    tx.success();
                }
                // process message without transaction context!
                abstractCommand.processMessage(webSocketData);
            }
        } catch (FrameworkException | InstantiationException | IllegalAccessException t) {
            try (final Tx tx = app.tx()) {
                // send 400 Bad Request
                if (t instanceof FrameworkException) {
                    final FrameworkException fex = (FrameworkException) t;
                    send(MessageBuilder.status().code(fex.getStatus()).message(fex.toString()).jsonErrorObject(fex.toJSON()).callback(webSocketData.getCallback()).build(), true);
                } else {
                    send(MessageBuilder.status().code(400).message(t.toString()).build(), true);
                }
                // commit transaction
                tx.success();
            } catch (FrameworkException fex) {
                logger.warn("Unable to send websocket result: {}", fex.getMessage());
            }
            return;
        }
    } else {
        logger.warn("Unknown command {}", command);
        // send 400 Bad Request
        send(MessageBuilder.status().code(400).message("Unknown command").build(), true);
        return;
    }
}
Also used : StructrApp(org.structr.core.app.StructrApp) App(org.structr.core.app.App) Tx(org.structr.core.graph.Tx) FrameworkException(org.structr.common.error.FrameworkException) HttpSession(javax.servlet.http.HttpSession) AbstractCommand(org.structr.websocket.command.AbstractCommand) DatabaseServiceNotAvailableException(org.structr.common.error.DatabaseServiceNotAvailableException) Method(java.lang.reflect.Method) FrameworkException(org.structr.common.error.FrameworkException) DatabaseServiceNotAvailableException(org.structr.common.error.DatabaseServiceNotAvailableException) IOException(java.io.IOException) PingCommand(org.structr.websocket.command.PingCommand) Services(org.structr.core.Services) WebSocketMessage(org.structr.websocket.message.WebSocketMessage)

Example 2 with WebSocketMessage

use of org.structr.websocket.message.WebSocketMessage in project structr by structr.

the class WebsocketController method getMessageForEvent.

// ----- private methods -----
private WebSocketMessage getMessageForEvent(final SecurityContext securityContext, final ModificationEvent modificationEvent) throws FrameworkException {
    final String callbackId = modificationEvent.getCallbackId();
    if (modificationEvent.isNode()) {
        final NodeInterface node = (NodeInterface) modificationEvent.getGraphObject();
        if (modificationEvent.isDeleted()) {
            final WebSocketMessage message = createMessage("DELETE", callbackId);
            message.setId(modificationEvent.getRemovedProperties().get(GraphObject.id));
            message.setCode(200);
            return message;
        }
        if (modificationEvent.isCreated()) {
            final WebSocketMessage message = createMessage("CREATE", callbackId);
            message.setGraphObject(node);
            message.setResult(Arrays.asList(new GraphObject[] { node }));
            message.setCode(201);
            return message;
        }
        if (modificationEvent.isModified()) {
            final WebSocketMessage message = createMessage("UPDATE", callbackId);
            // at login the securityContext is still null
            if (securityContext != null) {
                // only include changed properties (+ id and type)
                LinkedHashSet<String> propertySet = new LinkedHashSet();
                propertySet.add("id");
                propertySet.add("type");
                for (Iterator<PropertyKey> it = modificationEvent.getModifiedProperties().keySet().iterator(); it.hasNext(); ) {
                    final String jsonName = ((PropertyKey) it.next()).jsonName();
                    if (!propertySet.contains(jsonName)) {
                        propertySet.add(jsonName);
                    }
                }
                for (Iterator<PropertyKey> it = modificationEvent.getRemovedProperties().keySet().iterator(); it.hasNext(); ) {
                    final String jsonName = ((PropertyKey) it.next()).jsonName();
                    if (!propertySet.contains(jsonName)) {
                        propertySet.add(jsonName);
                    }
                }
                if (propertySet.size() > 2) {
                    securityContext.setCustomView(propertySet);
                }
            }
            message.setGraphObject(node);
            message.setResult(Arrays.asList(new GraphObject[] { node }));
            message.setId(node.getUuid());
            message.getModifiedProperties().addAll(modificationEvent.getModifiedProperties().keySet());
            message.getRemovedProperties().addAll(modificationEvent.getRemovedProperties().keySet());
            message.setNodeData(modificationEvent.getData(securityContext));
            message.setCode(200);
            if (securityContext != null) {
                // Clear custom view here. This is necessary because the security context is reused for all websocket frames.
                securityContext.clearCustomView();
            }
            return message;
        }
    } else {
        // handle relationship
        final RelationshipInterface relationship = (RelationshipInterface) modificationEvent.getGraphObject();
        final RelationshipType relType = modificationEvent.getRelationshipType();
        // special treatment of CONTAINS relationships
        if ("CONTAINS".equals(relType.name())) {
            if (modificationEvent.isDeleted()) {
                final WebSocketMessage message = createMessage("REMOVE_CHILD", callbackId);
                message.setNodeData("parentId", relationship.getSourceNodeId());
                message.setId(relationship.getTargetNodeId());
                message.setCode(200);
                return message;
            }
            if (modificationEvent.isCreated()) {
                final WebSocketMessage message = new WebSocketMessage();
                final NodeInterface startNode = relationship.getSourceNode();
                final NodeInterface endNode = relationship.getTargetNode();
                // don't send a notification
                if (startNode == null || endNode == null) {
                    return null;
                }
                message.setResult(Arrays.asList(new GraphObject[] { endNode }));
                message.setId(endNode.getUuid());
                message.setNodeData("parentId", startNode.getUuid());
                message.setCode(200);
                message.setCommand("APPEND_CHILD");
                if (endNode instanceof DOMNode) {
                    org.w3c.dom.Node refNode = ((DOMNode) endNode).getNextSibling();
                    if (refNode != null) {
                        message.setCommand("INSERT_BEFORE");
                        message.setNodeData("refId", ((AbstractNode) refNode).getUuid());
                    }
                } else if (endNode instanceof User) {
                    message.setCommand("APPEND_USER");
                    message.setNodeData("refId", startNode.getUuid());
                } else if (endNode instanceof AbstractFile) {
                    message.setCommand("APPEND_FILE");
                    message.setNodeData("refId", startNode.getUuid());
                }
                return message;
            }
        }
        if (modificationEvent.isDeleted()) {
            final WebSocketMessage message = createMessage("DELETE", callbackId);
            message.setId(modificationEvent.getRemovedProperties().get(GraphObject.id));
            message.setCode(200);
            return message;
        }
        if (modificationEvent.isModified()) {
            final WebSocketMessage message = createMessage("UPDATE", callbackId);
            message.getModifiedProperties().addAll(modificationEvent.getModifiedProperties().keySet());
            message.getRemovedProperties().addAll(modificationEvent.getRemovedProperties().keySet());
            message.setNodeData(modificationEvent.getData(securityContext));
            message.setGraphObject(relationship);
            message.setId(relationship.getUuid());
            message.setCode(200);
            final PropertyMap relProperties = relationship.getProperties();
            // final NodeInterface startNode = relationship.getSourceNode();
            // final NodeInterface endNode = relationship.getTargetNode();
            // relProperties.put(new StringProperty("startNodeId"), startNode.getUuid());
            // relProperties.put(new StringProperty("endNodeId"), endNode.getUuid());
            final Map<String, Object> properties = PropertyMap.javaTypeToInputType(securityContext, relationship.getClass(), relProperties);
            message.setRelData(properties);
            return message;
        }
    }
    return null;
}
Also used : LinkedHashSet(java.util.LinkedHashSet) User(org.structr.web.entity.User) AbstractFile(org.structr.web.entity.AbstractFile) RelationshipType(org.structr.api.graph.RelationshipType) GraphObject(org.structr.core.GraphObject) PropertyMap(org.structr.core.property.PropertyMap) RelationshipInterface(org.structr.core.graph.RelationshipInterface) GraphObject(org.structr.core.GraphObject) WebSocketMessage(org.structr.websocket.message.WebSocketMessage) DOMNode(org.structr.web.entity.dom.DOMNode) NodeInterface(org.structr.core.graph.NodeInterface) PropertyKey(org.structr.core.property.PropertyKey)

Example 3 with WebSocketMessage

use of org.structr.websocket.message.WebSocketMessage in project structr by structr.

the class WebsocketController method broadcast.

private void broadcast(final WebSocketMessage webSocketData, final String exemptedSessionId) {
    // session must be valid to be received by the client
    webSocketData.setSessionValid(true);
    final String pagePath = (String) webSocketData.getNodeData().get("pagePath");
    final String encodedPath = URIUtil.encodePath(pagePath);
    final List<StructrWebSocket> clientsToRemove = new LinkedList<>();
    final List<? extends GraphObject> result = webSocketData.getResult();
    final String command = webSocketData.getCommand();
    final GraphObject obj = webSocketData.getGraphObject();
    String message;
    // create message
    for (StructrWebSocket socket : clients) {
        String clientPagePath = socket.getPagePath();
        if (clientPagePath != null && !clientPagePath.equals(encodedPath)) {
            continue;
        }
        Session session = socket.getSession();
        if (session != null && socket.isAuthenticated()) {
            final SecurityContext securityContext = socket.getSecurityContext();
            if (exemptedSessionId != null && exemptedSessionId.equals(securityContext.getSessionId())) {
                // session id is supposed to be exempted from this broadcast message
                continue;
            }
            // THEN skip sending a message
            if (obj instanceof AbstractNode) {
                final AbstractNode node = (AbstractNode) obj;
                if (node.isHidden() || !securityContext.isVisible(node)) {
                    continue;
                }
            } else {
                if (!socket.isPrivilegedUser(socket.getCurrentUser())) {
                    continue;
                }
            }
            if (result != null && !result.isEmpty() && BroadcastCommands.contains(command)) {
                final WebSocketMessage clientData = webSocketData.copy();
                clientData.setResult(filter(securityContext, result));
                message = gson.toJson(clientData, WebSocketMessage.class);
            } else {
                message = gson.toJson(webSocketData, WebSocketMessage.class);
            }
            try {
                session.getRemote().sendString(message);
            } catch (Throwable t) {
                if (t instanceof WebSocketException) {
                    WebSocketException wse = (WebSocketException) t;
                    if ("RemoteEndpoint unavailable, current state [CLOSED], expecting [OPEN or CONNECTED]".equals(wse.getMessage())) {
                        clientsToRemove.add(socket);
                    }
                }
                logger.debug("Error sending message to client.", t);
            }
        }
    }
    for (StructrWebSocket s : clientsToRemove) {
        unregisterClient(s);
        logger.warn("Client removed from broadcast list: {}", s);
    }
}
Also used : WebSocketException(org.eclipse.jetty.websocket.api.WebSocketException) AbstractNode(org.structr.core.entity.AbstractNode) SecurityContext(org.structr.common.SecurityContext) WebSocketMessage(org.structr.websocket.message.WebSocketMessage) GraphObject(org.structr.core.GraphObject) LinkedList(java.util.LinkedList) Session(org.eclipse.jetty.websocket.api.Session)

Example 4 with WebSocketMessage

use of org.structr.websocket.message.WebSocketMessage in project structr by structr.

the class WebsocketController method createMessage.

private WebSocketMessage createMessage(final String command, final String callbackId) {
    final WebSocketMessage newMessage = new WebSocketMessage();
    newMessage.setCommand(command);
    if (callbackId != null) {
        newMessage.setCallback(callbackId);
    }
    return newMessage;
}
Also used : WebSocketMessage(org.structr.websocket.message.WebSocketMessage)

Example 5 with WebSocketMessage

use of org.structr.websocket.message.WebSocketMessage in project structr by structr.

the class WebSocketDataGSONAdapter method deserialize.

@Override
public WebSocketMessage deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
    WebSocketMessage webSocketData = new WebSocketMessage();
    if (json instanceof JsonObject) {
        JsonObject root = json.getAsJsonObject();
        JsonObject nodeData = root.getAsJsonObject("data");
        JsonObject relData = root.getAsJsonObject("relData");
        if (root.has("command")) {
            webSocketData.setCommand(root.getAsJsonPrimitive("command").getAsString());
        }
        if (root.has("id")) {
            webSocketData.setId(root.getAsJsonPrimitive("id").getAsString());
        }
        if (root.has("pageId")) {
            webSocketData.setPageId(root.getAsJsonPrimitive("pageId").getAsString());
        }
        if (root.has("sessionId")) {
            JsonPrimitive sessionId = root.getAsJsonPrimitive("sessionId");
            if (sessionId != null) {
                webSocketData.setSessionId(sessionId.getAsString());
            }
        }
        if (root.has("callback")) {
            webSocketData.setCallback(root.getAsJsonPrimitive("callback").getAsString());
        }
        if (root.has("button")) {
            webSocketData.setButton(root.getAsJsonPrimitive("button").getAsString());
        }
        if (root.has("parent")) {
            webSocketData.setParent(root.getAsJsonPrimitive("parent").getAsString());
        }
        if (root.has("view")) {
            webSocketData.setView(root.getAsJsonPrimitive("view").getAsString());
        }
        if (root.has("sort")) {
            webSocketData.setSortKey(root.getAsJsonPrimitive("sort").getAsString());
        }
        if (root.has("order")) {
            webSocketData.setSortOrder(root.getAsJsonPrimitive("order").getAsString());
        }
        if (root.has("pageSize")) {
            webSocketData.setPageSize(root.getAsJsonPrimitive("pageSize").getAsInt());
        }
        if (root.has("page")) {
            webSocketData.setPage(root.getAsJsonPrimitive("page").getAsInt());
        }
        if (nodeData != null) {
            JsonInputGSONAdapter adapter = new JsonInputGSONAdapter();
            for (Entry<String, JsonElement> entry : nodeData.entrySet()) {
                final JsonElement obj = entry.getValue();
                Object value = null;
                if (obj instanceof JsonPrimitive) {
                    value = adapter.fromPrimitive(obj.getAsJsonPrimitive());
                } else if (obj instanceof JsonObject) {
                    value = adapter.deserialize(obj, typeOfT, context);
                } else if (obj instanceof JsonArray) {
                    final JsonArray array = obj.getAsJsonArray();
                    final List list = new LinkedList();
                    for (JsonElement element : array) {
                        if (element.isJsonPrimitive()) {
                            list.add(fromPrimitive((element.getAsJsonPrimitive())));
                        } else if (element.isJsonObject()) {
                            // create map of values
                            list.add(JsonInputGSONAdapter.deserialize(element, context));
                        }
                    }
                    value = list;
                } else if (obj instanceof JsonNull) {
                    value = null;
                } else if (obj != null) {
                    value = obj.getAsString();
                }
                webSocketData.setNodeData(entry.getKey(), value);
            }
        }
        if (relData != null) {
            for (Entry<String, JsonElement> entry : relData.entrySet()) {
                JsonElement obj = entry.getValue();
                if (obj instanceof JsonNull || obj.isJsonNull()) {
                    webSocketData.setRelData(entry.getKey(), null);
                } else {
                    try {
                        webSocketData.setRelData(entry.getKey(), obj.getAsString());
                    } catch (Throwable t) {
                        webSocketData.setRelData(entry.getKey(), null);
                    }
                }
            }
        }
    }
    return webSocketData;
}
Also used : JsonPrimitive(com.google.gson.JsonPrimitive) JsonObject(com.google.gson.JsonObject) LinkedList(java.util.LinkedList) JsonNull(com.google.gson.JsonNull) JsonArray(com.google.gson.JsonArray) JsonElement(com.google.gson.JsonElement) JsonInputGSONAdapter(org.structr.core.rest.JsonInputGSONAdapter) JsonObject(com.google.gson.JsonObject) GraphObject(org.structr.core.GraphObject) LinkedList(java.util.LinkedList) List(java.util.List) WebSocketMessage(org.structr.websocket.message.WebSocketMessage)

Aggregations

WebSocketMessage (org.structr.websocket.message.WebSocketMessage)5 GraphObject (org.structr.core.GraphObject)3 LinkedList (java.util.LinkedList)2 JsonArray (com.google.gson.JsonArray)1 JsonElement (com.google.gson.JsonElement)1 JsonNull (com.google.gson.JsonNull)1 JsonObject (com.google.gson.JsonObject)1 JsonPrimitive (com.google.gson.JsonPrimitive)1 IOException (java.io.IOException)1 Method (java.lang.reflect.Method)1 LinkedHashSet (java.util.LinkedHashSet)1 List (java.util.List)1 HttpSession (javax.servlet.http.HttpSession)1 Session (org.eclipse.jetty.websocket.api.Session)1 WebSocketException (org.eclipse.jetty.websocket.api.WebSocketException)1 RelationshipType (org.structr.api.graph.RelationshipType)1 SecurityContext (org.structr.common.SecurityContext)1 DatabaseServiceNotAvailableException (org.structr.common.error.DatabaseServiceNotAvailableException)1 FrameworkException (org.structr.common.error.FrameworkException)1 Services (org.structr.core.Services)1