Package org.structr.core.entity

Source Code of org.structr.core.entity.AbstractNode

/**
* 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 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 General Public License
* along with Structr.  If not, see <http://www.gnu.org/licenses/>.
*/
package org.structr.core.entity;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
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.apache.commons.codec.digest.DigestUtils;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.index.Index;
import org.structr.common.AccessControllable;
import org.structr.common.GraphObjectComparator;
import org.structr.common.Permission;
import org.structr.common.PropertyView;
import org.structr.common.SecurityContext;
import org.structr.common.ValidationHelper;
import org.structr.common.View;
import org.structr.common.error.ErrorBuffer;
import org.structr.common.error.FrameworkException;
import org.structr.common.error.NullArgumentToken;
import org.structr.common.error.ReadOnlyPropertyToken;
import org.structr.core.GraphObject;
import org.structr.core.IterableAdapter;
import org.structr.core.Ownership;
import org.structr.core.Services;
import org.structr.core.app.StructrApp;
import org.structr.core.converter.PropertyConverter;
import org.structr.core.entity.relationship.PrincipalOwnsNode;
import org.structr.core.graph.NodeInterface;
import org.structr.core.graph.NodeRelationshipStatisticsCommand;
import org.structr.core.graph.NodeService;
import org.structr.core.graph.RelationshipFactory;
import org.structr.core.property.PropertyKey;
import org.structr.core.property.PropertyMap;
import org.structr.schema.SchemaHelper;
import org.structr.schema.action.ActionContext;

//~--- classes ----------------------------------------------------------------

/**
* Abstract base class for all node entities in structr.
*
* @author Axel Morgner
* @author Christian Morgner
*/
public abstract class AbstractNode implements NodeInterface, AccessControllable {

  private static final Map<Class, Object> relationshipTemplateInstanceCache = new LinkedHashMap<>();
  private static final Logger logger                                        = Logger.getLogger(AbstractNode.class.getName());

  public static final View defaultView = new View(AbstractNode.class, PropertyView.Public, id, type);

  public static final View uiView = new View(AbstractNode.class, PropertyView.Ui,
    id, name, owner, type, createdBy, deleted, hidden, createdDate, lastModifiedDate, visibleToPublicUsers, visibleToAuthenticatedUsers, visibilityStartDate, visibilityEndDate
  );

  private boolean readOnlyPropertiesUnlocked = false;

  protected Class entityType                 = null;
  protected Principal cachedOwnerNode        = null;
  protected SecurityContext securityContext  = null;
  protected String cachedUuid                = null;
  protected Node dbNode                      = null;

  //~--- constructors ---------------------------------------------------

  public AbstractNode() {}

  public AbstractNode(SecurityContext securityContext, final Node dbNode, final Class entityType) {
    init(securityContext, dbNode, entityType);
  }

  //~--- methods --------------------------------------------------------

  @Override
  public void onNodeCreation() {
  }

  @Override
  public void onNodeInstantiation() {
  }

  @Override
  public void onNodeDeletion() {
  }

  @Override
  public final void init(final SecurityContext securityContext, final Node dbNode, final Class entityType) {

    this.dbNode          = dbNode;
    this.entityType      = entityType;
    this.securityContext = securityContext;
  }

  @Override
  public void setSecurityContext(SecurityContext securityContext) {
    this.securityContext = securityContext;
  }

  @Override
  public SecurityContext getSecurityContext() {
    return securityContext;
  }

  @Override
  public boolean equals(final Object o) {

    if (o == null) {

      return false;
    }

    if (!(o instanceof AbstractNode)) {

      return false;
    }

    return (Integer.valueOf(this.hashCode()).equals(o.hashCode()));

  }

  @Override
  public int hashCode() {

    if (this.dbNode == null) {

      return (super.hashCode());
    }

    return Long.valueOf(dbNode.getId()).hashCode();

  }

