private GraphObject extractActiveElement(
      final DOMNode node,
      final Set<String> dataKeys,
      final String parentId,
      final ActiveElementState state,
      final int depth) {

    final GraphObjectMap activeElement = new GraphObjectMap();

    activeElement.put(GraphObject.id, node.getUuid());
    activeElement.put(GraphObject.type, node.getType());
    activeElement.put(DOMElement.dataKey, StringUtils.join(dataKeys, ","));
    activeElement.put(Content.content, node.getProperty(Content.content));

    switch (state) {
      case Button:
        activeElement.put(actionProperty, node.getProperty(DOMElement._action));
        break;

      case Link:
        activeElement.put(actionProperty, node.getProperty(Link._href));
        break;

      case Query:
        extractQueries(activeElement, node);
        break;
    }

    activeElement.put(stateProperty, state.name());
    activeElement.put(recursionDepthProperty, depth);
    activeElement.put(parentIdProperty, parentId);

    return activeElement;
  }
Пример #2
0
  @Override
  public void render(RenderContext renderContext, int depth) throws FrameworkException {

    renderContext.setPage(this);

    // Skip DOCTYPE node
    DOMNode subNode = (DOMNode) this.getFirstChild().getNextSibling();

    if (subNode == null) {

      subNode = (DOMNode) super.getFirstChild();

    } else {

      renderContext.getBuffer().append("<!DOCTYPE html>\n");
    }

    while (subNode != null) {

      if (subNode.isNotDeleted() && securityContext.isVisible(subNode)) {

        subNode.render(renderContext, depth);
      }

      subNode = (DOMNode) subNode.getNextSibling();
    }
  }
  // ----- private methods -----
  private void collectActiveElements(
      final List<GraphObject> resultList,
      final DOMNode root,
      final Set<String> parentDataKeys,
      final String parent,
      final int depth) {

    final String childDataKey = root.getProperty(DOMElement.dataKey);
    final Set<String> dataKeys = new LinkedHashSet<>(parentDataKeys);
    String parentId = parent;
    int dataCentricDepth = depth;

    if (!StringUtils.isEmpty(childDataKey)) {

      dataKeys.add(childDataKey);
      dataCentricDepth++;
    }

    final ActiveElementState state = isActive(root, dataKeys);
    if (!state.equals(ActiveElementState.None)) {

      resultList.add(extractActiveElement(root, dataKeys, parentId, state, depth));
      if (state.equals(ActiveElementState.Query)) {

        parentId = root.getUuid();
      }
    }

    for (final DOMChildren children : root.getChildRelationships()) {

      final DOMNode child = children.getTargetNode();
      collectActiveElements(resultList, child, dataKeys, parentId, dataCentricDepth);
    }
  }
  private boolean extractQuery(
      final GraphObjectMap activeElement, final DOMNode node, final Property<String> queryKey) {

    if (!StringUtils.isEmpty(node.getProperty(queryKey))) {

      activeElement.put(queryProperty, node.getProperty(queryKey));
      return true;
    }

    return false;
  }
Пример #5
0
  @Override
  protected void handleNewChild(Node newChild) {

    for (final DOMNode child : getAllChildNodes()) {

      try {

        child.setProperty(ownerDocument, this);

      } catch (FrameworkException ex) {
        logger.log(Level.WARNING, "", ex);
      }
    }
  }
Пример #6
0
  private Node importNode(
      final Node node, final boolean deep, final boolean removeParentFromSourceNode)
      throws DOMException {

    if (node instanceof DOMNode) {

      final DOMNode domNode = (DOMNode) node;

      // step 1: use type-specific import impl.
      Node importedNode = domNode.doImport(Page.this);

      // step 2: do recursive import?
      if (deep && domNode.hasChildNodes()) {

        // FIXME: is it really a good idea to do the
        // recursion inside of a transaction?
        Node child = domNode.getFirstChild();

        while (child != null) {

          // do not remove parent for child nodes
          importNode(child, deep, false);
          child = child.getNextSibling();

          logger.log(Level.INFO, "sibling is {0}", child);
        }
      }

      // step 3: remove node from its current parent
      // (Note that this step needs to be done last in
      // (order for the child to be able to find its
      // siblings.)
      if (removeParentFromSourceNode) {

        // only do this for the actual source node, do not remove
        // child nodes from its parents
        Node _parent = domNode.getParentNode();
        if (_parent != null) {
          _parent.removeChild(domNode);
        }
      }

      return importedNode;
    }

    return null;
  }
