org.structr.core.entity.AbstractRelationship.java Source code

Java tutorial

Introduction

Here is the source code for org.structr.core.entity.AbstractRelationship.java

Source

/*
 *  Copyright (C) 2010-2013 Axel Morgner, structr <structr@structr.org>
 *
 *  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 org.structr.core.property.IntProperty;
import org.structr.core.property.StringProperty;
import org.structr.core.property.GenericProperty;
import org.structr.core.graph.StructrTransaction;
import org.structr.core.graph.NodeFactory;
import org.structr.core.graph.TransactionCommand;
import org.structr.core.graph.GetNodeByIdCommand;
import org.structr.core.graph.CreateRelationshipCommand;
import org.structr.core.graph.DeleteRelationshipCommand;
import org.structr.core.property.Property;
import org.structr.core.property.PropertyMap;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.time.DateUtils;

import org.neo4j.graphdb.*;

import org.structr.common.*;
import org.structr.core.property.PropertyKey;
import org.structr.common.PropertyView;
import org.structr.common.RelType;
import org.structr.common.SecurityContext;
import org.structr.common.UuidCreationTransformation;
import org.structr.common.error.*;
import org.structr.common.error.FrameworkException;
import org.structr.common.error.IdNotFoundToken;
import org.structr.common.error.NullPropertyToken;
import org.structr.common.error.ReadOnlyPropertyToken;
import org.structr.core.EntityContext;
import org.structr.core.GraphObject;
import org.structr.core.converter.PropertyConverter;
import org.structr.core.PropertyGroup;
import org.structr.core.Services;
import org.structr.core.graph.NodeService.RelationshipIndex;
import org.structr.core.notion.Notion;
import org.structr.core.notion.RelationshipNotion;
import org.structr.core.validator.SimpleRegexValidator;

//~--- JDK imports ------------------------------------------------------------

import java.text.ParseException;

import java.util.*;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

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

/**
 * Bbstract base class for all relationship entities in structr.
 * 
 * @author Axel Morgner
 */
public abstract class AbstractRelationship implements GraphObject, Comparable<AbstractRelationship> {

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

    public static final Property<String> combinedType = new StringProperty("combinedType");
    public static final Property<Integer> cascadeDelete = new IntProperty("cascadeDelete");
    public static final Property<String[]> allowed = new GenericProperty<String[]>("allowed");

    //~--- static initializers --------------------------------------------

    static {

        EntityContext.registerSearchableProperty(AbstractRelationship.class, RelationshipIndex.rel_uuid.name(),
                uuid);

        // register transformation for automatic uuid creation
        EntityContext.registerEntityCreationTransformation(AbstractRelationship.class,
                new UuidCreationTransformation());

        // register uuid validator
        EntityContext.registerPropertyValidator(AbstractRelationship.class, uuid,
                new SimpleRegexValidator("[a-zA-Z0-9]{32}"));

    }

    //~--- fields ---------------------------------------------------------

    protected Class entityType = getClass();
    private String cachedEndNodeId = null;

    private String cachedStartNodeId = null;
    protected SecurityContext securityContext = null;
    private boolean readOnlyPropertiesUnlocked = false;

    // reference to database relationship
    protected Relationship dbRelationship;
    protected PropertyMap properties;
    protected String cachedUuid = null;
    protected boolean isDirty;

    //~--- constant enums -------------------------------------------------

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

    //      public enum Permission implements PropertyKey {
    //              allowed, denied, scanEntity, showTree, write, execute, createNode, deleteNode, editProperties, addRelationship, removeRelationship, accessControl;
    //      }
    public AbstractRelationship() {

        this.properties = new PropertyMap();
        isDirty = true;

    }

    public AbstractRelationship(final PropertyMap properties) {

        this.properties = properties;
        isDirty = true;

    }

    public AbstractRelationship(final SecurityContext securityContext, final PropertyMap data) {

        if (data != null) {

            this.securityContext = securityContext;
            this.properties = data;
            this.isDirty = true;

        }

    }

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

