/*
 *
 *  *  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.collection.OMultiValue;
import com.orientechnologies.common.util.OCallable;
import com.orientechnologies.common.util.OPair;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ORecordOperation;
import com.orientechnologies.orient.core.exception.ORecordNotFoundException;
import com.orientechnologies.orient.core.exception.OSchemaException;
import com.orientechnologies.orient.core.exception.OSerializationException;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OSchema;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.record.impl.OBlob;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.record.impl.ODocumentInternal;
import com.orientechnologies.orient.core.serialization.OSerializableStream;
import com.orientechnologies.orient.core.storage.OStorage;
import com.tinkerpop.blueprints.Edge;
import com.tinkerpop.blueprints.Element;
import com.tinkerpop.blueprints.util.ElementHelper;
import com.tinkerpop.blueprints.util.ExceptionFactory;
import com.tinkerpop.blueprints.util.StringFactory;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;

/**
 * Base Graph Element where OrientVertex and OrientEdge classes extends from. Labels are managed as OrientDB classes.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com) (http://orientdb.com)
 */
@SuppressWarnings("unchecked") public abstract class OrientElement
    implements Element, OSerializableStream, Externalizable, OIdentifiable {
  public static final  String  LABEL_FIELD_NAME          = "label";
  public static final  Object  DEF_ORIGINAL_ID_FIELDNAME = "origId";
  private static final long    serialVersionUID          = 1L;
  protected            boolean classicDetachMode         = false;
  protected transient OrientBaseGraph.Settings settings;
  protected           OIdentifiable            rawElement;
  private transient   OrientBaseGraph          graph;

  protected OrientElement(final OrientBaseGraph rawGraph, final OIdentifiable iRawElement) {
    if (classicDetachMode)
      graph = rawGraph;
    else
      graph = null;

    if (graph == null)
      graph = getGraph();

    if (graph != null)
      settings = graph.settings;

    rawElement = iRawElement;
  }

  public abstract String getLabel();

  public abstract String getBaseClassName();

  /**
   * (Blueprints Extension) Returns the element type in form of String between "Vertex" and "Edge".
   */
  public abstract String getElementType();

  /**
   * (Blueprints Extension) Gets all the properties from a Vertex or Edge in one shot.
   *
   * @return a map containing all the properties of the Vertex/Edge.
   */
  public abstract Map<String, Object> getProperties();

  /**
   * Removes the Element from the Graph. In case the element is a Vertex, all the incoming and outgoing edges are automatically
   * removed too.
   */
  void removeRecord() {
    checkIfAttached();

    final OrientBaseGraph graph = getGraph();
    graph.setCurrentGraphInThreadLocal();
    graph.autoStartTransaction();

    if (checkDeletedInTx())
      graph.throwRecordNotFoundException(getIdentity(), "The graph element with id " + getIdentity() + " not found");

    try {
      getRecord().load();
    } catch (ORecordNotFoundException e) {
      graph.throwRecordNotFoundException(getIdentity(), e.getMessage());
    }
    getRecord().delete();
  }

  protected boolean checkDeletedInTx() {
    OrientBaseGraph curGraph = getGraph();
    if (curGraph == null)
      return false;

    ORID id;
    if (getRecord() != null)
      id = getRecord().getIdentity();
    else
      return false;

    final ORecordOperation oper = curGraph.getRawGraph().getTransaction().getRecordEntry(id);
    if (oper == null)
      return id.isTemporary();
    else
      return oper.type == ORecordOperation.DELETED;
  }

  /**
   * (Blueprints Extension) Sets multiple properties in one shot against Vertices and Edges. This improves performance avoiding to
   * save the graph element at every property set.

   * Example:
   *

   *
   * vertex.setProperties( "name", "Jill", "age", 33, "city", "Rome", "born", "Victoria, TX" );
   *  You can also pass a Map of values as first argument. In this case all the map entries will be set as element
   * properties:
   *

   *
   * Map<String,Object> props = new HashMap<String,Object>();
   * props.put("name", "Jill");
   * props.put("age", 33);
   * props.put("city", "Rome");
   * props.put("born", "Victoria, TX");
   * vertex.setProperties(props);
   *
   *
   * @param fields Odd number of fields to set as repeating pairs of key, value, or if one parameter is received and it's a Map, the Map
   *               entries are used as field key/value pairs.
   * @param <T>
   * @return
   */
  public <T extends OrientElement> T setProperties(final Object... fields) {
    if (checkDeletedInTx())
      graph.throwRecordNotFoundException(getIdentity(), "The graph element " + getIdentity() + " has been deleted");

    setPropertiesInternal(fields);
    save();
    return (T) this;
  }

  /**
   * Sets a Property value.
   *
   * @param key   Property name
   * @param value Property value
   */
  @Override public void setProperty(final String key, final Object value) {
    if (checkDeletedInTx())
      graph.throwRecordNotFoundException(getIdentity(), "The graph element " + getIdentity() + " has been deleted");

    validateProperty(this, key, value);
    final OrientBaseGraph graph = getGraph();

    if (graph != null)
      graph.autoStartTransaction();
    getRecord().field(key, value);
    if (graph != null)
      save();
  }

  /**
   * Sets a Property value specifying a type. This is useful when you don't have a schema on this property but you want to force the
   * type.
   *
   * @param key   Property name
   * @param value Property value
   * @param iType Type to set
   */
  public void setProperty(final String key, final Object value, final OType iType) {
    if (checkDeletedInTx())
      graph.throwRecordNotFoundException(getIdentity(), "The graph element " + getIdentity() + " has been deleted");

    validateProperty(this, key, value);

    final OrientBaseGraph graph = getGraph();
    if (graph != null)
      graph.autoStartTransaction();
    getRecord().field(key, value, iType);
    if (graph != null)
      save();
  }

  /**
   * Removes a Property.
   *
   * @param key Property name
   * @return Old value if any
   */
  @Override public <T> T removeProperty(final String key) {
    if (checkDeletedInTx())
      throw new IllegalStateException("The vertex " + getIdentity() + " has been deleted");

    final OrientBaseGraph graph = getGraph();

    if (graph != null)
      graph.autoStartTransaction();

    final Object oldValue = getRecord().removeField(key);
    if (graph != null)
      save();
    return (T) oldValue;
  }

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

    final OrientBaseGraph graph = getGraph();
    if (key.equals("_class"))
      return (T) ODocumentInternal.getImmutableSchemaClass(getRecord()).getName();
    else if (key.equals("_version"))
      return (T) new Integer(getRecord().getVersion());
    else if (key.equals("_rid"))
      return (T) rawElement.getIdentity().toString();

    final ODocument record = getRecord();
    if (record == null)
      // NO RECORD
      return null;

    final Object fieldValue = record.field(key);
    if (graph != null && fieldValue instanceof OIdentifiable && !(((OIdentifiable) fieldValue).getRecord() instanceof OBlob)) {
      ODocument fieldRecord = ((OIdentifiable) fieldValue).getRecord();
      if (fieldRecord != null) {
        final OClass schemaClass = fieldRecord.getSchemaClass();
        if (schemaClass != null && (schemaClass.isVertexType() || schemaClass.isEdgeType())) {
          // CONVERT IT TO VERTEX/EDGE
          return (T) graph.getElement(fieldValue);
        }
      }
      return (T) fieldValue;
    } else if (!(fieldValue instanceof Map) && OMultiValue.isMultiValue(fieldValue) && OMultiValue
        .getFirstValue(fieldValue) instanceof OIdentifiable) {
      final OIdentifiable firstValue = (OIdentifiable) OMultiValue.getFirstValue(fieldValue);

      if (firstValue instanceof ODocument) {
        final ODocument document = (ODocument) firstValue;

        /// clusterId -2 Is considered a projection so does not have a class but is a not embedded record
        if (document.getIdentity().getClusterId() != -2 && (document.isEmbedded()
            || ODocumentInternal.getImmutableSchemaClass(document) == null))
          return (T) fieldValue;
      }

      if (graph != null)
        // CONVERT IT TO ITERABLE<VERTEX/EDGE>
        return (T) new OrientElementIterable<OrientElement>(graph, OMultiValue.getMultiValueIterable(fieldValue));
    }

    return (T) fieldValue;
  }

  /**
   * Returns the Element Id assuring to save it if it's transient yet.
   */
  @Override public Object getId() {
    return getIdentity();
  }

  /**
   * (Blueprints Extension) Saves current element. You don't need to call save() unless you're working against Temporary Vertices.
   */
  public void save() {
    save(null);
  }

  /**
   * (Blueprints Extension) Saves current element to a particular cluster. You don't need to call save() unless you're working
   * against Temporary Vertices.
   *
   * @param iClusterName Cluster name or null to use the default "E"
   */
  public void save(final String iClusterName) {
    final OrientBaseGraph graph = checkIfAttached();
    graph.setCurrentGraphInThreadLocal();

    if (rawElement instanceof ODocument)
      if (iClusterName != null)
        rawElement = ((ODocument) rawElement).save(iClusterName);
      else
        rawElement = ((ODocument) rawElement).save();
  }

  public int hashCode() {
    return ((rawElement == null) ? toString().hashCode() : rawElement.hashCode());
  }

  /**
   * (Blueprints Extension) Serializes the Element as byte[]
   *
   * @throws OSerializationException
   */
  @Override public byte[] toStream() throws OSerializationException {
    return rawElement.getIdentity().toString().getBytes();
  }

  /**
   * (Blueprints Extension) Fills the Element from a byte[]
   *
   * @param stream byte array representation of the object
   * @throws OSerializationException
   */
  @Override public OSerializableStream fromStream(final byte[] stream) throws OSerializationException {
    final ODocument record = getRecord();
    ((ORecordId) record.getIdentity()).fromString(new String(stream));
    return this;
  }

  @Override public void writeExternal(final ObjectOutput out) throws IOException {
    out.writeObject(rawElement != null ? rawElement.getIdentity() : null);
  }

  @Override public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
    rawElement = (OIdentifiable) in.readObject();
  }

  /**
   * (Blueprints Extension) Locks current Element to prevent concurrent access. If lock is exclusive, then no concurrent threads can
   * read/write it. If the lock is shared, then concurrent threads can only read Element properties, but can't change them. Locks
   * can be freed by calling @unlock or when the current transaction is closed (committed or rollbacked).
   *
   * @param iExclusive True = Exclusive Lock, False = Shared Lock
   * @see #lock(boolean)
   */
  @Override public void lock(final boolean iExclusive) {
    ODatabaseRecordThreadLocal.instance().get().getTransaction()
        .lockRecord(this, iExclusive ? OStorage.LOCKING_STRATEGY.EXCLUSIVE_LOCK : OStorage.LOCKING_STRATEGY.SHARED_LOCK);
  }

  /**
   * (Blueprints Extension) Checks if an Element is locked
   */
  @Override public boolean isLocked() {
    return ODatabaseRecordThreadLocal.instance().get().getTransaction().isLockedRecord(this);
  }

  @Override public OStorage.LOCKING_STRATEGY lockingStrategy() {
    return ODatabaseRecordThreadLocal.instance().get().getTransaction().lockingStrategy(this);
  }

  /**
   * (Blueprints Extension) Unlocks previous acquired @lock against the Element.
   *
   * @see #lock(boolean)
   */
  @Override public void unlock() {
    ODatabaseRecordThreadLocal.instance().get().getTransaction().unlockRecord(this);
  }

  /**
   * (Blueprints Extension) Returns the record's identity.
   */
  @Override public ORID getIdentity() {
    if (rawElement == null)
      return ORecordId.EMPTY_RECORD_ID;

    final ORID rid = rawElement.getIdentity();
    if (!rid.isValid()) {
      final OrientBaseGraph graph = getGraph();
      if (graph != null) {
        // SAVE THE RECORD TO OBTAIN A VALID RID
        graph.setCurrentGraphInThreadLocal();
        graph.autoStartTransaction();
        save();
      }
    }
    return rid;
  }

  /**
   * (Blueprints Extension) Returns the underlying record.
   */
  @Override public ODocument getRecord() {
    if (rawElement == null)
      return null;

    if (rawElement instanceof ODocument)
      return (ODocument) rawElement;

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

    // CHANGE THE RID -> DOCUMENT
    rawElement = doc;
    return doc;
  }

  /**
   * (Blueprints Extension) Removes the reference to the current graph instance to let working offline. To reattach it use @attach.
   *

   * This methods works only in "classic detach/attach mode" when dettachment/attachment is done manually, by default it is done
   * automatically, and currently active graph connection will be used as graph elements owner.
   *
   * @return Current object to allow chained calls.
   * @see #attach(OrientBaseGraph), #isDetached
   */
  public OrientElement detach() {
    // EARLY UNMARSHALL FIELDS
    getRecord().setLazyLoad(false);
    getRecord().fieldNames();
    // COPY GRAPH SETTINGS TO WORK OFFLINE
    if (graph != null) {
      settings = graph.settings.copy();
      graph = null;
    }
    classicDetachMode = true;
    return this;
  }

  /**
   * Switches to auto attachment mode, when graph element is automatically attached to currently open graph instance.
   */
  public void switchToAutoAttachmentMode() {
    graph = null;
    classicDetachMode = false;
  }

  /**
   * Behavior is the same as for {@link #attach(OrientBaseGraph)} method.
   */
  public void switchToManualAttachmentMode(final OrientBaseGraph iNewGraph) {
    attach(iNewGraph);
  }

  /**
   * (Blueprints Extension) Replaces current graph instance with new one on @detach -ed elements. Use this method to pass elements
   * between graphs or to switch between Tx and NoTx instances.
   *

   * This methods works only in "classic detach/attach mode" when detachment/attachment is done manually, by default it is done
   * automatically, and currently active graph connection will be used as graph elements owner.
   *

   * To set "classic detach/attach mode" please set custom database parameter classicDetachMode to true.
   *
   * @param iNewGraph The new Graph instance to use.
   * @return Current object to allow chained calls.
   * @see #detach(), #isDetached
   */
  public OrientElement attach(final OrientBaseGraph iNewGraph) {
    if (iNewGraph == null)
      throw new IllegalArgumentException("Graph is null");

    classicDetachMode = true;
    graph = iNewGraph;

    // LINK THE GRAPHS SETTINGS
    settings = graph.settings;
    return this;
  }

  /**
   * (Blueprints Extension) Tells if the current element has been @detach ed.
   *

   * This methods works only in "classic detach/attach mode" when detachment/attachment is done manually, by default it is done
   * automatically, and currently active graph connection will be used as graph elements owner.
   *

   * To set "classic detach/attach mode" please set custom database parameter classicDetachMode to true.
   *
   * @return True if detached, otherwise false
   * @see #attach(OrientBaseGraph), #detach
   */
  public boolean isDetached() {
    return getGraph() == null;
  }

  public boolean equals(final Object object) {
    return ElementHelper.areEqual(this, object);
  }

  public int compare(final OIdentifiable iFirst, final OIdentifiable iSecond) {
    if (iFirst == null || iSecond == null)
      return -1;
    return iFirst.compareTo(iSecond);
  }

  public int compareTo(final OIdentifiable iOther) {
    if (iOther == null)
      return 1;

    final ORID myRID = getIdentity();
    final ORID otherRID = iOther.getIdentity();

    if (myRID == null && otherRID == null)
      return 0;
    if (myRID == null)
      return -1;
    if (otherRID == null)
      return 1;

    return myRID.compareTo(otherRID);
  }

  /**
   * (Blueprints Extension) Returns the Graph instance associated to the current element. On @detach ed elements returns NULL.
   */
  public OrientBaseGraph getGraph() {
    if (classicDetachMode)
      return graph;

    OrientBaseGraph result = OrientBaseGraph.getActiveGraph();
    if (result == null && this.graph != null && !graph.isClosed()) {
      result = graph;
    }
    return result;
  }

  /**
   * (Blueprints Extension) Validates an Element property.
   *
   * @param element Element instance
   * @param key     Property name
   * @param value   property value
   * @throws IllegalArgumentException
   */
  public final void validateProperty(final Element element, final String key, final Object value) throws IllegalArgumentException {
    if (settings.isStandardElementConstraints() && null == value)
      throw ExceptionFactory.propertyValueCanNotBeNull();
    if (null == key)
      throw ExceptionFactory.propertyKeyCanNotBeNull();
    if (settings.isStandardElementConstraints() && key.equals(StringFactory.ID))
      throw ExceptionFactory.propertyKeyIdIsReserved();
    if (element instanceof Edge && key.equals(StringFactory.LABEL))
      throw ExceptionFactory.propertyKeyLabelIsReservedForEdges();
    if (key.isEmpty())
      throw ExceptionFactory.propertyKeyCanNotBeEmpty();
  }

  public void reload() {
    final ODocument rec = getRecord();
    if (rec != null)
      rec.reload(null, true);
  }

  protected void copyTo(final OrientElement iCopy) {
    iCopy.graph = graph;
    iCopy.settings = settings;
    if (rawElement instanceof ODocument) {
      iCopy.rawElement = new ODocument().fromStream(((ODocument) rawElement).toStream());
    } else if (rawElement instanceof ORID)
      iCopy.rawElement = ((ORID) rawElement).copy();
    else
      throw new IllegalArgumentException("Cannot clone element " + rawElement);
  }

  protected void checkClass() {
    // FORCE EARLY UNMARSHALLING
    final ODocument doc = getRecord();
    doc.deserializeFields();

    final OClass cls = ODocumentInternal.getImmutableSchemaClass(doc);

    if (cls == null || !cls.isSubClassOf(getBaseClassName()))
      throw new IllegalArgumentException("The document received is not a " + getElementType() + ". Found class '" + cls + "'");
  }

  /**
   * Check if a class already exists, otherwise create it at the fly. If a transaction is running commit changes, create the class
   * and begin a new transaction.
   *
   * @param className Class's name
   */
  protected String checkForClassInSchema(final String className) {
    if (className == null)
      return null;

    OrientBaseGraph graph = getGraph();
    if (graph == null)
      return className;

    final OSchema schema = graph.getRawGraph().getMetadata().getSchema();

    if (!schema.existsClass(className)) {
      // CREATE A NEW CLASS AT THE FLY
      try {
        graph.executeOutsideTx(new OCallable<OClass, OrientBaseGraph>() {

                                 @Override public OClass call(final OrientBaseGraph g) {
                                   return schema.createClass(className, schema.getClass(getBaseClassName()));

                                 }
                               }, "Committing the active transaction to create the new type '", className, "' as subclass of '", getBaseClassName(),
            "'. The transaction will be reopen right after that. To avoid this behavior create the classes outside the transaction");

      } catch (OSchemaException e) {
        if (!schema.existsClass(className))
          throw e;
      }
    } else {
      // CHECK THE CLASS INHERITANCE
      final OClass cls = schema.getClass(className);
      if (!cls.isSubClassOf(getBaseClassName()))
        throw new IllegalArgumentException("Class '" + className + "' is not an instance of " + getBaseClassName());
    }

    return className;
  }

  protected void setPropertyInternal(final Element element, final ODocument doc, final String key, final Object value) {
    validateProperty(element, key, value);
    doc.field(key, value);
  }

  protected OrientBaseGraph setCurrentGraphInThreadLocal() {
    final OrientBaseGraph graph = getGraph();
    if (graph != null)
      graph.setCurrentGraphInThreadLocal();
    return graph;
  }

  protected OrientBaseGraph checkIfAttached() {
    final OrientBaseGraph graph = getGraph();
    if (graph == null)
      throw new IllegalStateException(
          "There is no active graph instance for current element. Please either open connection to your storage, or use detach/attach methods instead.");
    return graph;
  }

  /**
   * (Blueprints Extension) Sets multiple properties in one shot against Vertices and Edges without saving the element. This
   * improves performance avoiding to save the graph element at every property set. Example:
   *

   *
   * vertex.setProperties( "name", "Jill", "age", 33, "city", "Rome", "born", "Victoria, TX" );
   *  You can also pass a Map of values as first argument. In this case all the map entries will be set as element
   * properties:
   *

   *
   * Map<String,Object> props = new HashMap<String,Object>();
   * props.put("name", "Jill");
   * props.put("age", 33);
   * props.put("city", "Rome");
   * props.put("born", "Victoria, TX");
   * vertex.setProperties(props);
   *
   *
   * @param fields Odd number of fields to set as repeating pairs of key, value, or if one parameter is received and it's a Map, the Map
   *               entries are used as field key/value pairs.
   * @param <T>
   * @return
   */
  protected <T extends OrientElement> T setPropertiesInternal(final Object... fields) {
    OrientBaseGraph graph = getGraph();
    if (fields != null && fields.length > 0 && fields[0] != null) {
      if (graph != null)
        graph.autoStartTransaction();

      if (fields.length == 1) {
        Object f = fields[0];
        if (f instanceof Map<?, ?>) {
          for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) f).entrySet())
            setPropertyInternal(this, (ODocument) rawElement.getRecord(), entry.getKey().toString(), entry.getValue());

        } else if (f instanceof Collection) {
          for (Object o : (Collection) f) {
            if (!(o instanceof OPair))
              throw new IllegalArgumentException(
                  "Invalid fields: expecting a pairs of fields as String,Object, but found the item: " + o);

            final OPair entry = (OPair) o;
            setPropertyInternal(this, (ODocument) rawElement.getRecord(), entry.getKey().toString(), entry.getValue());
          }

        } else
          throw new IllegalArgumentException(
              "Invalid fields: expecting a pairs of fields as String,Object or a single Map<String,Object>, but found: " + f);
      } else {
        if (fields.length % 2 != 0)
          throw new IllegalArgumentException(
              "Invalid fields: expecting a pairs of fields as String,Object or a single Map<String,Object>, but found: " + Arrays
                  .toString(fields));

        // SET THE FIELDS
        for (int i = 0; i < fields.length; i += 2)
          setPropertyInternal(this, (ODocument) rawElement.getRecord(), fields[i].toString(), fields[i + 1]);
      }
    }
    return (T) this;
  }
}