Пример #7
0
  private void test(
      final DOMNode p,
      final DOMNode text,
      final String content,
      final String expected,
      final RenderContext context)
      throws FrameworkException {

    text.setTextContent(content);

    // clear queue
    context.getBuffer().getQueue().clear();
    p.render(context, 0);

    assertEquals(
        "Invalid JavaScript evaluation result", expected, concat(context.getBuffer().getQueue()));
  }
Пример #8
0
  @Override
  public String toString() {

    if (existingNode instanceof Content) {

      return "Update Content("
          + existingNode.getIdHashOrProperty()
          + ") with "
          + newNode.getIdHashOrProperty();

    } else {

      return "Update "
          + newNode.getProperty(DOMElement.tag)
          + "("
          + existingNode.getIdHashOrProperty()
          + ") with "
          + newNode.getIdHashOrProperty();
    }
  }
Пример #9
0
  private Node adoptNode(final Node node, final boolean removeParentFromSourceNode)
      throws DOMException {

    if (node instanceof DOMNode) {

      final DOMNode domNode = (DOMNode) node;

      // step 2: do recursive import?
      if (domNode.hasChildNodes()) {

        Node child = domNode.getFirstChild();
        while (child != null) {

          // do not remove parent for child nodes
          adoptNode(child, false);
          child = child.getNextSibling();
        }
      }

      // step 3: remove node from its current parent
      // (Note that this step needs to be done last in
      // (order for the child to be able to find its
      // siblings.)
      if (removeParentFromSourceNode) {

        // only do this for the actual source node, do not remove
        // child nodes from its parents
        Node _parent = domNode.getParentNode();
        if (_parent != null) {
          _parent.removeChild(domNode);
        }
      }

      // step 1: use type-specific adopt impl.
      Node adoptedNode = domNode.doAdopt(Page.this);

      return adoptedNode;
    }

    return null;
  }
Пример #10
0
  @Override
  protected void checkHierarchy(Node otherNode) throws DOMException {

    // verify that this document has only one document element
    if (getDocumentElement() != null) {
      throw new DOMException(
          DOMException.HIERARCHY_REQUEST_ERR, HIERARCHY_REQUEST_ERR_MESSAGE_DOCUMENT);
    }

    if (!(otherNode instanceof Html
        || otherNode instanceof Comment
        || otherNode instanceof Template)) {

      throw new DOMException(
          DOMException.HIERARCHY_REQUEST_ERR, HIERARCHY_REQUEST_ERR_MESSAGE_ELEMENT);
    }

    super.checkHierarchy(otherNode);
  }