  @Override
  public int compareTo(final NodeInterface node) {

    if(node == null) {
      return -1;
    }


    String name = getName();

    if(name == null) {
      return -1;
    }


    String nodeName = node.getName();

    if(nodeName == null) {
      return -1;
    }

    return name.compareTo(nodeName);
  }

  /**
   * Implement standard toString() method
   */
  @Override
  public String toString() {
    return getUuid();

  }

  /**
   * Can be used to permit the setting of a read-only
   * property once. The lock will be restored automatically
   * after the next setProperty operation. This method exists
   * to prevent automatic set methods from setting a read-only
   * property while allowing a manual set method to override this
   * default behaviour.
   */
  @Override
  public void unlockReadOnlyPropertiesOnce() {

    this.readOnlyPropertiesUnlocked = true;

  }

  @Override
  public void removeProperty(final PropertyKey key) throws FrameworkException {

    if (this.dbNode != null) {

      if (key == null) {

        logger.log(Level.SEVERE, "Tried to set property with null key (action was denied)");

        return;

      }

      // check for read-only properties
      if (key.isReadOnly()) {

        // allow super user to set read-only properties
        if (readOnlyPropertiesUnlocked || securityContext.isSuperUser()) {

          // permit write operation once and
          // lock read-only properties again
          readOnlyPropertiesUnlocked = false;
        } else {

          throw new FrameworkException(this.getType(), new ReadOnlyPropertyToken(key));
        }

      }

      dbNode.removeProperty(key.dbName());

      // remove from index
      removeFromIndex(key);
    }

  }

  //~--- get methods ----------------------------------------------------

  @Override
  public PropertyKey getDefaultSortKey() {
    return AbstractNode.name;
  }

  @Override
  public String getDefaultSortOrder() {
    return GraphObjectComparator.ASCENDING;
  }

  @Override
  public String getType() {
    return getProperty(AbstractNode.type);
  }

  @Override
  public PropertyContainer getPropertyContainer() {
    return dbNode;
  }

  /**
   * Get name from underlying db node
   *
   * If name is null, return node id as fallback
   */
  @Override
  public String getName() {

    String name = getProperty(AbstractNode.name);
    if (name == null) {
      name = getNodeId().toString();
    }

    return name;
  }

  /**
   * Get id from underlying db
   */
  @Override
  public long getId() {

    if (dbNode == null) {

      return -1;
    }

    return dbNode.getId();

  }

  @Override
  public String getUuid() {

    if (cachedUuid == null) {
      cachedUuid = getProperty(GraphObject.id);
    }

    return cachedUuid;

  }

  public Long getNodeId() {

    return getId();

  }

  public String getIdString() {

    return Long.toString(getId());

  }

  /**
   * Indicates whether this node is visible to public users.
   *
   * @return whether this node is visible to public users
   */
  public boolean getVisibleToPublicUsers() {
    return getProperty(visibleToPublicUsers);
  }

  /**
   * Indicates whether this node is visible to authenticated users.
   *
   * @return whether this node is visible to authenticated users
   */
  public boolean getVisibleToAuthenticatedUsers() {
    return getProperty(visibleToPublicUsers);
  }

  /**
   * Indicates whether this node is hidden.
   *
   * @return whether this node is hidden
   */
  public boolean getHidden() {
    return getProperty(hidden);
  }

  /**
   * Indicates whether this node is deleted.
   *
   * @return whether this node is deleted
   */
  public boolean getDeleted() {
    return getProperty(deleted);
  }

  /**
   * Returns the property set for the given view as an Iterable.
   *
   * @param propertyView
   * @return the property set for the given view
   */
  @Override
  public Iterable<PropertyKey> getPropertyKeys(final String propertyView) {

    // check for custom view in content-type field
    if (securityContext != null && securityContext.hasCustomView()) {

      final Set<PropertyKey> keys  = new LinkedHashSet<>(StructrApp.getConfiguration().getPropertySet(entityType, propertyView));
      final Set<String> customView = securityContext.getCustomView();

      for (Iterator<PropertyKey> it = keys.iterator(); it.hasNext();) {
        if (!customView.contains(it.next().jsonName())) {

          it.remove();
        }
      }

      return keys;
    }

    // this is the default if no application/json; properties=[...] content-type header is present on the request
    return StructrApp.getConfiguration().getPropertySet(entityType, propertyView);
  }