/*
 *
 *  *  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.orientechnologies.orient.core.config;

import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Represents a context configuration where custom setting could be defined for the context only. If not defined, globals will be
 * taken.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 */
public class OContextConfiguration implements Serializable {
  private final Map<String, Object> config = new ConcurrentHashMap<String, Object>();

  /**
   * Empty constructor to create just a proxy for the OGlobalConfiguration. No values are setted.
   */
  public OContextConfiguration() {
  }

  /**
   * Initializes the context with custom parameters.
   *
   * @param iConfig Map of parameters of type Map<String, Object>.
   */
  public OContextConfiguration(final Map<String, Object> iConfig) {
    this.config.putAll(iConfig);
  }

  public OContextConfiguration(final OContextConfiguration iParent) {
    if (iParent != null)
      config.putAll(iParent.config);
  }

  public Object setValue(final OGlobalConfiguration iConfig, final Object iValue) {
    if (iValue == null)
      return config.remove(iConfig.getKey());

    return config.put(iConfig.getKey(), iValue);
  }

  public Object setValue(final String iName, final Object iValue) {
    if (iValue == null)
      return config.remove(iName);

    return config.put(iName, iValue);
  }

  public Object getValue(final OGlobalConfiguration iConfig) {
    if (config != null && config.containsKey(iConfig.getKey()))
      return config.get(iConfig.getKey());
    return iConfig.getValue();
  }

