Exemple #1
0
  @Override
  protected void setUp() throws Exception {

    init();

    securityContext = SecurityContext.getSuperUserInstance();
    createNodeCommand = Services.command(securityContext, CreateNodeCommand.class);
    createRelationshipCommand = Services.command(securityContext, CreateRelationshipCommand.class);
    deleteNodeCommand = Services.command(securityContext, DeleteNodeCommand.class);
    transactionCommand = Services.command(securityContext, TransactionCommand.class);
    graphDbCommand = Services.command(securityContext, GraphDatabaseCommand.class);
    findNodeCommand = Services.command(securityContext, FindNodeCommand.class);
  }
Exemple #2
0
  public static Object call(final String key, final Map<String, Object> parameters)
      throws FrameworkException {

    final SecurityContext superUserContext = SecurityContext.getSuperUserInstance();
    final App app = StructrApp.getInstance(superUserContext);

    // we might want to introduce caching here at some point in the future..
    // Cache can be invalidated when the schema is rebuilt for example..

    final List<SchemaMethod> methods = app.nodeQuery(SchemaMethod.class).andName(key).getAsList();
    if (methods.isEmpty()) {

      logger.debug("Tried to call method {} but no SchemaMethod entity was found.", key);

    } else {

      for (final SchemaMethod method : methods) {

        // only call methods that are NOT part of a schema node
        final AbstractSchemaNode entity = method.getProperty(SchemaMethod.schemaNode);
        if (entity == null) {

          final String source = method.getProperty(SchemaMethod.source);
          if (source != null) {

            return Actions.execute(superUserContext, null, "${" + source + "}", parameters);

          } else {

            logger.warn("Schema method {} has no source code, will NOT be executed.", key);
          }

        } else {

          logger.warn("Schema method {} is attached to an entity, will NOT be executed.", key);
        }
      }
    }

    return null;
  }
  /**
   * This method checks all configured external authentication services.
   *
   * @param request
   * @param response
   * @return user
   */
  protected static Principal checkExternalAuthentication(
      final HttpServletRequest request, final HttpServletResponse response)
      throws FrameworkException {

    final String path = PathHelper.clean(request.getPathInfo());
    final String[] uriParts = PathHelper.getParts(path);

    logger.log(Level.FINE, "Checking external authentication ...");

    if (uriParts == null || uriParts.length != 3 || !("oauth".equals(uriParts[0]))) {

      logger.log(Level.FINE, "Incorrect URI parts for OAuth process, need /oauth/<name>/<action>");
      return null;
    }

    final String name = uriParts[1];
    final String action = uriParts[2];

    // Try to getValue an OAuth2 server for the given name
    final StructrOAuthClient oauthServer = StructrOAuthClient.getServer(name);

    if (oauthServer == null) {

      logger.log(Level.FINE, "No OAuth2 authentication server configured for {0}", path);
      return null;
    }

    if ("login".equals(action)) {

      try {

        response.sendRedirect(oauthServer.getEndUserAuthorizationRequestUri(request));
        return null;

      } catch (Exception ex) {

        logger.log(Level.SEVERE, "Could not send redirect to authorization server", ex);
      }

    } else if ("auth".equals(action)) {

      final String accessToken = oauthServer.getAccessToken(request);
      final SecurityContext superUserContext = SecurityContext.getSuperUserInstance();

      if (accessToken != null) {

        logger.log(Level.FINE, "Got access token {0}", accessToken);
        // securityContext.setAttribute("OAuthAccessToken", accessToken);

        String value = oauthServer.getCredential(request);
        logger.log(Level.FINE, "Got credential value: {0}", new Object[] {value});

        if (value != null) {

          PropertyKey credentialKey = oauthServer.getCredentialKey();

          Principal user = AuthHelper.getPrincipalForCredential(credentialKey, value);

          if (user == null && userAutoCreate) {

            user =
                RegistrationResource.createUser(
                    superUserContext, credentialKey, value, true, userClass);
          }

          if (user != null) {

            AuthHelper.doLogin(request, user);
            HtmlServlet.setNoCacheHeaders(response);

            try {

              logger.log(Level.FINE, "Response status: {0}", response.getStatus());

              response.sendRedirect(oauthServer.getReturnUri());

            } catch (IOException ex) {

              logger.log(
                  Level.SEVERE,
                  "Could not redirect to {0}: {1}",
                  new Object[] {oauthServer.getReturnUri(), ex});
            }
            return user;
          }
        }
      }
    }

    try {

      response.sendRedirect(oauthServer.getErrorUri());

    } catch (IOException ex) {

      logger.log(
          Level.SEVERE,
          "Could not redirect to {0}: {1}",
          new Object[] {oauthServer.getReturnUri(), ex});
    }

    return null;
  }
  /**
   * Examine request and try to find a user.
   *
   * <p>First, check session id, then try external (OAuth) authentication, finally, check standard
   * login by credentials.
   *
   * @param request
   * @param response
   * @return security context
   * @throws FrameworkException
   */
  @Override
  public SecurityContext initializeAndExamineRequest(
      final HttpServletRequest request, final HttpServletResponse response)
      throws FrameworkException {

    SecurityContext securityContext;

    Principal user = checkSessionAuthentication(request);

    if (user == null) {

      user = checkExternalAuthentication(request, response);
    }

    if (user == null) {

      user = getUser(request, true);
    }

    if (user == null) {

      // If no user could be determined, assume frontend access
      securityContext = SecurityContext.getInstance(user, request, AccessMode.Frontend);

    } else {

      if (user instanceof SuperUser) {

        securityContext = SecurityContext.getSuperUserInstance(request);

      } else {

        securityContext = SecurityContext.getInstance(user, request, AccessMode.Backend);
      }
    }

    securityContext.setAuthenticator(this);

    // Check CORS settings (Cross-origin resource sharing, see
    // http://en.wikipedia.org/wiki/Cross-origin_resource_sharing)
    final String origin = request.getHeader("Origin");
    if (!StringUtils.isBlank(origin)) {

      final Services services = Services.getInstance();

      response.setHeader("Access-Control-Allow-Origin", origin);

      // allow cross site resource sharing (read only)
      final String maxAge = services.getConfigurationValue(Services.ACCESS_CONTROL_MAX_AGE);
      if (StringUtils.isNotBlank(maxAge)) {
        response.setHeader("Access-Control-MaxAge", maxAge);
      }

      final String allowMethods =
          services.getConfigurationValue(Services.ACCESS_CONTROL_ALLOW_METHODS);
      if (StringUtils.isNotBlank(allowMethods)) {
        response.setHeader("Access-Control-Allow-Methods", allowMethods);
      }

      final String allowHeaders =
          services.getConfigurationValue(Services.ACCESS_CONTROL_ALLOW_HEADERS);
      if (StringUtils.isNotBlank(allowHeaders)) {
        response.setHeader("Access-Control-Allow-Headers", allowHeaders);
      }

      final String allowCredentials =
          services.getConfigurationValue(Services.ACCESS_CONTROL_ALLOW_CREDENTIALS);
      if (StringUtils.isNotBlank(allowCredentials)) {
        response.setHeader("Access-Control-Allow-Credentials", allowCredentials);
      }

      final String exposeHeaders =
          services.getConfigurationValue(Services.ACCESS_CONTROL_EXPOSE_HEADERS);
      if (StringUtils.isNotBlank(exposeHeaders)) {
        response.setHeader("Access-Control-Expose-Headers", exposeHeaders);
      }
    }

    examined = true;
    return securityContext;
  }
  @Override
  public void execute(Map<String, Object> attributes) throws FrameworkException {

    final String entityType = (String) attributes.get("type");
    final String relType = (String) attributes.get("relType");
    final GraphDatabaseService graphDb = (GraphDatabaseService) arguments.get("graphDb");
    final SecurityContext superUserContext = SecurityContext.getSuperUserInstance();
    final NodeFactory nodeFactory = new NodeFactory(superUserContext);
    final RelationshipFactory relFactory = new RelationshipFactory(superUserContext);

    if (entityType != null) {

      final Class type = EntityContext.getEntityClassForRawType(entityType);

      if (type != null) {

        // final Result<AbstractNode> result = Services.command(securityContext,
        // SearchNodeCommand.class).execute(true, false, Search.andExactType(type.getSimpleName()));
        final Result<AbstractNode> result =
            nodeFactory.instantiateAll(GlobalGraphOperations.at(graphDb).getAllNodes());
        final List<AbstractNode> nodes = new ArrayList<AbstractNode>();

        for (AbstractNode node : result.getResults()) {

          if (node.getClass().equals(type)) {

            nodes.add(node);
          }
        }

        logger.log(
            Level.INFO,
            "Start (re-)indexing all nodes of type {0}",
            new Object[] {type.getSimpleName()});

        long count =
            bulkGraphOperation(
                securityContext,
                nodes,
                1000,
                "RebuildIndex",
                new BulkGraphOperation<AbstractNode>() {

                  @Override
                  public void handleGraphObject(
                      SecurityContext securityContext, AbstractNode node) {

                    node.updateInIndex();
                  }

                  @Override
                  public void handleThrowable(
                      SecurityContext securityContext, Throwable t, AbstractNode node) {

                    logger.log(
                        Level.WARNING,
                        "Unable to index node {0}: {1}",
                        new Object[] {node, t.getMessage()});
                  }

                  @Override
                  public void handleTransactionFailure(
                      SecurityContext securityContext, Throwable t) {

                    logger.log(Level.WARNING, "Unable to index node: {0}", t.getMessage());
                  }
                });

        logger.log(Level.INFO, "Done with (re-)indexing {0} nodes", count);

        return;
      }

    } else if (relType != null) {

      // final Result<AbstractNode> result = Services.command(securityContext,
      // SearchNodeCommand.class).execute(true, false, Search.andExactType(type.getSimpleName()));
      final List<AbstractRelationship> unfilteredRels =
          relFactory.instantiate(GlobalGraphOperations.at(graphDb).getAllRelationships());
      final List<AbstractRelationship> rels = new ArrayList<AbstractRelationship>();

      for (AbstractRelationship rel : unfilteredRels) {

        if (!rel.getType().equals(relType)) {

          rels.add(rel);
        }
      }

      logger.log(Level.INFO, "Start setting UUID on all rels of type {0}", new Object[] {relType});

      long count =
          bulkGraphOperation(
              securityContext,
              rels,
              1000,
              "SetRelationshipUuid",
              new BulkGraphOperation<AbstractRelationship>() {

                @Override
                public void handleGraphObject(
                    SecurityContext securityContext, AbstractRelationship rel) {

                  rel.updateInIndex();
                }

                @Override
                public void handleThrowable(
                    SecurityContext securityContext, Throwable t, AbstractRelationship rel) {

                  logger.log(
                      Level.WARNING,
                      "Unable to index relationship {0}: {1}",
                      new Object[] {rel, t.getMessage()});
                }

                @Override
                public void handleTransactionFailure(SecurityContext securityContext, Throwable t) {

                  logger.log(Level.WARNING, "Unable to index relationship: {0}", t.getMessage());
                }
              });

      logger.log(Level.INFO, "Done with (re-)indexing {0} relationships", count);

      return;
    }

    logger.log(Level.INFO, "Unable to determine entity type to re-index.");
  }
  @Override
  public Object apply(final ActionContext ctx, final GraphObject entity, final Object[] sources)
      throws FrameworkException {

    if (arrayHasMinLengthAndMaxLengthAndAllElementsNotNull(sources, 1, 2)) {

      final String cacheKey = cacheKey(sources);
      String value = getCachedValue(cacheKey);

      if (value == null) {

        final SecurityContext superUserSecurityContext = SecurityContext.getSuperUserInstance();
        final String locale = ctx.getLocale().toString();
        final String name = sources[0].toString();

        Query query =
            StructrApp.getInstance(superUserSecurityContext)
                .nodeQuery(Localization.class)
                .and(Localization.locale, locale)
                .and(Localization.name, name);
        List<Localization> localizations;

        final Locale ctxLocale = ctx.getLocale();
        final String fullLocale = ctxLocale.toString();
        final String lang = ctxLocale.getLanguage();

        if (sources.length == 2) {

          final String domain = sources[1].toString();

          // with domain
          query.and(Localization.domain, domain);

          localizations = query.getAsList();

          if (localizations.isEmpty() && fullLocale.contains("_")) {

            // no language-specific localization found, try language code only

            query =
                StructrApp.getInstance(superUserSecurityContext)
                    .nodeQuery(Localization.class)
                    .and(Localization.locale, lang)
                    .and(Localization.name, name)
                    .and(Localization.domain, domain);

            localizations = query.getAsList();
          }

        } else {

          // without domain
          query.blank(Localization.domain);

          localizations = query.getAsList();

          if (localizations.isEmpty() && fullLocale.contains("_")) {

            // no language-specific localization found, try language code only

            query =
                StructrApp.getInstance(superUserSecurityContext)
                    .nodeQuery(Localization.class)
                    .and(Localization.locale, lang)
                    .and(Localization.name, name)
                    .blank(Localization.domain);

            localizations = query.getAsList();
          }
        }

        if (localizations.size() > 1) {

          // Ambiguous localization found
          if (sources.length > 1) {

            logger.warn(
                "Found ambiguous localization for key \"{}\" and domain \"{}\". Please fix. Parameters: {}",
                new Object[] {
                  sources[0].toString(), sources[1].toString(), getParametersAsString(sources)
                });

          } else {

            logger.warn(
                "Found ambiguous localization for key \"{}\". Please fix. Parameters: {}",
                new Object[] {sources[0].toString(), getParametersAsString(sources)});
          }
        }

        // return first localization
        if (localizations.isEmpty()) {

          // no localization found - return the key
          value = name;

        } else {

          value = localizations.get(0).getProperty(Localization.localizedName);
        }

        cacheValue(cacheKey, value);
      }

      return value;

    } else if (sources.length == 1 || sources.length == 2) {

      logParameterError(entity, sources, ctx.isJavaScriptContext());

      // silently ignore null values
      return "";

    } else {

      logParameterError(entity, sources, ctx.isJavaScriptContext());

      // only show the error message for wrong parameter count
      return usage(ctx.isJavaScriptContext());
    }
  }
 @Override
 public SecurityContext initializeAndExamineRequest(
     HttpServletRequest request, HttpServletResponse response) throws FrameworkException {
   return SecurityContext.getSuperUserInstance(request);
 }
  public static void analyzeSchema() {

    final App app = StructrApp.getInstance();
    final FileBasedHashLongMap<NodeInfo> nodeIdMap =
        new FileBasedHashLongMap<>(userHome + File.separator + ".structrSchemaAnalyzer");
    final GraphDatabaseService graphDb = app.command(GraphDatabaseCommand.class).execute();
    final ConfigurationProvider configuration = Services.getInstance().getConfigurationProvider();
    final Set<NodeInfo> nodeTypes = new LinkedHashSet<>();
    final Set<RelationshipInfo> relationships = new LinkedHashSet<>();
    final Map<String, SchemaNode> schemaNodes = new LinkedHashMap<>();
    final Map<String, List<TypeInfo>> typeInfoTypeMap = new LinkedHashMap<>();
    final List<TypeInfo> reducedTypeInfos = new LinkedList<>();
    final List<TypeInfo> typeInfos = new LinkedList<>();
    Iterator<Relationship> relIterator = null;
    Iterator<Node> nodeIterator = null;

    logger.log(Level.INFO, "Fetching all nodes iterator..");

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

      nodeIterator =
          Iterables.filter(
                  new StructrAndSpatialPredicate(false, false, true),
                  GlobalGraphOperations.at(graphDb).getAllNodes())
              .iterator();
      tx.success();

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

    logger.log(Level.INFO, "Starting to analyze nodes..");

    NodeServiceCommand.bulkGraphOperation(
        SecurityContext.getSuperUserInstance(),
        nodeIterator,
        100000,
        "Analyzing nodes",
        new BulkGraphOperation<Node>() {

          @Override
          public void handleGraphObject(final SecurityContext securityContext, final Node node)
              throws FrameworkException {

            final NodeInfo nodeInfo = new NodeInfo(node);

            // hashcode of nodeInfo is derived from its property and type signature!
            nodeTypes.add(nodeInfo);

            // add node ID to our new test datastructure
            nodeIdMap.add(nodeInfo, node.getId());
          }
        });

    logger.log(Level.INFO, "Identifying common base classes..");

    try (final Tx tx = app.tx(true, false, false)) {

      // nodeTypes now contains all existing node types and their property sets
      identifyCommonBaseClasses(app, nodeTypes, nodeIdMap, typeInfos);

      tx.success();

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

    logger.log(Level.INFO, "Collecting type information..");

    try (final Tx tx = app.tx(true, false, false)) {

      // group type infos by type
      collectTypeInfos(typeInfos, typeInfoTypeMap);

      tx.success();

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

    logger.log(Level.INFO, "Aggregating type information..");

    try (final Tx tx = app.tx(true, false, false)) {

      // reduce type infos with more than one type
      reduceTypeInfos(typeInfoTypeMap, reducedTypeInfos);

      tx.success();

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

    logger.log(Level.INFO, "Identifying property sets..");
    try (final Tx tx = app.tx(true, false, false)) {

      // intersect property sets of type infos
      intersectPropertySets(reducedTypeInfos);

      tx.success();

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

    logger.log(Level.INFO, "Sorting result..");
    try (final Tx tx = app.tx(false, false, false)) {

      // sort type infos
      Collections.sort(reducedTypeInfos, new HierarchyComparator(false));

      tx.success();

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

    final Map<String, TypeInfo> reducedTypeInfoMap = new LinkedHashMap<>();

    for (final TypeInfo info : reducedTypeInfos) {

      final String type = info.getPrimaryType();

      // map TypeInfo to type for later use
      reducedTypeInfoMap.put(type, info);

      logger.log(Level.INFO, "Starting with setting of type and ID for type {0}", type);

      NodeServiceCommand.bulkGraphOperation(
          SecurityContext.getSuperUserInstance(),
          info.getNodeIds().iterator(),
          10000,
          "Setting type and ID",
          new BulkGraphOperation<Long>() {

            @Override
            public void handleGraphObject(SecurityContext securityContext, Long nodeId)
                throws FrameworkException {

              final Node node = graphDb.getNodeById(nodeId);

              node.setProperty(GraphObject.id.dbName(), NodeServiceCommand.getNextUuid());
              node.setProperty(GraphObject.type.dbName(), type);
            }
          });
    }

    logger.log(Level.INFO, "Fetching all relationships iterator..");

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

      relIterator =
          Iterables.filter(
                  new StructrAndSpatialPredicate(false, false, true),
                  GlobalGraphOperations.at(graphDb).getAllRelationships())
              .iterator();
      tx.success();

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

    logger.log(Level.INFO, "Starting with analyzing relationships..");

    NodeServiceCommand.bulkGraphOperation(
        SecurityContext.getSuperUserInstance(),
        relIterator,
        10000,
        "Analyzing relationships",
        new BulkGraphOperation<Relationship>() {

          @Override
          public void handleGraphObject(SecurityContext securityContext, Relationship rel)
              throws FrameworkException {

            final Node startNode = rel.getStartNode();
            final Node endNode = rel.getEndNode();

            // make sure node has been successfully identified above
            if (startNode.hasProperty("type") && endNode.hasProperty("type")) {

              final String relationshipType = rel.getType().name();
              final String startNodeType = (String) startNode.getProperty("type");
              final String endNodeType = (String) endNode.getProperty("type");

              relationships.add(new RelationshipInfo(startNodeType, endNodeType, relationshipType));

              // create combined type on imported relationship
              if (startNodeType != null && endNodeType != null) {

                final String combinedType =
                    getCombinedType(startNodeType, relationshipType, endNodeType);

                logger.log(
                    Level.FINE,
                    "Combined relationship type {0} found for rel type {1}, start node type {2}, end node type {3}",
                    new Object[] {combinedType, relationshipType, startNodeType, endNodeType});

                rel.setProperty(GraphObject.type.dbName(), combinedType);
              }

              // create ID on imported relationship
              rel.setProperty(GraphObject.id.dbName(), NodeServiceCommand.getNextUuid());
            }
          }
        });

    logger.log(Level.INFO, "Grouping relationships..");

    // group relationships by type
    final Map<String, List<RelationshipInfo>> relTypeInfoMap = new LinkedHashMap<>();
    for (final RelationshipInfo relInfo : relationships) {

      // final String relType         = relInfo.getRelType();
      final String combinedType =
          getCombinedType(
              relInfo.getStartNodeType(), relInfo.getRelType(), relInfo.getEndNodeType());
      List<RelationshipInfo> infos = relTypeInfoMap.get(combinedType);

      if (infos == null) {

        infos = new LinkedList<>();
        relTypeInfoMap.put(combinedType, infos);
      }

      infos.add(relInfo);
    }

    logger.log(Level.INFO, "Aggregating relationship information..");

    final List<RelationshipInfo> reducedRelationshipInfos = new ArrayList<>();
    if ("true"
        .equals(
            Services.getInstance()
                .getConfigurationValue("importer.inheritancedetection", "true"))) {

      // reduce relationship infos into one
      for (final List<RelationshipInfo> infos : relTypeInfoMap.values()) {

        reducedRelationshipInfos.addAll(reduceNodeTypes(infos, reducedTypeInfoMap));
      }

    } else {

      reducedRelationshipInfos.addAll(relationships);
    }

    logger.log(Level.INFO, "Starting with schema node creation..");

    NodeServiceCommand.bulkGraphOperation(
        SecurityContext.getSuperUserInstance(),
        reducedTypeInfos.iterator(),
        100000,
        "Creating schema nodes",
        new BulkGraphOperation<TypeInfo>() {

          @Override
          public void handleGraphObject(SecurityContext securityContext, TypeInfo typeInfo)
              throws FrameworkException {

            final String type = typeInfo.getPrimaryType();
            if (!"ReferenceNode".equals(type)) {

              final Map<String, Class> props = typeInfo.getPropertySet();
              final PropertyMap propertyMap = new PropertyMap();

              // add properties
              for (final Map.Entry<String, Class> propertyEntry : props.entrySet()) {

                final String propertyName = propertyEntry.getKey();
                final Class propertyType = propertyEntry.getValue();

                // handle array types differently
                String propertyTypeName = propertyType.getSimpleName();
                if (propertyType.isArray()) {

                  // remove "[]" from the end and append "Array" to match the appropriate parser
                  propertyTypeName =
                      propertyTypeName.substring(0, propertyTypeName.length() - 2).concat("Array");
                }

                propertyMap.put(new StringProperty("_".concat(propertyName)), propertyTypeName);
              }

              // set node type which is in "name" property
              propertyMap.put(AbstractNode.name, type);

              // check if there is an existing Structr entity with the same type
              // and make the dynamic class extend the existing class if yes.
              final Class existingType = configuration.getNodeEntityClass(type);
              if (existingType != null) {

                propertyMap.put(SchemaNode.extendsClass, existingType.getName());

              } else if (!typeInfo.getOtherTypes().isEmpty()) {

                // only the first supertype is supported
                propertyMap.put(
                    SchemaNode.extendsClass, typeInfo.getSuperclass(reducedTypeInfoMap));
              }

              final SchemaNode existingNode =
                  app.nodeQuery(SchemaNode.class).andName(type).getFirst();
              if (existingNode != null) {

                for (final Entry<PropertyKey, Object> entry : propertyMap.entrySet()) {

                  existingNode.setProperty(entry.getKey(), entry.getValue());
                }

                schemaNodes.put(type, existingNode);

              } else {

                // create schema node
                schemaNodes.put(type, app.create(SchemaNode.class, propertyMap));
              }
            }
          }
        });

    logger.log(Level.INFO, "Starting with schema relationship creation..");

    NodeServiceCommand.bulkGraphOperation(
        SecurityContext.getSuperUserInstance(),
        reducedRelationshipInfos.iterator(),
        100000,
        "Creating schema relationships",
        new BulkGraphOperation<RelationshipInfo>() {

          @Override
          public void handleGraphObject(SecurityContext securityContext, RelationshipInfo template)
              throws FrameworkException {

            final SchemaNode startNode = schemaNodes.get(template.getStartNodeType());
            final SchemaNode endNode = schemaNodes.get(template.getEndNodeType());
            final String relationshipType = template.getRelType();
            final PropertyMap propertyMap = new PropertyMap();

            propertyMap.put(SchemaRelationshipNode.sourceId, startNode.getUuid());
            propertyMap.put(SchemaRelationshipNode.targetId, endNode.getUuid());
            propertyMap.put(SchemaRelationshipNode.relationshipType, relationshipType);

            app.create(SchemaRelationshipNode.class, propertyMap);
          }
        });

    logger.log(Level.INFO, "Starting with index rebuild..");

    // rebuild index
    app.command(BulkRebuildIndexCommand.class).execute(Collections.EMPTY_MAP);
  }
  @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);
    }
  }