  /**
   * Return property value which is used for indexing.
   *
   * This is useful f.e. to filter markup from HTML to index only text,
   * or to get dates as long values.
   *
   * @param key
   * @return property value for indexing
   */
  @Override
  public Object getPropertyForIndexing(final PropertyKey key) {

    Object value = getProperty(key, false, null);
    if (value != null) {
      return value;
    }

    return getProperty(key);
  }

  /**
   * Returns the (converted, validated, transformed, etc.) property for the given
   * property key.
   *
   * @param <T>
   * @param key the property key to retrieve the value for
   * @return the converted, validated, transformed property value
   */
  @Override
  public <T> T getProperty(final PropertyKey<T> key) {
    return getProperty(key, true, null);
  }

  @Override
  public <T> T getProperty(final PropertyKey<T> key, final org.neo4j.helpers.Predicate<GraphObject> predicate) {
    return getProperty(key, true, predicate);
  }

  private <T> T getProperty(final PropertyKey<T> key, boolean applyConverter, final org.neo4j.helpers.Predicate<GraphObject> predicate) {

    // early null check, this should not happen...
    if (key == null || key.dbName() == null) {
      return null;
    }

    return key.getProperty(securityContext, this, applyConverter, predicate);
  }

  public String getPropertyMD5(final PropertyKey key) {

    Object value = getProperty(key);

    if (value instanceof String) {

      return DigestUtils.md5Hex((String) value);
    } else if (value instanceof byte[]) {

      return DigestUtils.md5Hex((byte[]) value);
    }

    logger.log(Level.WARNING, "Could not create MD5 hex out of value {0}", value);

    return null;

  }

  /**
   * Returns the property value for the given key as a Comparable
   *
   * @param key the property key to retrieve the value for
   * @return the property value for the given key as a Comparable
   */
  @Override
  public <T> Comparable getComparableProperty(final PropertyKey<T> key) {

    if (key != null) {

      final T propertyValue = getProperty(key);

      // check property converter
      PropertyConverter<T, ?> converter = key.databaseConverter(securityContext, this);
      if (converter != null) {

        try {
          return converter.convertForSorting(propertyValue);

        } catch (Throwable t) {

          t.printStackTrace();

          logger.log(Level.WARNING, "Unable to convert property {0} of type {1}: {2}", new Object[] {
            key.dbName(),
            getClass().getSimpleName(),
            t.getMessage()
          });
        }
      }

      // conversion failed, may the property value itself is comparable
      if (propertyValue instanceof Comparable) {
        return (Comparable)propertyValue;
      }

      // last try: convertFromInput to String to make comparable
      if (propertyValue != null) {
        return propertyValue.toString();
      }
    }

    return null;
  }

  /**
   * Returns the property value for the given key as a Iterable
   *
   * @param propertyKey the property key to retrieve the value for
   * @return the property value for the given key as a Iterable
   */
  public Iterable getIterableProperty(final PropertyKey<? extends Iterable> propertyKey) {
    return (Iterable)getProperty(propertyKey);
  }

  /**
   * Returns a list of related nodes for which a modification propagation is configured
   * via the relationship. Override this method to return a set of nodes that should
   * receive propagated modifications.
   *
   * @return a set of nodes to which modifications should be propagated
   */
  public Set<AbstractNode> getNodesForModificationPropagation() {
    return null;
  }

  /**
   * Returns database node.
   *
   * @return the database node
   */
  @Override
  public Node getNode() {

    return dbNode;

  }