Пример #11
0
  @Override
  protected void doGet(final HttpServletRequest request, final HttpServletResponse response) {

    final Authenticator auth = config.getAuthenticator();
    final SecurityContext securityContext;
    final App app;

    try {
      String path = request.getPathInfo();

      // check for registration (has its own tx because of write access
      if (checkRegistration(auth, request, response, path)) {

        return;
      }

      // isolate request authentication in a transaction
      try (final Tx tx = StructrApp.getInstance().tx()) {
        securityContext = auth.initializeAndExamineRequest(request, response);
        tx.success();
      }

      app = StructrApp.getInstance(securityContext);

      try (final Tx tx = app.tx()) {

        // Ensure access mode is frontend
        securityContext.setAccessMode(AccessMode.Frontend);

        request.setCharacterEncoding("UTF-8");

        // Important: Set character encoding before calling response.getWriter() !!, see Servlet
        // Spec 5.4
        response.setCharacterEncoding("UTF-8");

        boolean dontCache = false;

        logger.log(Level.FINE, "Path info {0}", path);

        // don't continue on redirects
        if (response.getStatus() == 302) {
          return;
        }

        Principal user = securityContext.getUser(false);
        if (user != null) {

          // Don't cache if a user is logged in
          dontCache = true;
        }

        final RenderContext renderContext =
            RenderContext.getInstance(request, response, getEffectiveLocale(request));

        renderContext.setResourceProvider(config.getResourceProvider());

        EditMode edit = renderContext.getEditMode(user);

        DOMNode rootElement = null;
        AbstractNode dataNode = null;

        String[] uriParts = PathHelper.getParts(path);
        if ((uriParts == null) || (uriParts.length == 0)) {

          // find a visible page
          rootElement = findIndexPage(securityContext);

          logger.log(Level.FINE, "No path supplied, trying to find index page");

        } else {

          if (rootElement == null) {

            rootElement = findPage(securityContext, request, path);

          } else {
            dontCache = true;
          }
        }

        if (rootElement == null) { // No page found

          // Look for a file
          File file = findFile(securityContext, request, path);
          if (file != null) {

            streamFile(securityContext, file, request, response, edit);
            return;
          }

          // store remaining path parts in request
          Matcher matcher = threadLocalUUIDMatcher.get();
          boolean requestUriContainsUuids = false;

          for (int i = 0; i < uriParts.length; i++) {

            request.setAttribute(uriParts[i], i);
            matcher.reset(uriParts[i]);

            // set to "true" if part matches UUID pattern
            requestUriContainsUuids |= matcher.matches();
          }

          if (!requestUriContainsUuids) {

            // Try to find a data node by name
            dataNode = findFirstNodeByName(securityContext, request, path);

          } else {

            dataNode = findNodeByUuid(securityContext, PathHelper.getName(path));
          }

          if (dataNode != null) {

            // Last path part matches a data node
            // Remove last path part and try again searching for a page
            // clear possible entry points
            request.removeAttribute(POSSIBLE_ENTRY_POINTS);

            rootElement =
                findPage(
                    securityContext,
                    request,
                    StringUtils.substringBeforeLast(path, PathHelper.PATH_SEP));

            renderContext.setDetailsDataObject(dataNode);

            // Start rendering on data node
            if (rootElement == null && dataNode instanceof DOMNode) {

              rootElement = ((DOMNode) dataNode);
            }
          }
        }

        // Still nothing found, do error handling
        if (rootElement == null) {

          // Check if security context has set an 401 status
          if (response.getStatus() == HttpServletResponse.SC_UNAUTHORIZED) {

            try {

              UiAuthenticator.writeUnauthorized(response);

            } catch (IllegalStateException ise) {
            }

          } else {

            rootElement = notFound(response, securityContext);
          }
        }

        if (rootElement == null) {
          return;
        }

        if (EditMode.WIDGET.equals(edit) || dontCache) {

          setNoCacheHeaders(response);
        }

        if (!securityContext.isVisible(rootElement)) {

          rootElement = notFound(response, securityContext);
          if (rootElement == null) {
            return;
          }
        }

        if (securityContext.isVisible(rootElement)) {

          if (!EditMode.WIDGET.equals(edit)
              && !dontCache
              && notModifiedSince(request, response, rootElement, dontCache)) {

            ServletOutputStream out = response.getOutputStream();
            out.flush();
            // response.flushBuffer();
            out.close();

          } else {

            // prepare response
            response.setCharacterEncoding("UTF-8");

            String contentType = rootElement.getProperty(Page.contentType);

            if (contentType != null && contentType.equals("text/html")) {

              contentType = contentType.concat(";charset=UTF-8");
              response.setContentType(contentType);

            } else {

              // Default
              response.setContentType("text/html;charset=UTF-8");
            }

            response.setHeader("Strict-Transport-Security", "max-age=60");
            response.setHeader("X-Content-Type-Options", "nosniff");
            response.setHeader("X-Frame-Options", "SAMEORIGIN");
            response.setHeader("X-XSS-Protection", "1; mode=block");

            // async or not?
            boolean isAsync =
                HttpService.parseBoolean(
                    Services.getBaseConfiguration().getProperty(HttpService.ASYNC), true);
            if (isAsync) {

              final AsyncContext async = request.startAsync();
              final ServletOutputStream out = async.getResponse().getOutputStream();
              final AtomicBoolean finished = new AtomicBoolean(false);
              final DOMNode rootNode = rootElement;

              threadPool.submit(
                  new Runnable() {

                    @Override
                    public void run() {

                      try (final Tx tx = app.tx()) {

                        // final long start = System.currentTimeMillis();

                        // render
                        rootNode.render(securityContext, renderContext, 0);
                        finished.set(true);

                        // final long end = System.currentTimeMillis();
                        // System.out.println("Done in " + (end-start) + " ms");

                        tx.success();

                      } catch (Throwable t) {
                        t.printStackTrace();
                        final String errorMsg = t.getMessage();
                        try {
                          // response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                          response.sendError(
                              HttpServletResponse.SC_INTERNAL_SERVER_ERROR, errorMsg);
                          finished.set(true);
                        } catch (IOException ex) {
                          ex.printStackTrace();
                        }
                      }
                    }
                  });

              // start output write listener
              out.setWriteListener(
                  new WriteListener() {

                    @Override
                    public void onWritePossible() throws IOException {

                      try {

                        final Queue<String> queue = renderContext.getBuffer().getQueue();
                        while (out.isReady()) {

                          String buffer = null;

                          synchronized (queue) {
                            buffer = queue.poll();
                          }

                          if (buffer != null) {

                            out.print(buffer);

                          } else {

                            if (finished.get()) {

                              async.complete();
                              response.setStatus(HttpServletResponse.SC_OK);

                              // prevent this block from being called again
                              break;
                            }

                            Thread.sleep(1);
                          }
                        }

                      } catch (Throwable t) {
                        t.printStackTrace();
                      }
                    }

                    @Override
                    public void onError(Throwable t) {
                      t.printStackTrace();
                    }
                  });

            } else {

              final StringRenderBuffer buffer = new StringRenderBuffer();
              renderContext.setBuffer(buffer);

              // render
              rootElement.render(securityContext, renderContext, 0);

              response.getOutputStream().write(buffer.getBuffer().toString().getBytes("utf-8"));
              response.getOutputStream().flush();
              response.getOutputStream().close();
            }
          }

        } else {

          notFound(response, securityContext);
        }

        tx.success();

      } catch (FrameworkException fex) {
        fex.printStackTrace();
        logger.log(Level.SEVERE, "Exception while processing request", fex);
      }

    } catch (IOException | FrameworkException t) {

      t.printStackTrace();
      logger.log(Level.SEVERE, "Exception while processing request", t);
      UiAuthenticator.writeInternalServerError(response);
    }
  }