  /**
   * @param config Global configuration parameter.
   *
   * @return Value of configuration parameter stored in this context as enumeration if such one exists, otherwise value stored in
   * passed in {@link OGlobalConfiguration} instance.
   *
   * @throws ClassCastException       if stored value can not be casted and parsed from string to passed in enumeration class.
   * @throws IllegalArgumentException if value associated with configuration parameter is a string bug can not be converted to
   *                                  instance of passed in enumeration class.
   */
  public <T extends Enum<T>> T getValueAsEnum(final OGlobalConfiguration config, Class<T> enumType) {
    final Object value;
    if (this.config != null && this.config.containsKey(config.getKey())) {
      value = this.config.get(config.getKey());
    } else {
      value = config.getValue();
    }

    if (value == null)
      return null;

    if (enumType.isAssignableFrom(value.getClass())) {
      return enumType.cast(value);
    } else if (value instanceof String) {
      final String presentation = value.toString();
      return Enum.valueOf(enumType, presentation);
    } else {
      throw new ClassCastException("Value " + value + " can not be cast to enumeration " + enumType.getSimpleName());
    }
  }

  @SuppressWarnings("unchecked")
  public <T> T getValue(final String iName, final T iDefaultValue) {
    if (config != null && config.containsKey(iName))
      return (T) config.get(iName);

    final String sysProperty = System.getProperty(iName);
    if (sysProperty != null)
      return (T) sysProperty;

    return iDefaultValue;
  }

  public boolean getValueAsBoolean(final OGlobalConfiguration iConfig) {
    final Object v = getValue(iConfig);
    if (v == null)
      return false;
    return v instanceof Boolean ? ((Boolean) v).booleanValue() : Boolean.parseBoolean(v.toString());
  }

  public String getValueAsString(final String iName, final String iDefaultValue) {
    return getValue(iName, iDefaultValue);
  }

  public String getValueAsString(final OGlobalConfiguration iConfig) {
    final Object v = getValue(iConfig);
    if (v == null)
      return null;
    return v.toString();
  }

  public int getValueAsInteger(final OGlobalConfiguration iConfig) {
    final Object v = getValue(iConfig);
    if (v == null)
      return 0;
    return v instanceof Integer ? ((Integer) v).intValue() : Integer.parseInt(v.toString());
  }

  public long getValueAsLong(final OGlobalConfiguration iConfig) {
    final Object v = getValue(iConfig);
    if (v == null)
      return 0;
    return v instanceof Long ? ((Long) v).intValue() : Long.parseLong(v.toString());
  }

  public float getValueAsFloat(final OGlobalConfiguration iConfig) {
    final Object v = getValue(iConfig);
    if (v == null)
      return 0;
    return v instanceof Float ? ((Float) v).floatValue() : Float.parseFloat(v.toString());
  }

  public int getContextSize() {
    return config.size();
  }

  public java.util.Set<String> getContextKeys() {
    return config.keySet();
  }

  public void merge(OContextConfiguration contextConfiguration) {
    this.config.putAll(contextConfiguration.config);
  }
}


package com.orientechnologies.orient.core.index;

import com.orientechnologies.common.util.OSizeable;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.exception.OIndexIsRebuildingException;

import java.util.Map;
import java.util.Set;

/**
 * Wrapper which is used to detect whether the cursor is working with index
 * which is being rebuilt at the moment.
 *

 * If such situation is detected any call to any method throws {@link OIndexIsRebuildingException}
 *
 * @see OIndexAbstract#getRebuildVersion()
 */
public class OIndexChangesWrapper implements OIndexCursor {
  protected final OIndex<?>    source;
  protected final OIndexCursor delegate;
  protected final long         indexRebuildVersion;

  public OIndexChangesWrapper(OIndex<?> source, OIndexCursor delegate, long indexRebuildVersion) {
    this.source = source;
    this.delegate = delegate;

    this.indexRebuildVersion = indexRebuildVersion;
  }