        init(securityContext, dbRel);

    }

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

    /**
     * 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.
     */
    public void onRelationshipInstantiation() {

        try {

            if (dbRelationship != null) {

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

                if ((startNode != null) && (endNode != null) && startNode.hasProperty(AbstractNode.uuid.dbName())
                        && endNode.hasProperty(AbstractNode.uuid.dbName())) {

                    cachedStartNodeId = (String) startNode.getProperty(AbstractNode.uuid.dbName());
                    cachedEndNodeId = (String) endNode.getProperty(AbstractNode.uuid.dbName());

                }

            }

        } catch (Throwable t) {
        }
    }

    public AbstractNode identifyStartNode(RelationshipMapping namedRelation, Map<String, Object> propertySet)
            throws FrameworkException {

        Notion startNodeNotion = getStartNodeNotion(); // new RelationshipNotion(getStartNodeIdKey());

        startNodeNotion.setType(namedRelation.getSourceType());

        PropertyKey startNodeIdentifier = startNodeNotion.getPrimaryPropertyKey();

        if (startNodeIdentifier != null) {

            Object identifierValue = propertySet.get(startNodeIdentifier.jsonName());

            propertySet.remove(startNodeIdentifier.jsonName());

            return (AbstractNode) startNodeNotion.getAdapterForSetter(securityContext).adapt(identifierValue);

        }

        return null;

    }

    public AbstractNode identifyEndNode(RelationshipMapping namedRelation, Map<String, Object> propertySet)
            throws FrameworkException {

        Notion endNodeNotion = getEndNodeNotion(); // new RelationshipNotion(getEndNodeIdKey());

        endNodeNotion.setType(namedRelation.getDestType());

        PropertyKey endNodeIdentifier = endNodeNotion.getPrimaryPropertyKey();

        if (endNodeIdentifier != null) {

            Object identifierValue = propertySet.get(endNodeIdentifier.jsonName());

            propertySet.remove(endNodeIdentifier.jsonName());

            return (AbstractNode) endNodeNotion.getAdapterForSetter(securityContext).adapt(identifierValue);

        }

        return null;

    }

    public void init(final SecurityContext securityContext, final Relationship dbRel) {

        this.dbRelationship = dbRel;
        this.isDirty = false;
        this.securityContext = securityContext;
    }

    public void init(final SecurityContext securityContext) {

        this.securityContext = securityContext;
        this.isDirty = false;

    }

    public void init(final SecurityContext securityContext, final AbstractRelationship rel) {

        this.dbRelationship = rel.dbRelationship;
        this.isDirty = false;
        this.securityContext = securityContext;
    }

    @Override
    public void unlockReadOnlyPropertiesOnce() {

        this.readOnlyPropertiesUnlocked = true;

    }

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

        Services.command(securityContext, TransactionCommand.class).execute(new StructrTransaction() {

            @Override
            public Object execute() throws FrameworkException {

                try {

                    dbRelationship.removeProperty(key.dbName());

                } finally {
                }

                return null;

            }

        });
    }

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

    public int cascadeDelete() {

        Integer cd = getIntProperty(AbstractRelationship.cascadeDelete);

        return (cd != null) ? cd : 0;

    }

    public void addPermission(final Permission permission) {

        String[] allowed = getPermissions();

        if (ArrayUtils.contains(allowed, permission.name())) {

            return;
        }

        setAllowed((String[]) ArrayUtils.add(allowed, permission.name()));

    }

    public void removePermission(final Permission permission) {

        String[] allowed = getPermissions();

        if (!ArrayUtils.contains(allowed, permission.name())) {

            return;
        }

        setAllowed((String[]) ArrayUtils.removeElement(allowed, permission.name()));

    }

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

    }

    public abstract PropertyKey getStartNodeIdKey();

    public abstract PropertyKey getEndNodeIdKey();

    public Notion getEndNodeNotion() {

        return new RelationshipNotion(getEndNodeIdKey());

    }

    public Notion getStartNodeNotion() {

        return new RelationshipNotion(getStartNodeIdKey());

    }

    @Override
    public long getId() {

        return getInternalId();

    }

    @Override
    public String getUuid() {

        return getProperty(AbstractRelationship.uuid);

    }

    public long getRelationshipId() {

        return getInternalId();

    }

    public long getInternalId() {

        return dbRelationship.getId();

    }

    public PropertyMap getProperties() throws FrameworkException {

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

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

    private <T> T getProperty(final PropertyKey<T> key, boolean applyConverter) {

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

        PropertyKey startNodeIdKey = getStartNodeIdKey();
        PropertyKey endNodeIdKey = getEndNodeIdKey();

        if (startNodeIdKey != null && key.equals(startNodeIdKey)) {

            return (T) getStartNodeId();
        }

        if (endNodeIdKey != null && key.equals(endNodeIdKey)) {

            return (T) getEndNodeId();
        }

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

    @Override
    public Integer getIntProperty(final PropertyKey<Integer> key) {

        Object propertyValue = getProperty(key);
        Integer result = null;

        if (propertyValue == null) {

            return null;
        }

        if (propertyValue instanceof Integer) {

            result = ((Integer) propertyValue);
        } else if (propertyValue instanceof String) {

            if ("".equals((String) propertyValue)) {

                return null;
            }

            result = Integer.parseInt(((String) propertyValue));

        }

        return result;

    }

    @Override
    public Long getLongProperty(final PropertyKey<Long> key) {

        Object propertyValue = getProperty(key);
        Long result = null;

        if (propertyValue == null) {

            return null;
        }

        if (propertyValue instanceof Long) {

            result = ((Long) propertyValue);
        } else if (propertyValue instanceof Integer) {

            result = ((Integer) propertyValue).longValue();
        } else if (propertyValue instanceof String) {

            if ("".equals((String) propertyValue)) {

                return null;
            }

            result = Long.parseLong(((String) propertyValue));

        }

        return result;

    }

    @Override
    public Date getDateProperty(final PropertyKey<Date> key) {

        Object propertyValue = getProperty(key);

        if (propertyValue != null) {

            if (propertyValue instanceof Date) {

                return (Date) propertyValue;
            } else if (propertyValue instanceof Long) {

                return new Date((Long) propertyValue);
            } else if (propertyValue instanceof String) {

                try {

                    // try to parse as a number
                    return new Date(Long.parseLong((String) propertyValue));
                } catch (NumberFormatException nfe) {

                    try {

                        Date date = DateUtils.parseDate(((String) propertyValue), new String[] {
                                "yyyy-MM-dd'T'HH:mm:ssZ", "yyyy-MM-dd'T'HH:mm:ss", "yyyymmdd", "yyyymm", "yyyy" });

                        return date;

                    } catch (ParseException ex2) {

                        logger.log(Level.WARNING, "Could not parse " + propertyValue + " to date", ex2);

                    }

                    logger.log(Level.WARNING, "Can''t parse String {0} to a Date.", propertyValue);

                    return null;

                }

            } else {

                logger.log(Level.WARNING,
                        "Date property is not null, but type is neither Long nor String, returning null");

                return null;

            }

        }

        return null;

    }

    @Override
    public boolean getBooleanProperty(final PropertyKey<Boolean> key) {

        Object propertyValue = getProperty(key);
        Boolean result = false;

        if (propertyValue == null) {

            return Boolean.FALSE;
        }

        if (propertyValue instanceof Boolean) {

            result = ((Boolean) propertyValue);
        } else if (propertyValue instanceof String) {

            result = Boolean.parseBoolean(((String) propertyValue));
        }

        return result;

    }

    @Override
    public Double getDoubleProperty(final PropertyKey<Double> key) throws FrameworkException {

        Object propertyValue = getProperty(key);
        Double result = null;

        if (propertyValue == null) {

            return null;
        }

        if (propertyValue instanceof Double) {

            Double doubleValue = (Double) propertyValue;

            if (doubleValue.equals(Double.NaN)) {

                // clean NaN values from database
                setProperty(key, null);

                return null;
            }

            result = doubleValue;

        } else if (propertyValue instanceof String) {

            if ("".equals((String) propertyValue)) {

                return null;
            }

            result = Double.parseDouble(((String) propertyValue));

        }

        return result;

    }

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

        Object propertyValue = getProperty(key, false); // get "raw" property without converter
        Class type = getClass();

        // 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
     */
    public Relationship getRelationship() {

        return dbRelationship;

    }

    public AbstractNode getEndNode() {

        NodeFactory nodeFactory = new NodeFactory(SecurityContext.getSuperUserInstance());
        return (AbstractNode) nodeFactory.createNode(dbRelationship.getEndNode());

    }

    public AbstractNode getStartNode() {

        NodeFactory nodeFactory = new NodeFactory(SecurityContext.getSuperUserInstance());
        return (AbstractNode) nodeFactory.createNode(dbRelationship.getStartNode());
    }

    public AbstractNode getOtherNode(final AbstractNode node) {

        NodeFactory nodeFactory = new NodeFactory(SecurityContext.getSuperUserInstance());
        return (AbstractNode) nodeFactory.createNode(dbRelationship.getOtherNode(node.getNode()));

    }

    public RelationshipType getRelType() {

        return dbRelationship.getType();

    }

    public String[] getPermissions() {

        if (dbRelationship.hasProperty(AbstractRelationship.allowed.dbName())) {

            // StringBuilder result             = new StringBuilder();
            String[] allowedProperties = (String[]) dbRelationship
                    .getProperty(AbstractRelationship.allowed.dbName());

            return allowedProperties;

            //                      if (allowedProperties != null) {
            //
            //                              for (String p : allowedProperties) {
            //
            //                                      result.append(p).append("\n");
            //
            //                              }
            //
            //                      }
            //
            //                      return result.toString();
        } else {

            return null;
        }

    }

    /**
     * Return all property keys.
     *
     * @return
     */
    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
     */
    @Override
    public Object getPropertyForIndexing(final PropertyKey key) {

        return getProperty(key);

    }

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

        return EntityContext.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() {

        return getRelType().name();

    }

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

    public String getStartNodeId() {

        return getStartNode().getUuid();

    }

    public String getEndNodeId() {

        return getEndNode().getUuid();

    }

    public String getOtherNodeId(final AbstractNode node) {

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

    }

    private AbstractNode getNodeByUuid(final String uuid) throws FrameworkException {

        return (AbstractNode) Services.command(securityContext, GetNodeByIdCommand.class).execute(uuid);

    }

    public String getCachedStartNodeId() {

        return cachedStartNodeId;

    }

    public String getCachedEndNodeId() {

        return cachedEndNodeId;

    }

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

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

    @Override
    public boolean beforeDeletion(SecurityContext securityContext, ErrorBuffer errorBuffer, PropertyMap properties)
            throws FrameworkException {

        cachedUuid = (String) properties.get(AbstractRelationship.uuid);

        return true;
    }

    @Override
    public void afterCreation(SecurityContext securityContext) {
    }

    @Override
    public void afterModification(SecurityContext securityContext) {
    }

    @Override
    public void afterDeletion(SecurityContext securityContext) {
    }

    @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.uuid, errorBuffer);

        return !error;

    }

    public boolean isType(RelType type) {

        return ((type != null) && type.equals(dbRelationship.getType()));

    }

    public boolean isAllowed(final Permission permission) {

        if (dbRelationship.hasProperty(allowed.dbName())) {

            String[] allowedProperties = (String[]) dbRelationship.getProperty(allowed.dbName());

            if (allowedProperties != null) {

                for (String p : allowedProperties) {

                    if (p.equals(permission.name())) {

                        return true;
                    }

                }

            }

        }

        return false;

    }

    //~--- 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 {

        PropertyKey startNodeIdKey = getStartNodeIdKey();
        PropertyKey endNodeIdKey = getEndNodeIdKey();

        if ((startNodeIdKey != null) && key.equals(startNodeIdKey)) {

            setStartNodeId((String) value);

            return;

        }

        if ((endNodeIdKey != null) && key.equals(endNodeIdKey)) {

            setEndNodeId((String) value);

            return;

        }

        // check for read-only properties
        //if (EntityContext.isReadOnlyProperty(type, key) || (EntityContext.isWriteOnceProperty(type, key) && (dbRelationship != null) && dbRelationship.hasProperty(key.name()))) {
        if (key.isReadOnlyProperty() || (key.isWriteOnceProperty() && (dbRelationship != null)
                && dbRelationship.hasProperty(key.dbName()))) {

            if (readOnlyPropertiesUnlocked) {

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

            } else {

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

        }

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

    /**
     * Set node id of start node.
     *
     * Internally, this method deletes the old relationship
     * and creates a new one, ends at the same end node,
     * but starting from the node with startNodeId
     *
     */
    public void setStartNodeId(final String startNodeId) throws FrameworkException {

        final String type = this.getClass().getSimpleName();
        final PropertyKey key = getStartNodeIdKey();

        // May never be null!!
        if (startNodeId == null) {

            throw new FrameworkException(type, new NullPropertyToken(key));
        }

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

        Services.command(securityContext, TransactionCommand.class).execute(new StructrTransaction() {

            @Override
            public Object execute() throws FrameworkException {

                DeleteRelationshipCommand deleteRel = Services.command(securityContext,
                        DeleteRelationshipCommand.class);
                CreateRelationshipCommand createRel = Services.command(securityContext,
                        CreateRelationshipCommand.class);
                AbstractNode newStartNode = getNodeByUuid(startNodeId);
                AbstractNode endNode = getEndNode();

                if (newStartNode == null) {

                    throw new FrameworkException(type, new IdNotFoundToken(startNodeId));
                }

                RelationshipType type = dbRelationship.getType();

                properties = getProperties();

                deleteRel.execute(dbRelationship);

                AbstractRelationship newRel = (AbstractRelationship) createRel.execute(newStartNode, endNode, type,
                        properties, false);

                dbRelationship = newRel.getRelationship();

                return (null);

            }

        });

    }

    /**
     * Set node id of end node.
     *
     * Internally, this method deletes the old relationship
     * and creates a new one, start from the same start node,
     * but pointing to the node with endNodeId
     *
     */
    public void setEndNodeId(final String endNodeId) throws FrameworkException {

        final String type = this.getClass().getSimpleName();
        final PropertyKey key = getStartNodeIdKey();

        // May never be null!!
        if (endNodeId == null) {

            throw new FrameworkException(type, new NullPropertyToken(key));
        }

        // Do nothing if new id equals old
        if (getEndNodeId().equals(endNodeId)) {
            return;
        }

        Services.command(securityContext, TransactionCommand.class).execute(new StructrTransaction() {

            @Override
            public Object execute() throws FrameworkException {

                DeleteRelationshipCommand deleteRel = Services.command(securityContext,
                        DeleteRelationshipCommand.class);
                CreateRelationshipCommand createRel = Services.command(securityContext,
                        CreateRelationshipCommand.class);
                AbstractNode startNode = getStartNode();
                AbstractNode newEndNode = getNodeByUuid(endNodeId);

                if (newEndNode == null) {

                    throw new FrameworkException(type, new IdNotFoundToken(endNodeId));
                }

                RelationshipType type = dbRelationship.getType();

                properties = getProperties();

                deleteRel.execute(dbRelationship);

                AbstractRelationship newRel = (AbstractRelationship) createRel.execute(startNode, newEndNode, type,
                        properties, false);

                dbRelationship = newRel.getRelationship();

                return (null);

            }

        });

    }

    /**
     * Set relationship combinedType
     *
     * Internally, this method deletes the old relationship
     * and creates a new one, with the same start and end node,
     * but with another combinedType
     *
     */
    public void setType(final String type) {

        if (type != null) {

            try {

                Services.command(securityContext, TransactionCommand.class).execute(new StructrTransaction() {

                    @Override
                    public Object execute() throws FrameworkException {

                        DeleteRelationshipCommand deleteRel = Services.command(securityContext,
                                DeleteRelationshipCommand.class);
                        CreateRelationshipCommand createRel = Services.command(securityContext,
                                CreateRelationshipCommand.class);
                        AbstractNode startNode = getStartNode();
                        AbstractNode endNode = getEndNode();

                        deleteRel.execute(dbRelationship);

                        dbRelationship = createRel.execute(startNode, endNode, type).getRelationship();

                        return (null);

                    }

                });

            } catch (FrameworkException fex) {

                logger.log(Level.WARNING, "Unable to set relationship type", fex);

            }

        }

    }

    public void setAllowed(final List<String> allowed) {

        String[] allowedActions = (String[]) allowed.toArray(new String[allowed.size()]);

        setAllowed(allowedActions);

    }

    public void setAllowed(final Permission[] allowed) {

        List<String> allowedActions = new ArrayList<String>();

        for (Permission permission : allowed) {

            allowedActions.add(permission.name());
        }

        setAllowed(allowedActions);

    }

    public void setAllowed(final String[] allowed) {

        dbRelationship.setProperty(AbstractRelationship.allowed.dbName(), allowed);

    }
}