All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tinkerpop.blueprints.impls.orient.OrientEdge Maven / Gradle / Ivy

There is a newer version: 3.2.35
Show newest version
/*
 *
 *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
 *  *
 *  *  Licensed under the Apache License, Version 2.0 (the "License");
 *  *  you may not use this file except in compliance with the License.
 *  *  You may obtain a copy of the License at
 *  *
 *  *       http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *  Unless required by applicable law or agreed to in writing, software
 *  *  distributed under the License is distributed on an "AS IS" BASIS,
 *  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *  See the License for the specific language governing permissions and
 *  *  limitations under the License.
 *  *
 *  * For more information: http://orientdb.com
 *
 */

package com.tinkerpop.blueprints.impls.orient;

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ridbag.ORidBag;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.tinkerpop.blueprints.Direction;
import com.tinkerpop.blueprints.Edge;
import com.tinkerpop.blueprints.Element;
import com.tinkerpop.blueprints.Index;
import com.tinkerpop.blueprints.util.ExceptionFactory;
import com.tinkerpop.blueprints.util.StringFactory;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * OrientDB Edge implementation of TinkerPop Blueprints standard. Edges can be classic or
 * lightweight. Lightweight edges have no properties and have no identity on database. Lightweight
 * edges are created by default when an Edge is created without properties. To disable this option
 * execute this command against the database: alter database custom useLightweightEdges=false
 * .
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com) (http://orientdb.com)
 */
@SuppressWarnings("unchecked")
public class OrientEdge extends OrientElement implements Edge {
  private static final long serialVersionUID = 1L;

  protected OIdentifiable vOut;
  protected OIdentifiable vIn;
  protected String label;

  /** (Internal) Called by serialization */
  public OrientEdge() {
    super(null, null);
  }

  protected OrientEdge(final OrientBaseGraph rawGraph, final OIdentifiable rawEdge) {
    super(rawGraph, rawEdge);
  }

  protected OrientEdge(final OrientBaseGraph rawGraph, final OIdentifiable rawEdge, String iLabel) {
    super(rawGraph, rawEdge);
    label = iLabel;
  }

  protected OrientEdge(
      final OrientBaseGraph rawGraph, final String iLabel, final Object... fields) {
    super(rawGraph, null);
    rawElement = createDocument(iLabel);
    setPropertiesInternal(fields);
  }

  protected OrientEdge(
      final OrientBaseGraph rawGraph,
      final OIdentifiable out,
      final OIdentifiable in,
      final String iLabel) {
    super(rawGraph, null);
    vOut = out;
    vIn = in;
    label = iLabel;
  }

  public static OIdentifiable getConnection(
      final ODocument iEdgeRecord, final Direction iDirection) {
    return iEdgeRecord.rawField(
        iDirection == Direction.OUT
            ? OrientBaseGraph.CONNECTION_OUT
            : OrientBaseGraph.CONNECTION_IN);
  }

  /**
   * (Blueprints Extension) Returns true if the edge is labeled with any of the passed strings.
   *
   * @param iEdgeLabel Label of current edge
   * @param iLabels Labels as array of Strings
   * @return true if the edge is labeled with any of the passed strings
   */
  public static boolean isLabeled(final String iEdgeLabel, final String[] iLabels) {
    if (iLabels != null && iLabels.length > 0) {
      // FILTER LABEL
      if (iEdgeLabel != null)
        for (String l : iLabels)
          if (l.equals(iEdgeLabel))
            // FOUND
            return true;

      // NOT FOUND
      return false;
    }
    // NO LABELS
    return true;
  }

  /**
   * (Blueprints Extension) Returns the record label if any, otherwise NULL.
   *
   * @param iEdge Edge instance
   */
  public static String getRecordLabel(final OIdentifiable iEdge) {
    if (iEdge == null) return null;

    final ODocument edge = iEdge.getRecord();
    if (edge == null) return null;

    return edge.field(OrientElement.LABEL_FIELD_NAME);
  }

  /**
   * (Blueprints Extension) This method does not remove connection from opposite side.
   *
   * @param iVertex vertex that holds connection
   * @param iFieldName name of field that holds connection
   * @param iVertexToRemove target of connection
   */
  private static void removeLightweightConnection(
      final ODocument iVertex, final String iFieldName, final OIdentifiable iVertexToRemove) {
    if (iVertex == null || iVertexToRemove == null) return;

    final Object fieldValue = iVertex.field(iFieldName);
    if (fieldValue instanceof OIdentifiable) {
      if (fieldValue.equals(iVertexToRemove)) {
        iVertex.removeField(iFieldName);
      }
    } else if (fieldValue instanceof ORidBag) {
      ((ORidBag) fieldValue).remove(iVertexToRemove);
    }
  }

  public OrientEdgeType getType() {
    final OrientBaseGraph graph = getGraph();
    return isLightweight()
        ? null
        : new OrientEdgeType(graph, ((ODocument) rawElement.getRecord()).getSchemaClass());
  }

  /**
   * Returns the connected incoming or outgoing vertex.
   *
   * @param direction Direction between IN or OUT
   */
  @Override
  public OrientVertex getVertex(final Direction direction) {
    final OrientBaseGraph graph = setCurrentGraphInThreadLocal();

    if (direction.equals(Direction.OUT)) return graph.getVertex(getOutVertex());
    else if (direction.equals(Direction.IN)) return graph.getVertex(getInVertex());
    else throw ExceptionFactory.bothIsNotSupported();
  }

  /** (Blueprints Extension) Returns the outgoing vertex in form of record. */
  public OIdentifiable getOutVertex() {
    if (vOut != null)
      // LIGHTWEIGHT EDGE
      return vOut;

    setCurrentGraphInThreadLocal();

    final ODocument doc = getRecord();
    if (doc == null) return null;

    if (settings != null && settings.isKeepInMemoryReferences())
      // AVOID LAZY RESOLVING+SETTING OF RECORD
      return doc.rawField(OrientBaseGraph.CONNECTION_OUT);
    else return doc.field(OrientBaseGraph.CONNECTION_OUT);
  }

  /** (Blueprints Extension) Returns the incoming vertex in form of record. */
  public OIdentifiable getInVertex() {
    if (vIn != null)
      // LIGHTWEIGHT EDGE
      return vIn;

    setCurrentGraphInThreadLocal();

    final ODocument doc = getRecord();
    if (doc == null) return null;

    if (settings != null && settings.isKeepInMemoryReferences())
      // AVOID LAZY RESOLVING+SETTING OF RECORD
      return doc.rawField(OrientBaseGraph.CONNECTION_IN);
    else return doc.field(OrientBaseGraph.CONNECTION_IN);
  }

  /**
   * Returns the Edge's label. By default OrientDB binds the Blueprints Label concept to Edge Class.
   * To disable this feature execute this at database level 
   * alter database custom useClassForEdgeLabel=false
   * 
   */
  @Override
  public String getLabel() {
    if (label != null)
      // LIGHTWEIGHT EDGE
      return label;
    else if (rawElement != null) {
      if (settings != null && settings.isUseClassForEdgeLabel()) {
        final String clsName = getRecord().getClassName();
        if (!OrientEdgeType.CLASS_NAME.equals(clsName))
          // RETURN THE CLASS NAME
          return OrientBaseGraph.decodeClassName(clsName);
      }

      setCurrentGraphInThreadLocal();

      final ODocument doc = rawElement.getRecord();
      if (doc == null) return null;

      final String label = doc.field(OrientElement.LABEL_FIELD_NAME);
      if (label != null) return OrientBaseGraph.decodeClassName(label);
    }
    return null;
  }

  @Override
  public boolean equals(final Object object) {
    if (rawElement == null && object instanceof OrientEdge) {
      final OrientEdge other = (OrientEdge) object;
      return vOut.equals(other.vOut)
          && vIn.equals(other.vIn)
          && (label != null && label.equals(other.label));
    }
    return super.equals(object);
  }

  /** Returns the Edge Id assuring to save it if it's transient yet. */
  @Override
  public Object getId() {
    if (rawElement == null)
      // CREATE A TEMPORARY ID
      return vOut.getIdentity() + "->" + vIn.getIdentity();

    setCurrentGraphInThreadLocal();

    return super.getId();
  }

  /**
   * Returns a Property value.
   *
   * @param key Property name
   * @return Property value if any, otherwise NULL.
   */
  @Override
  public  T getProperty(final String key) {
    setCurrentGraphInThreadLocal();

    if (rawElement == null)
      // LIGHTWEIGHT EDGE
      return null;

    return super.getProperty(key);
  }

  public boolean isLightweight() {
    return rawElement == null;
  }

  /**
   * Returns all the Property names as Set of String. out, in and label are not returned as
   * properties even if are part of the underlying document because are considered internal
   * properties.
   */
  @Override
  public Set getPropertyKeys() {
    if (rawElement == null)
      // LIGHTWEIGHT EDGE
      return Collections.emptySet();

    setCurrentGraphInThreadLocal();

    final Set result = new HashSet();

    for (String field : getRecord().fieldNames())
      if (!field.equals(OrientBaseGraph.CONNECTION_OUT)
          && !field.equals(OrientBaseGraph.CONNECTION_IN)
          && (settings.isUseClassForEdgeLabel() || !field.equals(OrientElement.LABEL_FIELD_NAME)))
        result.add(field);

    return result;
  }

  @Override
  public Map getProperties() {
    if (this.rawElement == null) return null;
    final ODocument raw = this.rawElement.getRecord();
    if (raw == null) return null;

    final OrientBaseGraph graph = setCurrentGraphInThreadLocal();

    final Map result = new HashMap();

    for (String field : raw.fieldNames())
      if (!field.equals(OrientBaseGraph.CONNECTION_OUT)
          && !field.equals(OrientBaseGraph.CONNECTION_IN)
          && (settings.isUseClassForEdgeLabel() || !field.equals(OrientElement.LABEL_FIELD_NAME)))
        result.put(field, raw.field(field));

    return result;
  }

  /**
   * Set a Property value. If the edge is lightweight, it's transparently transformed into a regular
   * edge.
   *
   * @param key Property name
   * @param value Property value
   */
  @Override
  public void setProperty(final String key, final Object value) {
    setCurrentGraphInThreadLocal();

    if (rawElement == null)
      // LIGHTWEIGHT EDGE
      convertToDocument();

    super.setProperty(key, value);
  }

  /**
   * Removed a Property.
   *
   * @param key Property name
   * @return Old value if any
   */
  @Override
  public  T removeProperty(String key) {
    setCurrentGraphInThreadLocal();

    if (rawElement != null)
      // NON LIGHTWEIGHT EDGE
      return super.removeProperty(key);
    return null;
  }

  /** Removes the Edge from the Graph. Connected vertices aren't removed. */
  @Override
  public void remove() {
    final OrientBaseGraph graph = getGraph();
    if (!isLightweight()) checkClass();

    graph.setCurrentGraphInThreadLocal();
    graph.autoStartTransaction();

    for (final Index index : graph.getIndices()) {
      if (Edge.class.isAssignableFrom(index.getIndexClass())) {
        final OrientIndex idx = (OrientIndex) index;
        idx.removeElement(this);
      }
    }

    if (graph != null) graph.removeEdgeInternal(this);
    else
      // IN MEMORY CHANGES ONLY: USE NOTX CLASS
      OrientGraphNoTx.removeEdgeInternal(null, this);
  }

  /** (Blueprints Extension) Returns "E" as base class name all the edge's sub-classes extend. */
  public final String getBaseClassName() {
    return OrientEdgeType.CLASS_NAME;
  }

  /** (Blueprints Extension) Returns "Edge". */
  @Override
  public String getElementType() {
    return "Edge";
  }

  /** Returns a string representation of the edge. */
  public String toString() {
    if (setCurrentGraphInThreadLocal() != null) {
      if (getLabel() == null)
        return StringFactory.E
            + StringFactory.L_BRACKET
            + getId()
            + StringFactory.R_BRACKET
            + StringFactory.L_BRACKET
            + getVertex(Direction.OUT).getId()
            + StringFactory.ARROW
            + getVertex(Direction.IN).getId()
            + StringFactory.R_BRACKET;

      return StringFactory.edgeString(this);
    } else return StringFactory.E + StringFactory.L_BRACKET + getId();
  }

  /**
   * (Blueprints Extension) Returns the underlying record if it's a regular edge, otherwise it
   * created a document with no identity with the edge properties.
   */
  @Override
  public ODocument getRecord() {
    if (rawElement == null) {
      // CREATE AT THE FLY
      final ODocument tmp = new ODocument(getClassName(label)).setTrackingChanges(false);
      tmp.field(OrientBaseGraph.CONNECTION_IN, vIn.getIdentity());
      tmp.field(OrientBaseGraph.CONNECTION_OUT, vOut.getIdentity());
      if (label != null && settings != null && !settings.isUseClassForEdgeLabel())
        tmp.field(OrientEdge.LABEL_FIELD_NAME, label);
      return tmp;
    }

    return super.getRecord();
  }

  /**
   * (Blueprints Extension) Converts the lightweight edge to a regular edge creating the underlying
   * document to store edge's properties.
   */
  public void convertToDocument() {
    final OrientBaseGraph graph = checkIfAttached();
    if (rawElement != null)
      // ALREADY CONVERTED
      return;

    graph.setCurrentGraphInThreadLocal();
    graph.autoStartTransaction();

    final ODocument vOutRecord = vOut.getRecord();
    final ODocument vInRecord = vIn.getRecord();

    final ODocument doc = createDocument(label);

    doc.field(
        OrientBaseGraph.CONNECTION_OUT,
        settings.isKeepInMemoryReferences() ? vOutRecord.getIdentity() : vOutRecord);
    doc.field(
        OrientBaseGraph.CONNECTION_IN,
        settings.isKeepInMemoryReferences() ? vInRecord.getIdentity() : vInRecord);
    rawElement = doc;

    final boolean useVertexFieldsForEdgeLabels = settings.isUseVertexFieldsForEdgeLabels();

    final String outFieldName =
        OrientVertex.getConnectionFieldName(Direction.OUT, label, useVertexFieldsForEdgeLabels);
    removeLightweightConnection(vOutRecord, outFieldName, vInRecord);

    // OUT-VERTEX ---> IN-VERTEX/EDGE
    OrientVertex.createLink(graph, vOutRecord, doc, outFieldName);
    vOutRecord.save();

    final String inFieldName =
        OrientVertex.getConnectionFieldName(Direction.IN, label, useVertexFieldsForEdgeLabels);
    removeLightweightConnection(vInRecord, inFieldName, vOutRecord);

    // IN-VERTEX ---> OUT-VERTEX/EDGE
    OrientVertex.createLink(graph, vInRecord, doc, inFieldName);
    vInRecord.save();

    vOut = null;
    vIn = null;
    label = null;
  }

  /** (Blueprints Extension) Returns the class name based on graph settings. */
  public String getClassName(final String iLabel) {
    if (iLabel != null && (settings == null || settings.isUseClassForEdgeLabel()))
      // USE THE LABEL AS DOCUMENT CLASS
      return checkForClassInSchema(iLabel);

    return OrientEdgeType.CLASS_NAME;
  }

  @Override
  public void writeExternal(final ObjectOutput out) throws IOException {
    super.writeExternal(out);

    out.writeObject(vOut != null ? vOut.getIdentity() : null);
    out.writeObject(vIn != null ? vIn.getIdentity() : null);
    out.writeUTF(label != null ? label : "");
  }

  @Override
  public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
    super.readExternal(in);

    vOut = (OIdentifiable) in.readObject();
    vIn = (OIdentifiable) in.readObject();
    label = in.readUTF();
    if (label.isEmpty()) label = null;
  }

  /**
   * Returns true if the edge is labeled with any of the passed strings.
   *
   * @param iLabels Labels as array of Strings
   * @return true if the edge is labeled with any of the passed strings
   */
  public boolean isLabeled(final String[] iLabels) {
    return isLabeled(getLabel(), iLabels);
  }

  protected ODocument createDocument(final String iLabel) {
    final String className = getClassName(iLabel);

    final ODocument doc = new ODocument(className);

    if (iLabel != null && !settings.isUseClassForEdgeLabel())
      // SET THE LABEL AS FIELD
      doc.field(OrientElement.LABEL_FIELD_NAME, iLabel);

    return doc;
  }

  protected boolean dropEdgeFromVertex(
      final OIdentifiable iEdge,
      final ODocument iVertex,
      final String iFieldName,
      final Object iFieldValue) {
    if (iFieldValue == null) {
      // NO EDGE? WARN
      OLogManager.instance()
          .debug(
              this,
              "Edge not found in vertex's property %s.%s while removing the edge %s",
              iVertex.getIdentity(),
              iFieldName,
              iEdge.getIdentity());
      return false;

    } else if (iFieldValue instanceof OIdentifiable) {
      // FOUND A SINGLE ITEM: JUST REMOVE IT

      if (iFieldValue.equals(iEdge)) iVertex.removeField(iFieldName);
      else {
        // NO EDGE? WARN
        OLogManager.instance()
            .warn(
                this,
                "Edge not found in vertex's property %s.%s link while removing the edge %s",
                iVertex.getIdentity(),
                iFieldName,
                iEdge.getIdentity());
        return false;
      }

    } else if (iFieldValue instanceof ORidBag) {
      // ALREADY A SET: JUST REMOVE THE NEW EDGE
      final ORidBag bag = (ORidBag) iFieldValue;
      bag.remove(iEdge);
    } else if (iFieldValue instanceof Collection) {
      // CONVERT COLLECTION IN TREE-SET AND REMOVE THE EDGE
      final Collection coll = (Collection) iFieldValue;

      if (!coll.remove(iEdge)) {
        OLogManager.instance()
            .warn(
                this,
                "Edge not found in vertex's property %s.%s set while removing the edge %s",
                iVertex.getIdentity(),
                iFieldName,
                iEdge.getIdentity());
        return false;
      }

      if (coll.size() == 1) iVertex.field(iFieldName, coll.iterator().next());
      else if (coll.size() == 0) iVertex.removeField(iFieldName);
    } else
      throw new IllegalStateException(
          "Wrong type found in the field '" + iFieldName + "': " + iFieldValue.getClass());

    return true;
  }
}