  private <A extends NodeInterface, B extends NodeInterface, T extends Target, R extends Relation<A, B, ManyStartpoint<A>, T>> Iterable<R> getIncomingRelationshipsAsSuperUser(final Class<R> type) {

    final RelationshipFactory<R> factory = new RelationshipFactory<>(SecurityContext.getSuperUserInstance());
    final R template                     = getRelationshipForType(type);

    return new IterableAdapter<>(template.getSource().getRawSource(SecurityContext.getSuperUserInstance(), dbNode, null), factory);
  }

  /**
   * Return the (cached) incoming relationship between this node and the
   * given principal which holds the security information.
   *
   * @param p
   * @return incoming security relationship
   */
  @Override
  public Security getSecurityRelationship(final Principal p) {

    if (p == null) {

      return null;
    }

    for (Security r : getIncomingRelationshipsAsSuperUser(Security.class)) {

      if (p.equals(r.getSourceNode())) {

        return r;

      }
    }

    return null;

  }

  @Override
  public <R extends AbstractRelationship> Iterable<R> getRelationships() {
    return new IterableAdapter<>(dbNode.getRelationships(), new RelationshipFactory<R>(securityContext));
  }

  @Override
  public <A extends NodeInterface, B extends NodeInterface, S extends Source, T extends Target, R extends Relation<A, B, S, T>> Iterable<R> getRelationships(final Class<R> type) {

    final RelationshipFactory<R> factory = new RelationshipFactory<>(securityContext);
    final R template                     = getRelationshipForType(type);
    final Direction direction            = template.getDirectionForType(entityType);
    final RelationshipType relType       = template;

    return new IterableAdapter<>(dbNode.getRelationships(relType, direction), factory);
  }

  @Override
  public <A extends NodeInterface, B extends NodeInterface, T extends Target, R extends Relation<A, B, OneStartpoint<A>, T>> R getIncomingRelationship(final Class<R> type) {

    final RelationshipFactory<R> factory = new RelationshipFactory<>(securityContext);
    final R template                     = getRelationshipForType(type);
    final Relationship relationship      = template.getSource().getRawSource(securityContext, dbNode, null);

    if (relationship != null) {
      return factory.adapt(relationship);
    }

    return null;
  }

  @Override
  public <A extends NodeInterface, B extends NodeInterface, T extends Target, R extends Relation<A, B, ManyStartpoint<A>, T>> Iterable<R> getIncomingRelationships(final Class<R> type) {

    final RelationshipFactory<R> factory = new RelationshipFactory<>(securityContext);
    final R template                     = getRelationshipForType(type);

    return new IterableAdapter<>(template.getSource().getRawSource(securityContext, dbNode, null), factory);
  }

  @Override
  public <A extends NodeInterface, B extends NodeInterface, S extends Source, R extends Relation<A, B, S, OneEndpoint<B>>> R getOutgoingRelationship(final Class<R> type) {

    final RelationshipFactory<R> factory = new RelationshipFactory<>(securityContext);
    final R template                     = getRelationshipForType(type);
    final Relationship relationship      = template.getTarget().getRawSource(securityContext, dbNode, null);

    if (relationship != null) {
      return factory.adapt(relationship);
    }

    return null;
  }

  @Override
  public <A extends NodeInterface, B extends NodeInterface, S extends Source, R extends Relation<A, B, S, ManyEndpoint<B>>> Iterable<R> getOutgoingRelationships(final Class<R> type) {

    final RelationshipFactory<R> factory = new RelationshipFactory<>(securityContext);
    final R template                     = getRelationshipForType(type);

    return new IterableAdapter<>(template.getTarget().getRawSource(securityContext, dbNode, null), factory);
  }

  @Override
  public <R extends AbstractRelationship> Iterable<R> getIncomingRelationships() {
    return new IterableAdapter<>(dbNode.getRelationships(Direction.INCOMING), new RelationshipFactory<R>(securityContext));
  }

