Package org.structr.core.entity

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

/**
* 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;

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

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
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.GraphObjectComparator;
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.IdNotFoundToken;
import org.structr.common.error.ReadOnlyPropertyToken;
import org.structr.core.GraphObject;
import static org.structr.core.GraphObject.id;
import static org.structr.core.GraphObject.type;
import org.structr.core.Services;
import org.structr.core.app.App;
import org.structr.core.app.StructrApp;
import org.structr.core.converter.PropertyConverter;
import org.structr.core.graph.NodeFactory;
import org.structr.core.graph.NodeInterface;
import org.structr.core.graph.NodeService;
import org.structr.core.graph.RelationshipInterface;
import org.structr.core.property.IntProperty;
import org.structr.core.property.Property;
import org.structr.core.property.PropertyKey;
import org.structr.core.property.PropertyMap;
import org.structr.core.property.RelationshipTypeProperty;
import org.structr.core.property.SourceId;
import org.structr.core.property.TargetId;
import org.structr.schema.SchemaHelper;
import org.structr.schema.action.ActionContext;


/**
* Abstract base class for all relationship entities in structr.
*
* @author Axel Morgner
* @param <S>
* @param <T>
*/
public abstract class AbstractRelationship<S extends NodeInterface, T extends NodeInterface> implements Comparable<AbstractRelationship>, RelationshipInterface {

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

  public static final Property<Integer> cascadeDelete = new IntProperty("cascadeDelete").writeOnce();
  public static final Property<String>  relType       = new RelationshipTypeProperty("relType");
  public static final SourceId          sourceId      = new SourceId("sourceId");
  public static final TargetId          targetId      = new TargetId("targetId");

  public static final View defaultView = new View(AbstractRelationship.class, PropertyView.Public,
    id, type, relType, sourceId, targetId
  );

  public static final View uiView = new View(AbstractRelationship.class, PropertyView.Ui,
    id, type, relType, sourceId, targetId
  );

  private boolean readOnlyPropertiesUnlocked = false;
  private String cachedEndNodeId             = null;
  private String cachedStartNodeId           = null;

  protected SecurityContext securityContext  = null;
  protected Relationship dbRelationship      = null;
  protected Class entityType                 = null;

  public AbstractRelationship() {}

  public AbstractRelationship(final SecurityContext securityContext, final Relationship dbRel, final Class entityType) {

    init(securityContext, dbRel, entityType);
  }

  @Override
  public final void init(final SecurityContext securityContext, final Relationship dbRel, final Class entityType) {

    this.dbRelationship  = dbRel;
    this.entityType      = entityType;
    this.securityContext = securityContext;
  }

  public Property<String> getSourceIdProperty() {
    return sourceId;
  }

  public Property<String> getTargetIdProperty() {
    return targetId;
  }

  @Override
  public void onRelationshipCreation() {
  }

  /**
   * Called when a relationship of this combinedType is instatiated. Please note that
   * a relationship can (and will) be instantiated several times during a
   * normal rendering turn.
   */
  @Override
  public void onRelationshipInstantiation() {

    try {

      if (dbRelationship != null) {

        Node startNode = dbRelationship.getStartNode();
        Node endNode   = dbRelationship.getEndNode();

        if ((startNode != null) && (endNode != null) && startNode.hasProperty(GraphObject.id.dbName()) && endNode.hasProperty(GraphObject.id.dbName())) {

          cachedStartNodeId = (String) startNode.getProperty(GraphObject.id.dbName());
          cachedEndNodeId   = (String) endNode.getProperty(GraphObject.id.dbName());

        }

      }

    } catch (Throwable t) {
    }
  }

  @Override
  public void onRelationshipDeletion() {
  }

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

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

  @Override
  public void unlockReadOnlyPropertiesOnce() {

    this.readOnlyPropertiesUnlocked = true;

  }

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

    dbRelationship.removeProperty(key.dbName());