Пример #12
0
  @Override
  public void processMessage(final WebSocketMessage webSocketData) {

    final SecurityContext securityContext = getWebSocket().getSecurityContext();
    final String id = webSocketData.getId();

    if (id != null) {

      final NodeInterface node = getNode(id);

      if (node != null) {

        if (node instanceof DOMNode) {

          // Use new DOM interface
          DOMNode domNode = (DOMNode) node;

          try {

            domNode.getParentNode().removeChild(domNode);

            // Remove node from page
            final PropertyMap changedProperties = new PropertyMap();
            changedProperties.put(DOMNode.syncedNodes, Collections.EMPTY_LIST);
            changedProperties.put(DOMNode.pageId, null);
            domNode.setProperties(securityContext, changedProperties);

          } catch (DOMException | FrameworkException ex) {

            logger.error("Could not remove node from page " + domNode, ex);
            getWebSocket()
                .send(MessageBuilder.status().code(422).message(ex.getMessage()).build(), true);
          }

        } else {

          final App app = StructrApp.getInstance(securityContext);

          try {

            // Old style: Delete all incoming CONTAINS rels
            for (AbstractRelationship rel : node.getIncomingRelationships()) {

              if ("CONTAINS".equals(rel.getType())) {

                app.delete(rel);
              }
            }

          } catch (Throwable t) {

            logger.error("Could not delete relationship", t);
            getWebSocket()
                .send(
                    MessageBuilder.status()
                        .code(400)
                        .message("Error in RemoveCommand: " + t.getMessage())
                        .build(),
                    true);
          }
        }

      } else {

        getWebSocket().send(MessageBuilder.status().code(404).build(), true);
      }

    } else {

      getWebSocket()
          .send(
              MessageBuilder.status()
                  .code(400)
                  .message("RemoveCommand called with empty id")
                  .build(),
              true);
    }
  }
