Package org.structr.websocket

Source Code of org.structr.websocket.SynchronizationController

/**
* Copyright (C) 2010-2014 Morgner UG (haftungsbeschränkt)
*
* This file is part of Structr <http://structr.org>.
*
* Structr is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Structr is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Structr.  If not, see <http://www.gnu.org/licenses/>.
*/
package org.structr.websocket;

import com.google.gson.Gson;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.jetty.util.ConcurrentHashSet;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.neo4j.graphdb.RelationshipType;
import org.structr.common.AccessMode;
import org.structr.common.SecurityContext;
import org.structr.common.error.FrameworkException;
import org.structr.core.GraphObject;
import org.structr.core.StructrTransactionListener;
import org.structr.core.app.StructrApp;
import org.structr.core.entity.AbstractNode;
import org.structr.core.graph.ModificationEvent;
import org.structr.core.graph.NodeInterface;
import org.structr.core.graph.RelationshipInterface;
import org.structr.core.graph.Tx;
import org.structr.core.property.PropertyMap;
import org.structr.core.property.StringProperty;
import org.structr.web.entity.User;
import org.structr.web.entity.dom.DOMNode;
import org.structr.websocket.message.WebSocketMessage;

/**
*
* @author Christian Morgner
*/
public class SynchronizationController implements StructrTransactionListener {

  private static final Logger logger = Logger.getLogger(SynchronizationController.class.getName());

  private final Set<StructrWebSocket> clients = new ConcurrentHashSet<>();
  private Gson gson = null;

  public SynchronizationController(final Gson gson) {

    this.gson = gson;

  }

  public void registerClient(final StructrWebSocket client) {

    clients.add(client);

  }

  public void unregisterClient(final StructrWebSocket client) {

    clients.remove(client);

  }

  // ----- private methods -----
  private void broadcast(final WebSocketMessage webSocketData) {

    //logger.log(Level.FINE, "Broadcasting message to {0} clients..", clients.size());
    // session must be valid to be received by the client
    webSocketData.setSessionValid(true);

    String message;
    String pagePath = (String) webSocketData.getNodeData().get("pagePath");

    List<StructrWebSocket> clientsToRemove = new LinkedList<>();

    // create message
    for (StructrWebSocket socket : clients) {

      String clientPagePath = socket.getPagePath();
      if (clientPagePath != null && !clientPagePath.equals(URIUtil.encodePath(pagePath))) {
        continue;
      }

      Session session = socket.getSession();

      webSocketData.setCallback(socket.getCallback());

      if ((session != null)) { //&& socket.isAuthenticated()) {

        List<? extends GraphObject> result = webSocketData.getResult();

        if ((result != null) && (result.size() > 0)
          && (webSocketData.getCommand().equals("UPDATE") || webSocketData.getCommand().equals("ADD") || webSocketData.getCommand().equals("CREATE"))) {

          WebSocketMessage clientData = webSocketData.copy();
          SecurityContext securityContext = socket.getSecurityContext();

          // For non-authenticated clients, construct a security context without user
          if (securityContext == null) {

            try {

              securityContext = SecurityContext.getInstance(null, AccessMode.Frontend);

            } catch (FrameworkException ex) {

              continue;
            }
          }

          clientData.setResult(filter(securityContext, result));

          message = gson.toJson(clientData, WebSocketMessage.class);

        } else {

          message = gson.toJson(webSocketData, WebSocketMessage.class);
        }

        //logger.log(Level.INFO, "############################################################ SENDING \n{0}", message);
        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.log(Level.FINE, "Error sending message to client.", t);
        }

      }

    }

    for (StructrWebSocket s : clientsToRemove) {

      unregisterClient(s);

      logger.log(Level.WARNING, "Client removed from broadcast list: {0}", s);
    }

  }

  private <T extends GraphObject> List<T> filter(final SecurityContext securityContext, final List<T> all) {

    List<T> filteredResult = new LinkedList<>();
    for (T obj : all) {

      if (securityContext.isVisible((AbstractNode) obj)) {

        filteredResult.add(obj);
      }
    }

    return filteredResult;

  }

  // ----- interface StructrTransactionListener -----
  @Override
  public void transactionCommited(final SecurityContext securityContext, final List<ModificationEvent> modificationEvents) {

    try (final Tx tx = StructrApp.getInstance(securityContext).tx()) {

      for (final ModificationEvent event : modificationEvents) {

        try {
          final WebSocketMessage message = getMessageForEvent(securityContext, event);
          if (message != null) {
            logger.log(Level.FINE, "################### Broadcast message: {0}", message.getCommand());
            broadcast(message);
          }

        } catch (FrameworkException ignore) {
        }
      }

    } catch (FrameworkException ex) {
      ex.printStackTrace();
    }
  }

  // ----- private methods -----
  private WebSocketMessage getMessageForEvent(final SecurityContext securityContext, final ModificationEvent modificationEvent) throws FrameworkException {

    if (modificationEvent.isNode()) {

      final NodeInterface node = (NodeInterface) modificationEvent.getGraphObject();

      if (modificationEvent.isDeleted()) {

        final WebSocketMessage message = createMessage("DELETE");

        message.setId(modificationEvent.getRemovedProperties().get(GraphObject.id));

        return message;
      }

      if (modificationEvent.isCreated()) {

        final WebSocketMessage message = createMessage("CREATE");

        message.setGraphObject(node);
        message.setResult(Arrays.asList(new GraphObject[]{node}));

        return message;
      }

      if (modificationEvent.isModified()) {

        final WebSocketMessage message = createMessage("UPDATE");

        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));

        return message;
      }

    } else {

      // handle relationship
      final RelationshipInterface relationship = (RelationshipInterface) modificationEvent.getGraphObject();
      final RelationshipType relType = modificationEvent.getRelationshipType();

      // only interested in CONTAINS relationships
      if (!("CONTAINS".equals(relType.name()))) {
        return null;
      }

      if (modificationEvent.isDeleted()) { // && "CONTAINS".equals(relType.name())) {

        final WebSocketMessage message = createMessage("REMOVE_CHILD");

        message.setNodeData("parentId", relationship.getSourceNodeId());
        message.setId(relationship.getTargetNodeId());

        return message;
      }

      if (modificationEvent.isCreated()) {

        final WebSocketMessage message = new WebSocketMessage();
        final NodeInterface startNode = relationship.getSourceNode();
        final NodeInterface endNode = relationship.getTargetNode();

        message.setResult(Arrays.asList(new GraphObject[]{endNode}));
        message.setId(endNode.getUuid());
        message.setNodeData("parentId", startNode.getUuid());

        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());
        }

        return message;
      }

      if (modificationEvent.isModified()) {

        final WebSocketMessage message = createMessage("UPDATE");

        message.setGraphObject(relationship);
        message.setId(relationship.getUuid());
        message.getModifiedProperties().addAll(modificationEvent.getModifiedProperties().keySet());
        message.getRemovedProperties().addAll(modificationEvent.getRemovedProperties().keySet());
        message.setNodeData(modificationEvent.getData(securityContext));

        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;
  }

  private WebSocketMessage createMessage(final String command) {

    final WebSocketMessage newMessage = new WebSocketMessage();

    newMessage.setCommand(command);

    return newMessage;
  }
}
TOP

Related Classes of org.structr.websocket.SynchronizationController

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.