  @Override
  public <R extends AbstractRelationship> Iterable<R> getOutgoingRelationships() {
    return new IterableAdapter<>(dbNode.getRelationships(Direction.OUTGOING), new RelationshipFactory<R>(securityContext));
  }

  /**
   * Return statistical information on all relationships of this node
   *
   * @param dir
   * @return number of relationships
   */
  public Map<RelationshipType, Long> getRelationshipInfo(final Direction dir) throws FrameworkException {
    return StructrApp.getInstance(securityContext).command(NodeRelationshipStatisticsCommand.class).execute(this, dir);
  }

  /**
   * Returns the owner node of this node, following an INCOMING OWNS relationship.
   *
   * @return the owner node of this node
   */
  @Override
  public Principal getOwnerNode() {

    if (cachedOwnerNode == null) {

      final Ownership ownership = getIncomingRelationshipAsSuperUser(PrincipalOwnsNode.class);
      if (ownership != null) {

        Principal principal = ownership.getSourceNode();
        cachedOwnerNode = (Principal) principal;
      }
    }

    return cachedOwnerNode;
  }


  /**
   * Returns the database ID of the owner node of this node.
   *
   * @return the database ID of the owner node of this node
   */
  public Long getOwnerId() {

    return getOwnerNode().getId();

  }

  /**
   * Return a list with the connected principals (user, group, role)
   * @return list with principals
   */
  public List<Principal> getSecurityPrincipals() {

    List<Principal> principalList = new LinkedList<>();

    // check any security relationships
    for (Security r : getIncomingRelationshipsAsSuperUser(Security.class)) {

      // check security properties
      Principal principalNode = r.getSourceNode();

      principalList.add(principalNode);
    }

    return principalList;

  }

  private <A extends NodeInterface, B extends NodeInterface, T extends Target, R extends Relation<A, B, OneStartpoint<A>, T>> R getIncomingRelationshipAsSuperUser(final Class<R> type) {

    final RelationshipFactory<R> factory = new RelationshipFactory<>(SecurityContext.getSuperUserInstance());
    final R template                     = getRelationshipForType(type);
    final Relationship relationship      = template.getSource().getRawSource(SecurityContext.getSuperUserInstance(), dbNode, null);

    if (relationship != null) {
      return factory.adapt(relationship);
    }

    return null;
  }

  /**
   * Return true if this node has a relationship of given type and direction.
   *
   * @param <A>
   * @param <B>
   * @param <S>
   * @param <T>
   * @param type
   * @return relationships
   */
  public <A extends NodeInterface, B extends NodeInterface, S extends Source, T extends Target> boolean hasRelationship(final Class<? extends Relation<A, B, S, T>> type) {
    return this.getRelationships(type).iterator().hasNext();
  }

  public <A extends NodeInterface, B extends NodeInterface, S extends Source, T extends Target, R extends Relation<A, B, S, T>> boolean hasIncomingRelationships(final Class<R> type) {
    return getRelationshipForType(type).getSource().hasElements(securityContext, dbNode, null);
  }

  public <A extends NodeInterface, B extends NodeInterface, S extends Source, T extends Target, R extends Relation<A, B, S, T>> boolean hasOutgoingRelationships(final Class<R> type) {
    return getRelationshipForType(type).getTarget().hasElements(securityContext, dbNode, null);
  }

  // ----- interface AccessControllable -----
  @Override
  public boolean isGranted(final Permission permission, final Principal principal) {

    if (principal == null) {

      return false;
    }

    // just in case ...
    if (permission == null) {

      return false;
    }

    // superuser
    if (principal instanceof SuperUser) {

      return true;
    }

    // user has full control over his/her own user node
    if (this.equals(principal)) {

      return true;
    }

    Security r = getSecurityRelationship(principal);

    if ((r != null) && r.isAllowed(permission)) {

      return true;
    }

    // Now check possible parent principals
    for (Principal parent : principal.getParents()) {

      if (isGranted(permission, parent)) {

        return true;
      }

    }

    return false;

  }