Пример #13
0
  @Override
  public void processMessage(WebSocketMessage webSocketData) {

    final Map<String, Object> nodeData = webSocketData.getNodeData();
    final String parentId = (String) nodeData.get("parentId");
    final String pageId = webSocketData.getPageId();

    nodeData.remove("parentId");

    if (pageId != null) {

      // check for parent ID before creating any nodes
      if (parentId == null) {

        getWebSocket()
            .send(
                MessageBuilder.status()
                    .code(422)
                    .message("Cannot add node without parentId")
                    .build(),
                true);
        return;
      }

      // check if content node with given ID exists
      final DOMNode contentNode = getDOMNode(parentId);
      if (contentNode == null) {

        getWebSocket()
            .send(MessageBuilder.status().code(404).message("Parent node not found").build(), true);
        return;
      }

      final Document document = getPage(pageId);
      if (document != null) {

        final String tagName = (String) nodeData.get("tagName");
        nodeData.remove("tagName");

        final DOMNode parentNode = (DOMNode) contentNode.getParentNode();

        try {

          DOMNode elementNode = null;
          if (tagName != null && !tagName.isEmpty()) {

            elementNode = (DOMNode) document.createElement(tagName);
          }

          // append new node to parent parent node
          if (elementNode != null) {

            parentNode.appendChild(elementNode);
          }

          // append new node to parent parent node
          if (elementNode != null) {

            // append content node to new node
            elementNode.appendChild(contentNode);
          }

        } catch (DOMException dex) {

          // send DOM exception
          getWebSocket()
              .send(MessageBuilder.status().code(422).message(dex.getMessage()).build(), true);
        }

      } else {

        getWebSocket()
            .send(MessageBuilder.status().code(404).message("Page not found").build(), true);
      }

    } else {

      getWebSocket()
          .send(
              MessageBuilder.status()
                  .code(422)
                  .message("Cannot create node without pageId")
                  .build(),
              true);
    }
  }