  /**
   * Wraps courser only if it is not already wrapped.
   *
   * @param source              Index which is used to create given cursor.
   * @param cursor              Cursor to wrap.
   * @param indexRebuildVersion Rebuild version of index before cursor was created.
   * @return Wrapped cursor.
   * @see OIndex#getRebuildVersion()
   */
  public static OIndexCursor wrap(OIndex<?> source, OIndexCursor cursor, long indexRebuildVersion) {
    if (cursor instanceof OIndexChangesWrapper)
      return cursor;

    if (cursor instanceof OSizeable) {
      return new OIndexChangesSizeable(source, cursor, indexRebuildVersion);
    }

    return new OIndexChangesWrapper(source, cursor, indexRebuildVersion);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void remove() {
    delegate.remove();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Map.Entry<Object, OIdentifiable> nextEntry() {
    if (source.isRebuilding())
      throwRebuildException();

    final Map.Entry<Object, OIdentifiable> entry = delegate.nextEntry();

    if (source.getRebuildVersion() != indexRebuildVersion)
      throwRebuildException();

    return entry;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Set<OIdentifiable> toValues() {
    if (source.isRebuilding())
      throwRebuildException();

    final Set<OIdentifiable> values = delegate.toValues();

    if (source.getRebuildVersion() != indexRebuildVersion)
      throwRebuildException();

    return values;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Set<Map.Entry<Object, OIdentifiable>> toEntries() {
    if (source.isRebuilding())
      throwRebuildException();

    final Set<Map.Entry<Object, OIdentifiable>> entries = delegate.toEntries();

    if (source.getRebuildVersion() != indexRebuildVersion)
      throwRebuildException();

    return entries;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Set<Object> toKeys() {
    if (source.isRebuilding())
      throwRebuildException();

    final Set<Object> keys = delegate.toKeys();

    if (source.getRebuildVersion() != indexRebuildVersion)
      throwRebuildException();

    return keys;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setPrefetchSize(int prefetchSize) {
    delegate.setPrefetchSize(prefetchSize);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean hasNext() {
    if (source.isRebuilding())
      throwRebuildException();

    final boolean isNext = delegate.hasNext();

    if (source.getRebuildVersion() != indexRebuildVersion)
      throwRebuildException();

    return isNext;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public OIdentifiable next() {
    if (source.isRebuilding())
      throwRebuildException();

    final OIdentifiable next = delegate.next();

    if (source.getRebuildVersion() != indexRebuildVersion)
      throwRebuildException();

    return next;
  }

  protected void throwRebuildException() {
    throw new OIndexIsRebuildingException("Index " + source.getName() + " is rebuilding at the moment and can not be used");
  }
}

/**
 * Adds support of {@link OSizeable} interface if index cursor implements it.
 */
class OIndexChangesSizeable extends OIndexChangesWrapper implements OSizeable {
  public OIndexChangesSizeable(OIndex<?> source, OIndexCursor delegate, long indexRebuildVersion) {
    super(source, delegate, indexRebuildVersion);
  }

  @Override
  public int size() {
    if (source.isRebuilding())
      throwRebuildException();

    final int size = ((OSizeable) delegate).size();

    if (source.getRebuildVersion() != indexRebuildVersion)
      throwRebuildException();

    return size;
  }
}


/*
 *
 *  *  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.orientechnologies.orient.graph.graphml;

import com.orientechnologies.common.exception.OException;
import com.orientechnologies.common.io.OFileUtils;
import com.orientechnologies.orient.core.command.OCommandOutputListener;
import com.orientechnologies.orient.core.db.tool.ODatabaseImportException;
import com.orientechnologies.orient.core.id.ORID;
import com.tinkerpop.blueprints.Edge;
import com.tinkerpop.blueprints.Graph;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.blueprints.impls.orient.OrientBaseGraph;
import com.tinkerpop.blueprints.impls.orient.OrientVertex;
import com.tinkerpop.blueprints.util.io.graphml.GraphMLTokens;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.events.XMLEvent;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * GraphMLReader writes the data from a GraphML stream to a graph. Derived from Blueprints GraphMLReader. Supports also vertex
 * labels.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com) (l.garulli(at)orientdb.com)
 * @author Marko A. Rodriguez (http://markorodriguez.com)
 * @author Alex Averbuch (alex.averbuch-at-gmail.com)
 * @author Joshua Shinavier (http://fortytwo.net)
 */
public class OGraphMLReader {
  private static final String                 LABELS              = "labels";
  private final OrientBaseGraph               graph;
  private int                                 vertexLabelIndex    = 0;
  private String                              vertexIdKey         = "id";
  private String                              edgeIdKey           = null;
  private String                              edgeLabelKey        = GraphMLTokens.LABEL;
  private boolean                             storeVertexIds      = false;
  private int                                 batchSize           = 1000;
  private Map<String, OGraphMLImportStrategy> vertexPropsStrategy = new HashMap<String, OGraphMLImportStrategy>();
  private Map<String, OGraphMLImportStrategy> edgePropsStrategy   = new HashMap<String, OGraphMLImportStrategy>();
  private OCommandOutputListener              output;

  /**
   * @param graph
   *          the graph to populate with the GraphML data
   */
  public OGraphMLReader(OrientBaseGraph graph) {
    this.graph = graph;
  }

  /**
   * Define custom strategy to use for vertex attribute.
   *
   * @param iAttributeName
   *          attribute name
   * @param iStrategy
   *          strategy implementation
   */
  public OGraphMLReader defineVertexAttributeStrategy(final String iAttributeName, final OGraphMLImportStrategy iStrategy) {
    vertexPropsStrategy.put(iAttributeName, iStrategy);
    return this;
  }

  /**
   * Define custom strategy to use for edge attribute.
   *
   * @param iAttributeName
   *          attribute name
   * @param iStrategy
   *          strategy implementation
   */
  public OGraphMLReader defineEdgeAttributeStrategy(final String iAttributeName, final OGraphMLImportStrategy iStrategy) {
    edgePropsStrategy.put(iAttributeName, iStrategy);
    return this;
  }

  /**
   * Input the GraphML stream data into the graph. In practice, usually the provided graph is empty.
   *
   * @param inputGraph
   *          the graph to populate with the GraphML data
   * @param graphMLInputStream
   *          an InputStream of GraphML data
   * @throws IOException
   *           thrown when the GraphML data is not correctly formatted
   */
  public void inputGraph(final Graph inputGraph, final InputStream graphMLInputStream) throws IOException {
    inputGraph(inputGraph, graphMLInputStream, batchSize, vertexIdKey, edgeIdKey, edgeLabelKey);
  }

  /**
   * Input the GraphML stream data into the graph. In practice, usually the provided graph is empty.
   *
   * @param inputGraph
   *          the graph to populate with the GraphML data
   * @param filename
   *          name of a file containing GraphML data
   * @throws IOException
   *           thrown when the GraphML data is not correctly formatted
   */
  public void inputGraph(final Graph inputGraph, final String filename) throws IOException {
    inputGraph(inputGraph, filename, batchSize, vertexIdKey, edgeIdKey, edgeLabelKey);
  }

  /**
   * Input the GraphML stream data into the graph. More control over how data is streamed is provided by this method.
   *
   * @param inputGraph
   *          the graph to populate with the GraphML data
   * @param filename
   *          name of a file containing GraphML data
   * @param bufferSize
   *          the amount of elements to hold in memory before committing a transactions (only valid for TransactionalGraphs)
   * @param vertexIdKey
   *          if the id of a vertex is a <data/> property, fetch it from the data property.
   * @param edgeIdKey
   *          if the id of an edge is a <data/> property, fetch it from the data property.
   * @param edgeLabelKey
   *          if the label of an edge is a <data/> property, fetch it from the data property.
   * @throws IOException
   *           thrown when the GraphML data is not correctly formatted
   */
  public OGraphMLReader inputGraph(final Graph inputGraph, final String filename, int bufferSize, String vertexIdKey,
      String edgeIdKey, String edgeLabelKey) throws IOException {
    FileInputStream fis = new FileInputStream(filename);
    try {
      return inputGraph(inputGraph, fis, bufferSize, vertexIdKey, edgeIdKey, edgeLabelKey);
    } finally {
      fis.close();
    }
  }

  /**
   * Input the GraphML stream data into the graph. More control over how data is streamed is provided by this method.
   *
   * @param inputGraph
   *          the graph to populate with the GraphML data
   * @param graphMLInputStream
   *          an InputStream of GraphML data
   * @param bufferSize
   *          the amount of elements to hold in memory before committing a transactions (only valid for TransactionalGraphs)
   * @param vertexIdKey
   *          if the id of a vertex is a <data/> property, fetch it from the data property.
   * @param edgeIdKey
   *          if the id of an edge is a <data/> property, fetch it from the data property.
   * @param edgeLabelKey
   *          if the label of an edge is a <data/> property, fetch it from the data property.
   * @throws IOException
   *           thrown when the GraphML data is not correctly formatted
   */
  public OGraphMLReader inputGraph(final Graph inputGraph, final InputStream graphMLInputStream, int bufferSize, String vertexIdKey,
      String edgeIdKey, String edgeLabelKey) throws IOException {

    XMLInputFactory inputFactory = XMLInputFactory.newInstance();

    try {
      XMLStreamReader reader = inputFactory.createXMLStreamReader(graphMLInputStream);

      final OrientBaseGraph graph = (OrientBaseGraph) inputGraph;

      if (storeVertexIds)
        graph.setSaveOriginalIds(storeVertexIds);

      Map<String, String> keyIdMap = new HashMap<String, String>();
      Map<String, String> keyTypesMaps = new HashMap<String, String>();
      // <Mapped ID String, ID Object>

      // <Default ID String, Mapped ID String>
      Map<String, ORID> vertexMappedIdMap = new HashMap<String, ORID>();

      // Buffered Vertex Data
      String vertexId = null;
      Map<String, Object> vertexProps = null;
      boolean inVertex = false;

      // Buffered Edge Data
      String edgeId = null;
      String edgeLabel = null;
      String vertexLabel = null;
      Vertex[] edgeEndVertices = null; // [0] = outVertex , [1] = inVertex
      Map<String, Object> edgeProps = null;
      boolean inEdge = false;

      int bufferCounter = 0;

      long importedVertices = 0;
      long importedEdges = 0;

      while (reader.hasNext()) {

        Integer eventType = reader.next();
        if (eventType.equals(XMLEvent.START_ELEMENT)) {
          String elementName = reader.getName().getLocalPart();

          if (elementName.equals(GraphMLTokens.KEY)) {
            String id = reader.getAttributeValue(null, GraphMLTokens.ID);
            String attributeName = reader.getAttributeValue(null, GraphMLTokens.ATTR_NAME);
            String attributeType = reader.getAttributeValue(null, GraphMLTokens.ATTR_TYPE);
            keyIdMap.put(id, attributeName);
            keyTypesMaps.put(id, attributeType);

          } else if (elementName.equals(GraphMLTokens.NODE)) {
            vertexId = reader.getAttributeValue(null, GraphMLTokens.ID);
            vertexLabel = reader.getAttributeValue(null, LABELS);
            if (vertexLabel != null) {
              if (vertexLabel.startsWith(":"))
                // REMOVE : AS PREFIX
                vertexLabel = vertexLabel.substring(1);

              final String[] vertexLabels = vertexLabel.split(":");

              // GET ONLY FIRST LABEL AS CLASS
              vertexLabel = vertexId + ",class:" + vertexLabels[vertexLabelIndex];
            } else
              vertexLabel = vertexId;

            inVertex = true;
            vertexProps = new HashMap<String, Object>();

          } else if (elementName.equals(GraphMLTokens.EDGE)) {
            edgeId = reader.getAttributeValue(null, GraphMLTokens.ID);
            edgeLabel = reader.getAttributeValue(null, GraphMLTokens.LABEL);
            edgeLabel = edgeLabel == null ? GraphMLTokens._DEFAULT : edgeLabel;

            String[] vertexIds = new String[2];
            vertexIds[0] = reader.getAttributeValue(null, GraphMLTokens.SOURCE);
            vertexIds[1] = reader.getAttributeValue(null, GraphMLTokens.TARGET);
            edgeEndVertices = new Vertex[2];

            for (int i = 0; i < 2; i++) { // i=0 => outVertex, i=1 => inVertex
              if (vertexIdKey == null) {
                edgeEndVertices[i] = null;
              } else {
                final Object vId = vertexMappedIdMap.get(vertexIds[i]);
                edgeEndVertices[i] = vId != null ? graph.getVertex(vId) : null;
              }

              if (null == edgeEndVertices[i]) {
                edgeEndVertices[i] = graph.addVertex(vertexLabel);
                if (vertexIdKey != null) {
                  mapId(vertexMappedIdMap, vertexIds[i], (ORID) edgeEndVertices[i].getId());
                }
                bufferCounter++;
                importedVertices++;
                printStatus(reader, importedVertices, importedEdges);
              }
            }

            inEdge = true;
            vertexLabel = null;
            edgeProps = new HashMap<String, Object>();

          } else if (elementName.equals(GraphMLTokens.DATA)) {
            String key = reader.getAttributeValue(null, GraphMLTokens.KEY);
            String attributeName = keyIdMap.get(key);

            if (attributeName == null)
              attributeName = key;

            String value = reader.getElementText();

            if (inVertex) {
              if ((vertexIdKey != null) && (key.equals(vertexIdKey))) {
                // Should occur at most once per Vertex
                vertexId = value;
              } else if (attributeName.equalsIgnoreCase(LABELS)) {
                // IGNORE LABELS
              } else {
                final Object attrValue = typeCastValue(key, value, keyTypesMaps);

                final OGraphMLImportStrategy strategy = vertexPropsStrategy.get(attributeName);
                if (strategy != null) {
                  attributeName = strategy.transformAttribute(attributeName, attrValue);
                }

                if (attributeName != null)
                  vertexProps.put(attributeName, attrValue);
              }
            } else if (inEdge) {
              if ((edgeLabelKey != null) && (key.equals(edgeLabelKey)))
                edgeLabel = value;
              else if ((edgeIdKey != null) && (key.equals(edgeIdKey)))
                edgeId = value;
              else {
                final Object attrValue = typeCastValue(key, value, keyTypesMaps);

                final OGraphMLImportStrategy strategy = edgePropsStrategy.get(attributeName);
                if (strategy != null) {
                  attributeName = strategy.transformAttribute(attributeName, attrValue);
                }

                if (attributeName != null)
                  edgeProps.put(attributeName, attrValue);
              }
            }

          }
        } else if (eventType.equals(XMLEvent.END_ELEMENT)) {
          String elementName = reader.getName().getLocalPart();

          if (elementName.equals(GraphMLTokens.NODE)) {
            ORID currentVertex = null;

            if (vertexIdKey != null)
              currentVertex = vertexMappedIdMap.get(vertexId);

            if (currentVertex == null) {
              final OrientVertex v = graph.addVertex(vertexLabel, vertexProps);
              if (vertexIdKey != null)
                mapId(vertexMappedIdMap, vertexId, v.getIdentity());
              bufferCounter++;

              importedVertices++;
              printStatus(reader, importedVertices, importedEdges);

            } else {
              // UPDATE IT
              final OrientVertex v = graph.getVertex(currentVertex);
              v.setProperties(vertexProps);
            }

            vertexId = null;
            vertexLabel = null;
            vertexProps = null;
            inVertex = false;
          } else if (elementName.equals(GraphMLTokens.EDGE)) {
            Edge currentEdge = ((OrientVertex) edgeEndVertices[0]).addEdge(null, (OrientVertex) edgeEndVertices[1], edgeLabel, null,
                edgeProps);
            bufferCounter++;

            importedEdges++;
            printStatus(reader, importedVertices, importedEdges);

            edgeId = null;
            edgeLabel = null;
            edgeEndVertices = null;
            edgeProps = null;
            inEdge = false;
          }

        }

        if (bufferCounter > bufferSize) {
          graph.commit();
          bufferCounter = 0;
        }
      }

      reader.close();

      graph.commit();

    } catch (Exception xse) {
      throw OException.wrapException(new ODatabaseImportException("Error on importing GraphML"), xse);
    }

    return this;
  }

  public int getVertexLabelIndex() {
    return vertexLabelIndex;
  }

  public void setVertexLabelIndex(int vertexLabelIndex) {
    this.vertexLabelIndex = vertexLabelIndex;
  }

  /**
   * @param vertexIdKey
   *          if the id of a vertex is a <data/> property, fetch it from the data property.
   */
  public void setVertexIdKey(String vertexIdKey) {
    this.vertexIdKey = vertexIdKey;
  }

  /**
   * @param edgeIdKey
   *          if the id of an edge is a <data/> property, fetch it from the data property.
   */
  public void setEdgeIdKey(String edgeIdKey) {
    this.edgeIdKey = edgeIdKey;
  }

  /**
   * @param edgeLabelKey
   *          if the label of an edge is a <data/> property, fetch it from the data property.
   */
  public void setEdgeLabelKey(String edgeLabelKey) {
    this.edgeLabelKey = edgeLabelKey;
  }

  /**
   * Input the GraphML stream data into the graph. In practice, usually the provided graph is empty.
   *
   * @param graphMLInputStream
   *          an InputStream of GraphML data
   * @throws IOException
   *           thrown when the GraphML data is not correctly formatted
   */
  public OGraphMLReader inputGraph(final InputStream graphMLInputStream) throws IOException {
    return inputGraph(this.graph, graphMLInputStream, batchSize, this.vertexIdKey, this.edgeIdKey, this.edgeLabelKey);
  }

  /**
   * Input the GraphML stream data into the graph. In practice, usually the provided graph is empty.
   *
   * @param filename
   *          name of a file containing GraphML data
   * @throws IOException
   *           thrown when the GraphML data is not correctly formatted
   */
  public OGraphMLReader inputGraph(final String filename) throws IOException {
    return inputGraph(this.graph, filename, batchSize, this.vertexIdKey, this.edgeIdKey, this.edgeLabelKey);
  }

  /**
   * Input the GraphML stream data into the graph. In practice, usually the provided graph is empty.
   *
   * @param graphMLInputStream
   *          an InputStream of GraphML data
   * @param bufferSize
   *          the amount of elements to hold in memory before committing a transactions (only valid for TransactionalGraphs)
   * @throws IOException
   *           thrown when the GraphML data is not correctly formatted
   */
  public OGraphMLReader inputGraph(final InputStream graphMLInputStream, int bufferSize) throws IOException {
    return inputGraph(this.graph, graphMLInputStream, bufferSize, this.vertexIdKey, this.edgeIdKey, this.edgeLabelKey);
  }

  /**
   * Input the GraphML stream data into the graph. In practice, usually the provided graph is empty.
   *
   * @param filename
   *          name of a file containing GraphML data
   * @param bufferSize
   *          the amount of elements to hold in memory before committing a transactions (only valid for TransactionalGraphs)
   * @throws IOException
   *           thrown when the GraphML data is not correctly formatted
   */
  public OGraphMLReader inputGraph(final String filename, final int bufferSize) throws IOException {
    return inputGraph(this.graph, filename, bufferSize, this.vertexIdKey, this.edgeIdKey, this.edgeLabelKey);
  }

  public boolean isStoreVertexIds() {
    return storeVertexIds;
  }

  public void setStoreVertexIds(final boolean storeVertexIds) {
    this.storeVertexIds = storeVertexIds;
  }

  public OGraphMLReader setOptions(final Map<String, List<String>> opts) {
    for (Map.Entry<String, List<String>> opt : opts.entrySet()) {
      if (opt.getKey().equalsIgnoreCase("storeVertexIds")) {
        storeVertexIds = Boolean.parseBoolean(opt.getValue().get(0));
      } else if (opt.getKey().equalsIgnoreCase("batchSize")) {
        batchSize = Integer.parseInt(opt.getValue().get(0));
      }
    }
    return this;
  }

  public int getBatchSize() {
    return batchSize;
  }

  public void setBatchSize(final int batchSize) {
    this.batchSize = batchSize;
  }

  protected void mapId(final Map<String, ORID> vertexMappedIdMap, final String vertexId, final ORID rid) {
    if (vertexMappedIdMap.containsKey(vertexId))
      throw new IllegalArgumentException("Vertex with id '" + vertexId + "' has been already loaded");
    vertexMappedIdMap.put(vertexId, rid);
  }

  private Object typeCastValue(final String key, final String value, final Map<String, String> keyTypes) {
    String type = keyTypes.get(key);
    if (null == type || type.equals(GraphMLTokens.STRING))
      return value;
    else if (type.equals(GraphMLTokens.FLOAT))
      return Float.valueOf(value);
    else if (type.equals(GraphMLTokens.INT))
      return Integer.valueOf(value);
    else if (type.equals(GraphMLTokens.DOUBLE))
      return Double.valueOf(value);
    else if (type.equals(GraphMLTokens.BOOLEAN))
      return Boolean.valueOf(value);
    else if (type.equals(GraphMLTokens.LONG))
      return Long.valueOf(value);
    else
      return value;
  }

  public OCommandOutputListener getOutput() {
    return output;
  }

  public OGraphMLReader setOutput(final OCommandOutputListener output) {
    this.output = output;
    return this;
  }

  protected void printStatus(final XMLStreamReader input, final long importedVertices, final long importedEdges) {
    if (output != null && (importedVertices + importedEdges) % 50000 == 0) {
      final long parsed = input.getTextStart();

      output.onMessage(String.format("Imported %d graph elements: %d vertices and %d edges. Parsed %s (uncompressed)",
          importedVertices + importedEdges, importedVertices, importedEdges, OFileUtils.getSizeAsString(parsed)));
    }
  }
}


/*
 *
 *  *  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.orientechnologies.common.serialization.types;

import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges;

import java.nio.ByteBuffer;
import java.util.Calendar;
import java.util.Date;

/**
 * Serializer for {@link Date} type, it serializes it without time part.
 *
 * @author Ilya Bershadskiy (ibersh20-at-gmail.com)
 * @since 20.01.12
 */
public class ODateSerializer implements OBinarySerializer<Date> {

  public static final byte      ID       = 4;
  public static final ODateSerializer INSTANCE = new ODateSerializer();

  public int getObjectSize(Date object, Object... hints) {
    return OLongSerializer.LONG_SIZE;
  }

  public void serialize(Date object, byte[] stream, int startPosition, Object... hints) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(object);
    calendar.set(Calendar.HOUR_OF_DAY, 0);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);
    ODateTimeSerializer dateTimeSerializer = ODateTimeSerializer.INSTANCE;
    dateTimeSerializer.serialize(calendar.getTime(), stream, startPosition);
  }

  public Date deserialize(byte[] stream, int startPosition) {
    ODateTimeSerializer dateTimeSerializer = ODateTimeSerializer.INSTANCE;
    return dateTimeSerializer.deserialize(stream, startPosition);
  }

  public int getObjectSize(byte[] stream, int startPosition) {
    return OLongSerializer.LONG_SIZE;
  }

  public byte getId() {
    return ID;
  }

  public int getObjectSizeNative(byte[] stream, int startPosition) {
    return OLongSerializer.LONG_SIZE;
  }

  public void serializeNativeObject(final Date object, byte[] stream, int startPosition, Object... hints) {
    final Calendar calendar = Calendar.getInstance();
    calendar.setTime(object);
    calendar.set(Calendar.HOUR_OF_DAY, 0);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);
    final ODateTimeSerializer dateTimeSerializer = ODateTimeSerializer.INSTANCE;
    dateTimeSerializer.serializeNativeObject(calendar.getTime(), stream, startPosition);
  }

  public Date deserializeNativeObject(byte[] stream, int startPosition) {
    ODateTimeSerializer dateTimeSerializer = ODateTimeSerializer.INSTANCE;
    return dateTimeSerializer.deserializeNativeObject(stream, startPosition);
  }

  public boolean isFixedLength() {
    return true;
  }

  public int getFixedLength() {
    return OLongSerializer.LONG_SIZE;
  }

  @Override
  public Date preprocess(Date value, Object... hints) {
    if(value==null){
      return null;
    }
    final Calendar calendar = Calendar.getInstance();
    calendar.setTime(value);
    calendar.set(Calendar.HOUR_OF_DAY, 0);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);

    return calendar.getTime();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void serializeInByteBufferObject(Date object, ByteBuffer buffer, Object... hints) {
    final Calendar calendar = Calendar.getInstance();
    calendar.setTime(object);
    calendar.set(Calendar.HOUR_OF_DAY, 0);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);
    final ODateTimeSerializer dateTimeSerializer = ODateTimeSerializer.INSTANCE;
    dateTimeSerializer.serializeInByteBufferObject(calendar.getTime(), buffer);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Date deserializeFromByteBufferObject(ByteBuffer buffer) {
    final ODateTimeSerializer dateTimeSerializer = ODateTimeSerializer.INSTANCE;
    return dateTimeSerializer.deserializeFromByteBufferObject(buffer);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getObjectSizeInByteBuffer(ByteBuffer buffer) {
    return OLongSerializer.LONG_SIZE;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Date deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) {
    final ODateTimeSerializer dateTimeSerializer = ODateTimeSerializer.INSTANCE;
    return dateTimeSerializer.deserializeFromByteBufferObject(buffer, walChanges, offset);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) {
    return OLongSerializer.LONG_SIZE;
  }
}


/*
 *
 *  *  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.orientechnologies.orient.core.tx;

import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ORecordOperation;
import com.orientechnologies.orient.core.exception.OTransactionException;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.index.OCompositeKey;
import com.orientechnologies.orient.core.index.OIndex;
import com.orientechnologies.orient.core.index.OIndexDefinition;
import com.orientechnologies.orient.core.index.OIndexManager;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.ORecordInternal;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.record.impl.ODocumentInternal;
import com.orientechnologies.orient.core.storage.OBasicTransaction;
import com.orientechnologies.orient.core.storage.OStorage;
import com.orientechnologies.orient.core.tx.OTransactionIndexChanges.OPERATION;
import com.orientechnologies.orient.core.tx.OTransactionIndexChangesPerKey.OTransactionIndexEntry;

import java.util.*;
import java.util.Map.Entry;

public abstract class OTransactionRealAbstract extends OTransactionAbstract implements OTransactionInternal {
  protected       Map<ORID, ORID>                                   updatedRids           = new HashMap<ORID, ORID>();
  protected       Map<ORID, ORecordOperation>                       allEntries            = new LinkedHashMap<ORID, ORecordOperation>();
  protected       Map<String, OTransactionIndexChanges>             indexEntries          = new LinkedHashMap<String, OTransactionIndexChanges>();
  protected       Map<ORID, List<OTransactionRecordIndexOperation>> recordIndexOperations = new HashMap<ORID, List<OTransactionRecordIndexOperation>>();
  protected       int                                               id;
  protected       int                                               newObjectCounter      = -2;
  protected       Map<String, Object>                               userData              = new HashMap<String, Object>();
  private         Map<ORID, LockedRecordMetadata>                   noTxLocks;
  /**
   * This set is used to track which documents are changed during tx, if documents are changed but not saved all changes are made
   * during tx will be undone.
   */
  protected final Set<ODocument>                                    changedDocuments      = new HashSet<ODocument>();

  protected OTransactionRealAbstract(ODatabaseDocumentInternal database, int id) {
    super(database);
    this.id = id;
  }

  @Override
  public void addChangedDocument(ODocument document) {
    if (getRecord(document.getIdentity()) == null) {
      changedDocuments.add(document);
    }
  }

  public void close() {
    super.close();

    for (final ORecordOperation recordOperation : getRecordOperations()) {
      final ORecord record = recordOperation.getRecord();
      if (record instanceof ODocument) {
        final ODocument document = (ODocument) record;

        if (document.isDirty()) {
          document.undo();
        }

        changedDocuments.remove(document);
      }
    }

    for (ODocument changedDocument : changedDocuments) {
      changedDocument.undo();
    }

    changedDocuments.clear();
    updatedRids.clear();
    allEntries.clear();
    indexEntries.clear();
    recordIndexOperations.clear();
    newObjectCounter = -2;
    status = TXSTATUS.INVALID;

    database.setDefaultTransactionMode(getNoTxLocks());

    userData.clear();
  }

  public int getId() {
    return id;
  }

  public void clearRecordEntries() {
  }

  public void restore() {
  }

  @Override
  public int getEntryCount() {
    return allEntries.size();
  }

  public Collection<ORecordOperation> getCurrentRecordEntries() {
    return allEntries.values();
  }

  public Collection<ORecordOperation> getRecordOperations() {
    return allEntries.values();
  }

  public ORecordOperation getRecordEntry(ORID rid) {
    ORecordOperation entry;
    do {
      entry = allEntries.get(rid);
      if (entry == null) {
        rid = updatedRids.get(rid);
      }
    } while (entry == null && rid != null);
    return entry;
  }

  public ORecord getRecord(final ORID rid) {
    final ORecordOperation e = getRecordEntry(rid);
    if (e != null)
      if (e.type == ORecordOperation.DELETED)
        return OBasicTransaction.DELETED_RECORD;
      else
        return e.getRecord();
    return null;
  }

  /**
   * Called by class iterator.
   */
  public List<ORecordOperation> getNewRecordEntriesByClass(final OClass iClass, final boolean iPolymorphic) {
    final List<ORecordOperation> result = new ArrayList<ORecordOperation>();

    if (iClass == null)
      // RETURN ALL THE RECORDS
      for (ORecordOperation entry : allEntries.values()) {
        if (entry.type == ORecordOperation.CREATED)
          result.add(entry);
      }
    else {
      // FILTER RECORDS BY CLASSNAME
      for (ORecordOperation entry : allEntries.values()) {
        if (entry.type == ORecordOperation.CREATED)
          if (entry.getRecord() != null && entry.getRecord() instanceof ODocument) {
            if (iPolymorphic) {
              if (iClass.isSuperClassOf(((ODocument) entry.getRecord()).getSchemaClass()))
                result.add(entry);
            } else if (iClass.getName().equals(((ODocument) entry.getRecord()).getClassName()))
              result.add(entry);
          }
      }
    }

    return result;
  }

  /**
   * Called by cluster iterator.
   */
  public List<ORecordOperation> getNewRecordEntriesByClusterIds(final int[] iIds) {
    final List<ORecordOperation> result = new ArrayList<ORecordOperation>();

    if (iIds == null)
      // RETURN ALL THE RECORDS
      for (ORecordOperation entry : allEntries.values()) {
        if (entry.type == ORecordOperation.CREATED)
          result.add(entry);
      }
    else
      // FILTER RECORDS BY ID
      for (ORecordOperation entry : allEntries.values()) {
        for (int id : iIds) {
          if (entry.getRecord() != null && entry.getRecord().getIdentity().getClusterId() == id
              && entry.type == ORecordOperation.CREATED) {
            result.add(entry);
            break;
          }
        }
      }

    return result;
  }

  public void clearIndexEntries() {
    indexEntries.clear();
    recordIndexOperations.clear();
  }

  public List<String> getInvolvedIndexes() {
    List<String> list = null;
    for (String indexName : indexEntries.keySet()) {
      if (list == null)
        list = new ArrayList<String>();
      list.add(indexName);
    }
    return list;
  }

  public ODocument getIndexChanges() {

    final ODocument result = new ODocument().setAllowChainedAccess(false).setTrackingChanges(false);

    for (Entry<String, OTransactionIndexChanges> indexEntry : indexEntries.entrySet()) {
      final ODocument indexDoc = new ODocument().setTrackingChanges(false);
      ODocumentInternal.addOwner(indexDoc, result);

      result.field(indexEntry.getKey(), indexDoc, OType.EMBEDDED);

      if (indexEntry.getValue().cleared)
        indexDoc.field("clear", Boolean.TRUE);

      final List<ODocument> entries = new ArrayList<ODocument>();
      indexDoc.field("entries", entries, OType.EMBEDDEDLIST);

      // STORE INDEX ENTRIES
      for (OTransactionIndexChangesPerKey entry : indexEntry.getValue().changesPerKey.values()) {
        if (!entry.clientTrackOnly)
          entries.add(serializeIndexChangeEntry(entry, indexDoc));
      }

      indexDoc.field("nullEntries", serializeIndexChangeEntry(indexEntry.getValue().nullKeyChanges, indexDoc));
    }

    indexEntries.clear();

    return result;
  }

  public Map<String, OTransactionIndexChanges> getIndexOperations() {
    return indexEntries;
  }

  /**
   * Bufferizes index changes to be flushed at commit time.
   *
   * @return
   */
  public OTransactionIndexChanges getIndexChanges(final String iIndexName) {
    return indexEntries.get(iIndexName);
  }

  public OTransactionIndexChanges getIndexChangesInternal(final String indexName) {
    if (getDatabase().getStorage().isRemote())
      return null;
    return getIndexChanges(indexName);
  }

  public void addIndexEntry(final OIndex<?> delegate, final String iIndexName, final OTransactionIndexChanges.OPERATION iOperation,
      final Object key, final OIdentifiable iValue) {
    addIndexEntry(delegate, iIndexName, iOperation, key, iValue, false);
  }

  /**
   * Bufferizes index changes to be flushed at commit time.
   */
  public void addIndexEntry(final OIndex<?> delegate, final String iIndexName, final OTransactionIndexChanges.OPERATION iOperation,
      final Object key, final OIdentifiable iValue, boolean clientTrackOnly) {
    OTransactionIndexChanges indexEntry = indexEntries.get(iIndexName);
    if (indexEntry == null) {
      indexEntry = new OTransactionIndexChanges();
      indexEntries.put(iIndexName, indexEntry);
    }

    if (iOperation == OPERATION.CLEAR)
      indexEntry.setCleared();
    else {
      OTransactionIndexChangesPerKey changes = indexEntry.getChangesPerKey(key);
      changes.clientTrackOnly = clientTrackOnly;
      changes.add(iValue, iOperation);

      if (iValue == null)
        return;

      List<OTransactionRecordIndexOperation> transactionIndexOperations = recordIndexOperations.get(iValue.getIdentity());

      if (transactionIndexOperations == null) {
        transactionIndexOperations = new ArrayList<OTransactionRecordIndexOperation>();
        recordIndexOperations.put(iValue.getIdentity().copy(), transactionIndexOperations);
      }

      transactionIndexOperations.add(new OTransactionRecordIndexOperation(iIndexName, key, iOperation));
    }
  }

  public void updateIdentityAfterCommit(final ORID oldRid, final ORID newRid) {
    if (oldRid.equals(newRid))
      // NO CHANGE, IGNORE IT
      return;

    // XXX: Identity update may mutate the index keys, so we have to identify and reinsert potentially affected index keys to keep
    // the OTransactionIndexChanges.changesPerKey in a consistent state.

    final List<KeyChangesUpdateRecord> keyRecordsToReinsert = new ArrayList<KeyChangesUpdateRecord>();
    final OIndexManager indexManager = getDatabase().getMetadata().getIndexManager();
    for (Entry<String, OTransactionIndexChanges> entry : indexEntries.entrySet()) {
      final OIndex<?> index = indexManager.getIndex(entry.getKey());
      if (index == null)
        throw new OTransactionException("Cannot find index '" + entry.getValue() + "' while committing transaction");

      final Dependency[] fieldRidDependencies = getIndexFieldRidDependencies(index);
      if (!isIndexMayDependOnRids(fieldRidDependencies))
        continue;

      final OTransactionIndexChanges indexChanges = entry.getValue();
      for (final Iterator<OTransactionIndexChangesPerKey> iterator = indexChanges.changesPerKey.values().iterator(); iterator
          .hasNext(); ) {
        final OTransactionIndexChangesPerKey keyChanges = iterator.next();
        if (isIndexKeyMayDependOnRid(keyChanges.key, oldRid, fieldRidDependencies)) {
          keyRecordsToReinsert.add(new KeyChangesUpdateRecord(keyChanges, indexChanges));
          iterator.remove();
        }
      }
    }

    // Update the identity.

    final ORecordOperation rec = getRecordEntry(oldRid);
    if (rec != null) {
      updatedRids.put(newRid.copy(), oldRid.copy());

      if (!rec.getRecord().getIdentity().equals(newRid)) {
        ORecordInternal.onBeforeIdentityChanged(rec.getRecord());

        final ORecordId recordId = (ORecordId) rec.getRecord().getIdentity();
        if (recordId == null) {
          ORecordInternal.setIdentity(rec.getRecord(), new ORecordId(newRid));
        } else {
          recordId.setClusterPosition(newRid.getClusterPosition());
          recordId.setClusterId(newRid.getClusterId());
        }

        ORecordInternal.onAfterIdentityChanged(rec.getRecord());
      }
    }

    // Reinsert the potentially affected index keys.

    for (KeyChangesUpdateRecord record : keyRecordsToReinsert)
      record.indexChanges.changesPerKey.put(record.keyChanges.key, record.keyChanges);

    // Update the indexes.

    ORecordOperation val = getRecordEntry(oldRid);
    final List<OTransactionRecordIndexOperation> transactionIndexOperations = recordIndexOperations
        .get(val != null ? val.getRID() : null);
    if (transactionIndexOperations != null) {
      for (final OTransactionRecordIndexOperation indexOperation : transactionIndexOperations) {
        OTransactionIndexChanges indexEntryChanges = indexEntries.get(indexOperation.index);
        if (indexEntryChanges == null)
          continue;
        final OTransactionIndexChangesPerKey keyChanges;
        if (indexOperation.key == null) {
          keyChanges = indexEntryChanges.nullKeyChanges;
        } else {
          keyChanges = indexEntryChanges.changesPerKey.get(indexOperation.key);
        }
        if (keyChanges != null)
          updateChangesIdentity(oldRid, newRid, keyChanges);
      }
    }
  }

  protected void checkTransaction() {
    if (status == TXSTATUS.INVALID)
      throw new OTransactionException("Invalid state of the transaction. The transaction must be begun.");
  }

  protected ODocument serializeIndexChangeEntry(OTransactionIndexChangesPerKey entry, final ODocument indexDoc) {
    // SERIALIZE KEY

    ODocument keyContainer = new ODocument();
    keyContainer.setTrackingChanges(false);

    if (entry.key != null) {
      if (entry.key instanceof OCompositeKey) {
        final List<Object> keys = ((OCompositeKey) entry.key).getKeys();

        keyContainer.field("key", keys, OType.EMBEDDEDLIST);
        keyContainer.field("binary", false);
      } else {
        keyContainer.field("key", entry.key);
        keyContainer.field("binary", false);
      }

    } else
      keyContainer = null;

    final List<ODocument> operations = new ArrayList<ODocument>();

    // SERIALIZE VALUES
    if (entry.entries != null && !entry.entries.isEmpty()) {
      for (OTransactionIndexEntry e : entry.entries) {

        final ODocument changeDoc = new ODocument().setAllowChainedAccess(false);
        ODocumentInternal.addOwner((ODocument) changeDoc, indexDoc);

        // SERIALIZE OPERATION
        changeDoc.field("o", e.operation.ordinal());

        if (e.value instanceof ORecord && e.value.getIdentity().isNew()) {
          final ORecord saved = getRecord(e.value.getIdentity());
          if (saved != null)
            e.value = saved;
          else
            ((ORecord) e.value).save();
        }

        changeDoc.field("v", e.value != null ? e.value.getIdentity() : null);

        operations.add(changeDoc);
      }
    }
    ODocument res = new ODocument();
    res.setTrackingChanges(false);
    ODocumentInternal.addOwner(res, indexDoc);
    return res.setAllowChainedAccess(false).field("k", keyContainer, OType.EMBEDDED).field("ops", operations, OType.EMBEDDEDLIST);
  }

  private void updateChangesIdentity(ORID oldRid, ORID newRid, OTransactionIndexChangesPerKey changesPerKey) {
    if (changesPerKey == null)
      return;

    for (final OTransactionIndexEntry indexEntry : changesPerKey.entries)
      if (indexEntry.value.getIdentity().equals(oldRid))
        indexEntry.value = newRid;
  }

  @Override
  public void setCustomData(String iName, Object iValue) {
    userData.put(iName, iValue);
  }

  @Override
  public Object getCustomData(String iName) {
    return userData.get(iName);
  }

  private static Dependency[] getIndexFieldRidDependencies(OIndex<?> index) {
    final OIndexDefinition definition = index.getDefinition();

    if (definition == null) // type for untyped index is still not resolved
      return null;

    final OType[] types = definition.getTypes();
    final Dependency[] dependencies = new Dependency[types.length];

    for (int i = 0; i < types.length; ++i)
      dependencies[i] = getTypeRidDependency(types[i]);

    return dependencies;
  }

  private static boolean isIndexMayDependOnRids(Dependency[] fieldDependencies) {
    if (fieldDependencies == null)
      return true;

    for (Dependency dependency : fieldDependencies)
      switch (dependency) {
      case Unknown:
        return true;
      case Yes:
        return true;
      case No:
        break; // do nothing
      }

    return false;
  }

  private static boolean isIndexKeyMayDependOnRid(Object key, ORID rid, Dependency[] keyDependencies) {
    if (key instanceof OCompositeKey) {
      final List<Object> subKeys = ((OCompositeKey) key).getKeys();
      for (int i = 0; i < subKeys.size(); ++i)
        if (isIndexKeyMayDependOnRid(subKeys.get(i), rid, keyDependencies == null ? null : keyDependencies[i]))
          return true;
      return false;
    }

    return isIndexKeyMayDependOnRid(key, rid, keyDependencies == null ? null : keyDependencies[0]);
  }

  private static boolean isIndexKeyMayDependOnRid(Object key, ORID rid, Dependency dependency) {
    if (dependency == Dependency.No)
      return false;

    if (key instanceof OIdentifiable)
      return key.equals(rid);

    return dependency == Dependency.Unknown || dependency == null;
  }

  private static Dependency getTypeRidDependency(OType type) {
    switch (type) {
    case CUSTOM:
    case ANY:
      return Dependency.Unknown;

    case EMBEDDED:
    case LINK:
      return Dependency.Yes;

    case LINKLIST:
    case LINKSET:
    case LINKMAP:
    case LINKBAG:
    case EMBEDDEDLIST:
    case EMBEDDEDSET:
    case EMBEDDEDMAP:
      assert false; // under normal conditions, collection field type is already resolved to its component type
      return Dependency.Unknown; // fallback to the safest variant, just in case

    default: // all other primitive types which doesn't depend on rids
      return Dependency.No;
    }
  }

  private enum Dependency {
    Unknown, Yes, No
  }

  private static class KeyChangesUpdateRecord {
    public final OTransactionIndexChangesPerKey keyChanges;
    public final OTransactionIndexChanges       indexChanges;

    public KeyChangesUpdateRecord(OTransactionIndexChangesPerKey keyChanges, OTransactionIndexChanges indexChanges) {
      this.keyChanges = keyChanges;
      this.indexChanges = indexChanges;
    }
  }

  public Map<ORID, ORID> getUpdatedRids() {
    return updatedRids;
  }

  public int getNewObjectCounter() {
    return newObjectCounter;
  }

  public void setNoTxLocks(Map<ORID, LockedRecordMetadata> noTxLocks) {
    this.noTxLocks = noTxLocks;
  }

  public Map<ORID, LockedRecordMetadata> getNoTxLocks() {
    return noTxLocks;
  }
}


/*
 *
 *  *  Copyright 2014 OrientDB LTD (info(at)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://www.orientdb.com
 *
 */
package com.orientechnologies.orient.core.record.impl;

import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ORecordElement;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.ORecordInternal;

import java.util.*;
import java.util.Map.Entry;

/**
 * @author Emanuele Tagliafetti
 */
public class ODirtyManager {

  private ODirtyManager                       overrider;
  private Map<ODocument, List<OIdentifiable>> references;
  private Set<ORecord>                        newRecords;
  private Set<ORecord>                        updateRecords;

  public void setDirty(ORecord record) {
    ODirtyManager real = getReal();
    if (record.getIdentity().isNew() && !record.getIdentity().isTemporary()) {
      if (real.newRecords == null)
        real.newRecords = Collections.newSetFromMap(new IdentityHashMap<ORecord, Boolean>());
      real.newRecords.add(record);
    } else {
      if (real.updateRecords == null)
        real.updateRecords = Collections.newSetFromMap(new IdentityHashMap<ORecord, Boolean>());
      real.updateRecords.add(record);
    }
  }

  public ODirtyManager getReal() {
    ODirtyManager real = this;
    while (real.overrider != null) {
      real = real.overrider;
    }
    if (this.overrider != null && this.overrider != real)
      this.overrider = real;
    return real;
  }

  public Set<ORecord> getNewRecords() {
    return getReal().newRecords;
  }

  public Set<ORecord> getUpdateRecords() {
    return getReal().updateRecords;
  }

  public Map<ODocument, List<OIdentifiable>> getReferences() {
    return getReal().references;
  }

  public boolean isSame(ODirtyManager other) {
    // other = other.getReal();
    // if (overrider != null)
    // return overrider.isSame(other);
    return this.getReal() == other.getReal();
  }

  public void merge(ODirtyManager toMerge) {
    if (isSame(toMerge))
      return;
    this.newRecords = mergeSet(this.newRecords, toMerge.getNewRecords());
    this.updateRecords = mergeSet(this.updateRecords, toMerge.getUpdateRecords());
    if (toMerge.getReferences() != null) {
      if (references == null)
        references = new IdentityHashMap<ODocument, List<OIdentifiable>>();
      for (Entry<ODocument, List<OIdentifiable>> entry : toMerge.getReferences().entrySet()) {
        List<OIdentifiable> refs = references.get(entry.getKey());
        if (refs == null)
          references.put(entry.getKey(), entry.getValue());
        else
          refs.addAll(entry.getValue());
      }
    }
    toMerge.override(this);
  }

  /**
   * Merge the two set try to use the optimum case
   *
   * @param target
   * @param source
   *
   * @return
   */
  private static Set<ORecord> mergeSet(Set<ORecord> target, Set<ORecord> source) {
    if (source != null) {
      if (target == null) {
        return source;
      } else {
        if (target.size() > source.size()) {
          target.addAll(source);
          return target;
        } else {
          source.addAll(target);
          return source;
        }
      }
    } else {
      return target;
    }
  }

  public void track(ORecord pointing, OIdentifiable pointed) {
    getReal().internalTrack(pointing, pointed);
  }

  public void unTrack(ORecord pointing, OIdentifiable pointed) {
    getReal().internalUnTrack(pointing, pointed);
  }

  private void internalUnTrack(ORecord pointing, OIdentifiable pointed) {
    if (references == null)
      return;

    if (pointed.getIdentity().isNew()) {
      List<OIdentifiable> refs = references.get(pointing);
      if (refs == null)
        return;
      if (!(pointed instanceof ODocument) || !((ODocument) pointed).isEmbedded()) {
        refs.remove(pointed);
      }
    }
  }

  private void internalTrack(ORecord pointing, OIdentifiable pointed) {
    if (pointing instanceof ODocument) {
      if (((ODocument) pointing).isEmbedded()) {

        ORecordElement ele = pointing.getOwner();
        while (!(ele instanceof ODocument) && ele != null && ele.getOwner() != null)
          ele = ele.getOwner();
        if (ele != null)
          pointing = (ORecord) ele;
      }
    }
    if (pointed.getIdentity().isNew()) {
      if (!(pointed instanceof ODocument) || !((ODocument) pointed).isEmbedded()) {
        if (references == null) {
          references = new IdentityHashMap<ODocument, List<OIdentifiable>>();
        }
        List<OIdentifiable> refs = references.get(pointing);
        if (refs == null) {
          refs = new ArrayList<OIdentifiable>();
          references.put((ODocument) pointing, refs);
        }
        refs.add(pointed);
      } else if (pointed instanceof ODocument) {
        List<OIdentifiable> point = ORecordInternal.getDirtyManager((ORecord) pointed).getPointed((ORecord) pointed);
        if (point != null && point.size() > 0) {
          if (references == null) {
            references = new IdentityHashMap<ODocument, List<OIdentifiable>>();
          }
          List<OIdentifiable> refs = references.get(pointing);
          if (refs == null) {
            refs = new ArrayList<OIdentifiable>();
            references.put((ODocument) pointing, refs);
          }
          for (OIdentifiable embPoint : point) {
            refs.add(embPoint);
          }
        }
      }
    }
    if (pointed instanceof ORecord) {
      ORecordInternal.setDirtyManager((ORecord) pointed, this);
    }
  }

  private void override(ODirtyManager oDirtyManager) {
    ODirtyManager real = getReal();
    oDirtyManager = oDirtyManager.getReal();
    if (real == oDirtyManager)
      return;
    real.overrider = oDirtyManager;
    real.newRecords = null;
    real.updateRecords = null;
    real.references = null;
  }

  public void clearForSave() {
    ODirtyManager real = getReal();
    real.newRecords = null;
    real.updateRecords = null;
  }

  public List<OIdentifiable> getPointed(ORecord rec) {
    ODirtyManager real = getReal();
    if (real.references == null)
      return null;
    return real.references.get(rec);
  }

  public void removeNew(ORecord record) {
    ODirtyManager real = getReal();
    if (real.newRecords != null)
      real.newRecords.remove(record);
  }

  public void removePointed(ORecord record) {
    ODirtyManager real = getReal();
    if (real.references != null) {
      real.references.remove(record);
      if (real.references.size() == 0)
        references = null;
    }
  }

  public void clear() {
    clearForSave();
    getReal().references = null;
  }
}


/*
 *
 *  *  Copyright 2016 OrientDB LTD (info(at)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://www.orientdb.com
 *
 */
package com.orientechnologies.orient.server.security.authenticator;

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.security.OSecurityManager;
import com.orientechnologies.orient.server.OServer;
import com.orientechnologies.orient.server.config.OServerConfigurationManager;
import com.orientechnologies.orient.server.config.OServerUserConfiguration;
import com.orientechnologies.orient.server.security.OSecurityAuthenticatorAbstract;

/**
 * Provides an OSecurityAuthenticator for the users listed in orientdb-server-config.xml.
 *
 * @author S. Colin Leister
 */
public class OServerConfigAuthenticator extends OSecurityAuthenticatorAbstract {
  // OSecurityComponent
  // Called once the Server is running.
  public void active() {
    OLogManager.instance().info(this, "OServerConfigAuthenticator is active");
  }

  // OSecurityAuthenticator
  // Returns the actual username if successful, null otherwise.
  public String authenticate(final String username, final String password) {
    String principal = null;

    try {
      if (getServerConfig() != null) {
        OServerUserConfiguration userCfg = null;

        // This will throw an IllegalArgumentException if username is null or empty.
        // However, a null or empty username is possible with some security implementations.
        if (username != null && !username.isEmpty())
          userCfg = getServerConfig().getUser(username);

        if (userCfg != null && userCfg.password != null) {
          if (OSecurityManager.instance().checkPassword(password, userCfg.password)) {
            principal = userCfg.name;
          }
        }
      } else {
        OLogManager.instance().error(this, "OServerConfigAuthenticator.authenticate() ServerConfig is null", null);
      }
    } catch (Exception ex) {
      OLogManager.instance().error(this, "OServerConfigAuthenticator.authenticate()", ex);
    }

    return principal;
  }

  // OSecurityAuthenticator
  public void config(final OServer oServer, final OServerConfigurationManager serverCfg, final ODocument jsonConfig) {
    super.config(oServer, serverCfg, jsonConfig);
  }

  // OSecurityAuthenticator
  public OServerUserConfiguration getUser(final String username) {
    OServerUserConfiguration userCfg = null;

    if (getServerConfig() != null) {
      userCfg = getServerConfig().getUser(username);
    }

    return userCfg;
  }

  // OSecurityAuthenticator
  // If not supported by the authenticator, return false.
  public boolean isAuthorized(final String username, final String resource) {
    if (username == null || resource == null)
      return false;

    if (getServerConfig() != null) {
      // getUser() will throw an IllegalArgumentException if username is null or empty.
      // However, a null or empty username is possible with some security implementations.
      if (!username.isEmpty()) {
        OServerUserConfiguration userCfg = getServerConfig().getUser(username);

        if (userCfg != null) {
          // Total Access
          if (userCfg.resources.equals("*"))
            return true;

          String[] resourceParts = userCfg.resources.split(",");

          for (String r : resourceParts) {
            if (r.equalsIgnoreCase(resource))
              return true;
          }
        }
      }
    } else {
      OLogManager.instance().error(this, "OServerConfigAuthenticator.isAuthorized() ServerConfig is null", null);
    }

    return false;
  }

  // Server configuration users are never case sensitive.
  @Override
  protected boolean isCaseSensitive() {
    return false;
  }
}