  @Override
  public boolean onCreation(SecurityContext securityContext, ErrorBuffer errorBuffer) throws FrameworkException {
    return isValid(errorBuffer);
  }

  @Override
  public boolean onModification(SecurityContext securityContext, ErrorBuffer errorBuffer) throws FrameworkException {
    return isValid(errorBuffer);
  }

  @Override
  public boolean onDeletion(SecurityContext securityContext, ErrorBuffer errorBuffer, PropertyMap properties) throws FrameworkException {
    return true;
  }

  @Override
  public void afterCreation(SecurityContext securityContext) {
  }

  @Override
  public void afterModification(SecurityContext securityContext) {
  }

  @Override
  public void afterDeletion(SecurityContext securityContext, PropertyMap properties) {
  }

  @Override
  public void ownerModified(SecurityContext securityContext) {
  }

  @Override
  public void securityModified(SecurityContext securityContext) {
  }

  @Override
  public void locationModified(SecurityContext securityContext) {
  }

  @Override
  public void propagatedModification(SecurityContext securityContext) {
  }

  @Override
  public boolean isValid(ErrorBuffer errorBuffer) {

    boolean error = false;

    error |= ValidationHelper.checkStringNotBlank(this, id, errorBuffer);
    error |= ValidationHelper.checkStringNotBlank(this, type, errorBuffer);

    return !error;

  }

  @Override
  public boolean isVisibleToPublicUsers() {

    return getVisibleToPublicUsers();

  }

  @Override
  public boolean isVisibleToAuthenticatedUsers() {

    return getProperty(visibleToAuthenticatedUsers);

  }

  @Override
  public boolean isNotHidden() {

    return !getHidden();

  }

  @Override
  public boolean isHidden() {

    return getHidden();

  }

  @Override
  public Date getVisibilityStartDate() {
    return getProperty(visibilityStartDate);
  }

  @Override
  public Date getVisibilityEndDate() {
    return getProperty(visibilityEndDate);
  }

  @Override
  public Date getCreatedDate() {
    return getProperty(createdDate);
  }

  @Override
  public Date getLastModifiedDate() {
    return getProperty(lastModifiedDate);
  }

  // ----- end interface AccessControllable -----
  public boolean isNotDeleted() {

    return !getDeleted();

  }

  @Override
  public boolean isDeleted() {

    return getDeleted();

  }

  /**
   * Return true if node is the root node
   *
   * @return isRootNode
   */
  public boolean isRootNode() {

    return getId() == 0;

  }

  public boolean isVisible() {

    return securityContext.isVisible(this);

  }

  /**
   * Set a property in database backend. This method needs to be wrappend into
   * a StructrTransaction, otherwise Neo4j will throw a NotInTransactionException!
   * Set property only if value has changed.
   *
   * @param <T>
   * @param key
   * @throws org.structr.common.error.FrameworkException
   */
  @Override
  public <T> void setProperty(final PropertyKey<T> key, final T value) throws FrameworkException {

    T oldValue = getProperty(key);

    // check null cases
    if ((oldValue == null) && (value == null)) {

      return;
    }

    // no old value exists, set property
    if ((oldValue == null) && (value != null)) {

      setPropertyInternal(key, value);

      return;

    }

    // old value exists and is NOT equal
    if ((oldValue != null) && !oldValue.equals(value)) {

      setPropertyInternal(key, value);
    }

  }

  private <T> void setPropertyInternal(final PropertyKey<T> key, final T value) throws FrameworkException {

    if (key == null) {

      logger.log(Level.SEVERE, "Tried to set property with null key (action was denied)");

      throw new FrameworkException(getClass().getSimpleName(), new NullArgumentToken(base));

    }

    // check for read-only properties
    if (key.isReadOnly() || (key.isWriteOnce() && (dbNode != null) && dbNode.hasProperty(key.dbName()))) {

      if (readOnlyPropertiesUnlocked || securityContext.isSuperUser()) {

        // permit write operation once and
        // lock read-only properties again
        readOnlyPropertiesUnlocked = false;

      } else {

        throw new FrameworkException(getClass().getSimpleName(), new ReadOnlyPropertyToken(key));
      }

    }

    key.setProperty(securityContext, this, value);
  }