Пример #14
0
  @Override
  public void processMessage(final WebSocketMessage webSocketData) {

    String id = webSocketData.getId();
    Map<String, Object> nodeData = webSocketData.getNodeData();
    String parentId = (String) nodeData.get("parentId");

    // check node to append
    if (id == null) {

      getWebSocket()
          .send(
              MessageBuilder.status()
                  .code(422)
                  .message("Cannot append node, no id is given")
                  .build(),
              true);

      return;
    }

    // check for parent ID
    if (parentId == null) {

      getWebSocket()
          .send(
              MessageBuilder.status().code(422).message("Cannot add node without parentId").build(),
              true);

      return;
    }

    // check if parent node with given ID exists
    AbstractNode parentNode = getNode(parentId);

    if (parentNode == null) {

      getWebSocket()
          .send(MessageBuilder.status().code(404).message("Parent node not found").build(), true);

      return;
    }

    if (parentNode instanceof DOMNode) {

      DOMNode parentDOMNode = getDOMNode(parentId);

      if (parentDOMNode == null) {

        getWebSocket()
            .send(
                MessageBuilder.status().code(422).message("Parent node is no DOM node").build(),
                true);

        return;
      }

      DOMNode node = (DOMNode) getDOMNode(id);

      // append node to parent
      if (node != null) {

        parentDOMNode.appendChild(node);
      }

    } else {

      // send exception
      getWebSocket()
          .send(
              MessageBuilder.status()
                  .code(422)
                  .message("Cannot use given node, not instance of DOMNode")
                  .build(),
              true);
    }
  }
  private ActiveElementState isActive(final DOMNode node, final Set<String> dataKeys) {

    if (!StringUtils.isEmpty(node.getProperty(DOMElement.dataKey))) {
      return ActiveElementState.Query;
    }

    if (!StringUtils.isEmpty(node.getProperty(DOMElement.restQuery))) {
      return ActiveElementState.Query;
    }

    if (!StringUtils.isEmpty(node.getProperty(DOMElement.cypherQuery))) {
      return ActiveElementState.Query;
    }

    if (!StringUtils.isEmpty(node.getProperty(DOMElement.xpathQuery))) {
      return ActiveElementState.Query;
    }

    if (!StringUtils.isEmpty(node.getProperty(DOMElement.functionQuery))) {
      return ActiveElementState.Query;
    }

    /*
     attributes to check for !isEmpty:
      - data-structr-action
      - data-structr-attributes
      - data-structr-raw-value
    */
    if (node.getProperty(DOMElement._action) != null
        || node.getProperty(DOMElement._attributes) != null
        || node.getProperty(DOMElement._rawValue) != null) {
      return ActiveElementState.Button;
    }

    if (node.getProperty(Content.content) != null && !dataKeys.isEmpty()) {

      if (containsDataKeyReference(node.getProperty(Content.content), dataKeys)) {
        return ActiveElementState.Content;
      }
    }

    if (node.getProperty(DOMElement._id) != null && !dataKeys.isEmpty()) {

      if (containsDataKeyReference(node.getProperty(DOMElement._id), dataKeys)) {
        return ActiveElementState.Content;
      }
    }

    if (node.getProperty(Link._href) != null && !dataKeys.isEmpty()) {

      if (containsDataKeyReference(node.getProperty(Link._href), dataKeys)) {
        return ActiveElementState.Link;
      }
    }

    if (node.getProperty(Input._value) != null && !dataKeys.isEmpty()) {

      if (containsDataKeyReference(node.getProperty(Input._value), dataKeys)) {

        return ActiveElementState.Input;
      }
    }

    // last option: just some hidden element..
    if (!StringUtils.isEmpty(node.getProperty(DOMNode.hideConditions))) {
      return ActiveElementState.Content;
    }

    if (!StringUtils.isEmpty(node.getProperty(DOMNode.showConditions))) {
      return ActiveElementState.Content;
    }

    if (node.getProperty(DOMNode.hideOnIndex)) {
      return ActiveElementState.Content;
    }

    if (node.getProperty(DOMNode.hideOnDetail)) {
      return ActiveElementState.Content;
    }

    return ActiveElementState.None;
  }
Пример #16
0
 // ----- interface InvertibleModificationOperation -----
 @Override
 public void apply(final App app, final Page sourcePage, final Page newPage)
     throws FrameworkException {
   existingNode.updateFromNode(newNode);
 }