    // remove from index
    removeFromIndex(key);
  }

  @Override
  public boolean equals(final Object o) {

    return (o != null && new Integer(this.hashCode()).equals(new Integer(o.hashCode())));

  }

  @Override
  public int hashCode() {

    if (this.dbRelationship == null) {

      return (super.hashCode());
    }

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

  }

  @Override
  public int compareTo(final AbstractRelationship rel) {

    // TODO: implement finer compare methods, e.g. taking title and position into account
    if (rel == null) {

      return -1;
    }

    return ((Long) this.getId()).compareTo((Long) rel.getId());
  }

  @Override
  public int cascadeDelete() {

    Integer value = getProperty(AbstractRelationship.cascadeDelete);

    return value != null ? value : 0;
  }

  /**
   * Indicates whether this relationship type propagates modifications
   * in the given direction. Overwrite this method and return true for
   * the desired direction to enable a callback on non-local node
   * modification.
   *
   * @param direction the direction for which the propagation should is to be returned
   * @return the propagation status for the given direction
   */
  public boolean propagatesModifications(Direction direction) {
    return false;
  }

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

  @Override
  public PropertyKey getDefaultSortKey() {

    return null;

  }

  @Override
  public String getDefaultSortOrder() {

    return GraphObjectComparator.ASCENDING;

  }

  @Override
  public long getId() {

    return getInternalId();

  }

  @Override
  public String getUuid() {
    return getProperty(AbstractRelationship.id);
  }

  public long getRelationshipId() {

    return getInternalId();

  }

  public long getInternalId() {

    return dbRelationship.getId();

  }

  @Override
  public PropertyMap getProperties() throws FrameworkException {

    Map<String, Object> properties = new LinkedHashMap<>();

    for (String key : dbRelationship.getPropertyKeys()) {

      properties.put(key, dbRelationship.getProperty(key));
    }

    // convert the database properties back to their java types
    return PropertyMap.databaseTypeToJavaType(securityContext, this, properties);

  }

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

  @Override
  public <T> Comparable getComparableProperty(final PropertyKey<T> key) {

    if (key != null) {

      final T propertyValue = getProperty(key, false, null)// get "raw" property without converter

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

        try {
          return converter.convertForSorting(propertyValue);

        } catch(FrameworkException fex) {
          logger.log(Level.WARNING, "Unable to convert property {0} of type {1}: {2}", new Object[] {
            key.dbName(),
            getClass().getSimpleName(),
            fex.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;
  }

  /**
   * Return database relationship
   *
   * @return database relationship
   */
  @Override
  public Relationship getRelationship() {

    return dbRelationship;

  }

  @Override
  public T getTargetNode() {

    try {

      NodeFactory<T> nodeFactory = new NodeFactory<>(securityContext);
      return nodeFactory.instantiate(dbRelationship.getEndNode());

    } catch (FrameworkException t) {
      // ignore FrameworkException but let NotInTransactionException pass
    }

    return null;
  }

  @Override
  public S getSourceNode() {

    try {

      NodeFactory<S> nodeFactory = new NodeFactory<>(securityContext);
      return nodeFactory.instantiate(dbRelationship.getStartNode());

    } catch (FrameworkException t) {
      // ignore FrameworkException but let NotInTransactionException pass
    }

    return null;
  }

  @Override
  public NodeInterface getOtherNode(final NodeInterface node) {

    try {

      NodeFactory nodeFactory = new NodeFactory(securityContext);
      return (NodeInterface)nodeFactory.instantiate(dbRelationship.getOtherNode(node.getNode()));

    } catch (FrameworkException t) {
      // ignore FrameworkException but let NotInTransactionException pass
    }

    return null;
  }

  @Override
  public RelationshipType getRelType() {

    if (dbRelationship != null) {

      return dbRelationship.getType();
    }

    return null;
  }

  /**
   * Return all property keys.
   *
   * @return property keys
   */
  public Iterable<PropertyKey> getPropertyKeys() {

    return getPropertyKeys(PropertyView.All);

  }

  /**
   * Return property value which is used for indexing.
   *
   * This is useful f.e. to filter markup from HTML to index only text
   *
   * @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);
  }

  // ----- interface GraphObject -----
  @Override
  public Iterable<PropertyKey> getPropertyKeys(final String propertyView) {

    return StructrApp.getConfiguration().getPropertySet(this.getClass(), propertyView);

  }

  public Map<RelationshipType, Long> getRelationshipInfo(Direction direction) {

    return null;

  }

  public List<AbstractRelationship> getRelationships(RelationshipType type, Direction dir) {

    return null;

  }

  @Override
  public String getType() {

    final RelationshipType relType = getRelType();
    if (relType != null) {
      return relType.name();
    }

    return null;
  }

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

  @Override
  public String getSourceNodeId() {
    return cachedStartNodeId;
  }

  @Override
  public String getTargetNodeId() {
    return cachedEndNodeId;

  }

  public String getOtherNodeId(final AbstractNode node) {

    return getOtherNode(node).getProperty(AbstractRelationship.id);

  }

  @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) {
  }

  public boolean isValid(ErrorBuffer errorBuffer) {

    boolean error = false;

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

    return !error;

  }

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

  public void setProperties(final PropertyMap properties) throws FrameworkException {

    for (Entry<PropertyKey, Object> prop : properties.entrySet()) {

      setProperty(prop.getKey(), prop.getValue());
    }

  }

  @Override
  public <T> void setProperty(final PropertyKey<T> key, final T value) throws FrameworkException {

    // check for read-only properties
    //if (StructrApp.getConfiguration().isReadOnlyProperty(type, key) || (StructrApp.getConfiguration().isWriteOnceProperty(type, key) && (dbRelationship != null) && dbRelationship.hasProperty(key.name()))) {
    if (key.isReadOnly() || (key.isWriteOnce() && (dbRelationship != null) && dbRelationship.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<Relationship> index : Services.getInstance().getService(NodeService.class).getRelationshipIndices()) {

      synchronized (index) {

        index.remove(dbRelationship);
      }
    }
  }

  public void removeFromIndex(PropertyKey key) {

    for (Index<Relationship> index : Services.getInstance().getService(NodeService.class).getRelationshipIndices()) {

      synchronized (index) {

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

  @Override
  public void indexPassiveProperties() {

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

      if (key.isPassivelyIndexed()) {

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

  }

  @Override
  public void setSourceNodeId(final String startNodeId) throws FrameworkException {

    // Do nothing if new id equals old
    if (getSourceNodeId().equals(startNodeId)) {
      return;
    }

    final App app = StructrApp.getInstance(securityContext);

    final NodeInterface newStartNode = (NodeInterface)app.get(startNodeId);
    final NodeInterface endNode      = getTargetNode();
    final Class relationType         = getClass();
    final PropertyMap _props         = getProperties();
    final String type                = this.getClass().getSimpleName();

    if (newStartNode == null) {
      throw new FrameworkException(type, new IdNotFoundToken(startNodeId));
    }

    // delete this as the new rel will be the container afterwards
    app.delete(this);

    // create new relationship
    app.create(newStartNode, endNode, relationType, _props);
  }

  @Override
  public void setTargetNodeId(final String targetIdNode) throws FrameworkException {

    // Do nothing if new id equals old
    if (getTargetNodeId().equals(targetIdNode)) {
      return;
    }

    final App app = StructrApp.getInstance(securityContext);

    final NodeInterface newTargetNode = (NodeInterface)app.get(targetIdNode);
    final NodeInterface startNode     = getSourceNode();
    final Class relationType          = getClass();
    final PropertyMap _props          = getProperties();
    final String type                 = this.getClass().getSimpleName();

    if (newTargetNode == null) {
      throw new FrameworkException(type, new IdNotFoundToken(targetIdNode));
    }

    // delete this as the new rel will be the container afterwards
    app.delete(this);

    // create new relationship and store here
    app.create(startNode, newTargetNode, relationType, _props);
  }

  @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 methods -----
  protected Direction getDirectionForType(final Class<S> sourceType, final Class<T> targetType, final Class<? extends NodeInterface> type) {

    if (sourceType.equals(type) && targetType.equals(type)) {
      return Direction.BOTH;
    }

    if (sourceType.equals(type)) {
      return Direction.OUTGOING;
    }

    if (targetType.equals(type)) {
      return Direction.INCOMING;
    }

    if (sourceType.isAssignableFrom(type)) {
      return Direction.OUTGOING;
    }

    if (targetType.isAssignableFrom(type)) {
      return Direction.INCOMING;
    }

    return Direction.BOTH;
  }
}
TOP

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

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.