  @Override
  public void addToIndex() {

    for (PropertyKey key : StructrApp.getConfiguration().getPropertySet(entityType, PropertyView.All)) {

      if (key.isIndexed()) {

        key.index(this, this.getPropertyForIndexing(key));
      }
    }
  }

  @Override
  public void updateInIndex() {

    removeFromIndex();
    addToIndex();
  }

  @Override
  public void removeFromIndex() {

    for (Index<Node> index : Services.getInstance().getService(NodeService.class).getNodeIndices()) {

      synchronized (index) {

        index.remove(dbNode);
      }
    }
  }

  public void removeFromIndex(PropertyKey key) {

    for (Index<Node> index : Services.getInstance().getService(NodeService.class).getNodeIndices()) {

      synchronized (index) {

        index.remove(dbNode, key.dbName());
      }
    }
  }

  @Override
  public void indexPassiveProperties() {

    for (PropertyKey key : StructrApp.getConfiguration().getPropertySet(entityType, PropertyView.All)) {

      if (key.isPassivelyIndexed()) {

        key.index(this, this.getPropertyForIndexing(key));
      }
    }
  }

  public static <A extends NodeInterface, B extends NodeInterface, R extends Relation<A, B, ?, ?>> R getRelationshipForType(final Class<R> type) {

    R instance = (R)relationshipTemplateInstanceCache.get(type);
    if (instance == null) {

      try {

        instance = type.newInstance();
        relationshipTemplateInstanceCache.put(type, instance);

      } catch (Throwable t) {

        // TODO: throw meaningful exception here,
        // should be a RuntimeException that indicates
        // wrong use of Relationships etc.

        t.printStackTrace();
      }
    }

    return instance;
  }

  @Override
  public String getPropertyWithVariableReplacement(SecurityContext securityContext, ActionContext renderContext, PropertyKey<String> key) throws FrameworkException {
    return SchemaHelper.getPropertyWithVariableReplacement(securityContext, this, renderContext, key);
  }

  @Override
  public String replaceVariables(final SecurityContext securityContext, final ActionContext actionContext, final Object rawValue) throws FrameworkException {
    return SchemaHelper.replaceVariables(securityContext, this, actionContext, rawValue);
  }

  protected String[] split(final String source) {

    ArrayList<String> tokens = new ArrayList<>(20);
    boolean inDoubleQuotes = false;
    boolean inSingleQuotes = false;
    boolean ignoreNext = false;
    int len = source.length();
    int level = 0;
    StringBuilder currentToken = new StringBuilder(len);

    for (int i = 0; i < len; i++) {

      char c = source.charAt(i);

      // do not strip away separators in nested functions!
      if ((level != 0) || (c != ',')) {

        currentToken.append(c);
      }

      if (ignoreNext) {

        ignoreNext = false;
        continue;

      }

      switch (c) {

        case '\\':

          ignoreNext = true;

          break;

        case '(':
          level++;

          break;

        case ')':
          level--;

          break;

        case '"':
          if (inDoubleQuotes) {

            inDoubleQuotes = false;

            level--;

          } else {

            inDoubleQuotes = true;

            level++;

          }

          break;

        case '\'':
          if (inSingleQuotes) {

            inSingleQuotes = false;

            level--;

          } else {

            inSingleQuotes = true;

            level++;

          }

          break;

        case ',':
          if (level == 0) {

            tokens.add(currentToken.toString().trim());
            currentToken.setLength(0);

          }

          break;

      }

    }

    if (currentToken.length() > 0) {

      tokens.add(currentToken.toString().trim());
    }

    return tokens.toArray(new String[0]);

  }
}

TOP

Related Classes of org.structr.core.entity.AbstractNode

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.