Пример #17
0
  @Test
  public void testScripting() {

    NodeInterface detailsDataObject = null;
    Page page = null;
    DOMNode html = null;
    DOMNode head = null;
    DOMNode body = null;
    DOMNode title = null;
    DOMNode div = null;
    DOMNode p = null;
    DOMNode text = null;

    try (final Tx tx = app.tx()) {

      detailsDataObject = app.create(TestOne.class, "TestOne");
      page = Page.createNewPage(securityContext, "testpage");

      page.setProperties(
          page.getSecurityContext(), new PropertyMap(Page.visibleToPublicUsers, true));

      assertTrue(page != null);
      assertTrue(page instanceof Page);

      html = (DOMNode) page.createElement("html");
      head = (DOMNode) page.createElement("head");
      body = (DOMNode) page.createElement("body");
      title = (DOMNode) page.createElement("title");
      div = (DOMNode) page.createElement("div");
      p = (DOMNode) page.createElement("p");
      text = (DOMNode) page.createTextNode("x");

      // add HTML element to page
      page.appendChild(html);

      // add HEAD and BODY elements to HTML
      html.appendChild(head);
      html.appendChild(body);

      // add TITLE element to HEAD
      head.appendChild(title);

      body.appendChild(div);
      div.appendChild(p);

      final PropertyMap changedProperties = new PropertyMap();
      changedProperties.put(DOMElement.restQuery, "/divs");
      changedProperties.put(DOMElement.dataKey, "div");
      p.setProperties(p.getSecurityContext(), changedProperties);

      p.appendChild(text);

      tx.success();

    } catch (FrameworkException fex) {

      fail("Unexpected exception");
    }

    try (final Tx tx = app.tx()) {

      final RenderContext ctx =
          new RenderContext(
              securityContext,
              new RequestMockUp(),
              new ResponseMockUp(),
              RenderContext.EditMode.NONE);
      ctx.setDetailsDataObject(detailsDataObject);
      ctx.setPage(page);

      test(p, text, "${{ return Structr.get('div').id}}", "<p>" + div.getUuid() + "</p>", ctx);
      test(p, text, "${{ return Structr.get('page').id}}", "<p>" + page.getUuid() + "</p>", ctx);
      test(p, text, "${{ return Structr.get('parent').id}}", "<p>" + p.getUuid() + "</p>", ctx);

      tx.success();

    } catch (FrameworkException fex) {

      logger.warn("", fex);
      fail("Unexpected exception.");
    }
  }
  @Override
  public void processMessage(WebSocketMessage webSocketData) {

    final Map<String, Object> nodeData = webSocketData.getNodeData();
    final String parentId = (String) nodeData.get("parentId");
    final String childContent = (String) nodeData.get("childContent");
    final String pageId = webSocketData.getPageId();

    nodeData.remove("parentId");

    if (pageId != null) {

      // check for parent ID before creating any nodes
      if (parentId == null) {

        getWebSocket()
            .send(
                MessageBuilder.status()
                    .code(422)
                    .message("Cannot add node without parentId")
                    .build(),
                true);
        return;
      }

      // check if parent node with given ID exists
      final DOMNode parentNode = getDOMNode(parentId);
      if (parentNode == null) {

        getWebSocket()
            .send(MessageBuilder.status().code(404).message("Parent node not found").build(), true);
        return;
      }

      final Document document = getPage(pageId);
      if (document != null) {

        final String tagName = (String) nodeData.get("tagName");
        final App app = StructrApp.getInstance();

        nodeData.remove("tagName");

        try {
          app.beginTx();

          DOMNode newNode;

          if (tagName != null && !tagName.isEmpty()) {

            newNode = (DOMNode) document.createElement(tagName);

          } else {

            newNode = (DOMNode) document.createTextNode("#text");
          }

          // append new node to parent
          if (newNode != null) {

            parentNode.appendChild(newNode);

            for (Entry entry : nodeData.entrySet()) {

              String key = (String) entry.getKey();
              Object val = entry.getValue();

              PropertyKey propertyKey =
                  StructrApp.getConfiguration()
                      .getPropertyKeyForDatabaseName(newNode.getClass(), key);
              if (propertyKey != null) {

                try {
                  Object convertedValue = val;

                  PropertyConverter inputConverter =
                      propertyKey.inputConverter(SecurityContext.getSuperUserInstance());
                  if (inputConverter != null) {

                    convertedValue = inputConverter.convert(val);
                  }

                  // newNode.unlockReadOnlyPropertiesOnce();
                  newNode.setProperty(propertyKey, convertedValue);

                } catch (FrameworkException fex) {

                  logger.log(
                      Level.WARNING,
                      "Unable to set node property {0} of node {1} to {2}: {3}",
                      new Object[] {propertyKey, newNode.getUuid(), val, fex.getMessage()});
                }
              }
            }

            // create a child text node if content is given
            if (StringUtils.isNotBlank(childContent)) {

              DOMNode childNode = (DOMNode) document.createTextNode(childContent);

              if (newNode != null) {

                newNode.appendChild(childNode);
              }
            }
          }
          app.commitTx();

        } catch (DOMException dex) {

          // send DOM exception
          getWebSocket()
              .send(MessageBuilder.status().code(422).message(dex.getMessage()).build(), true);

        } catch (FrameworkException ex) {

          Logger.getLogger(CreateAndAppendDOMNodeCommand.class.getName())
              .log(Level.SEVERE, null, ex);

        } finally {

          app.finishTx();
        }

      } else {

        getWebSocket()
            .send(MessageBuilder.status().code(404).message("Page not found").build(), true);
      }

    } else {

      getWebSocket()
          .send(
              MessageBuilder.status()
                  .code(422)
                  .message("Cannot create node without pageId")
                  .build(),
              true);
    }
  }