/******************************************************************************
 * Copyright (c) 2012-2013, Pedro Ramalhete, Andreia Correia
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Concurrency Freaks nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ******************************************************************************
 */
package com.orientechnologies.common.concur.lock;

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.StampedLock;

/**
 *
 Scalable Read-Write Lock

 * A Read-Write Lock that is scalable with the number of threads doing Read.
 * Uses a two-state-machine for the Readers, and averages two synchronized
 * operations.

 * Although this mechanism was independently designed and implemented by the
 * authors, the idea is very similar to the algorithm C-RW-WP described in this
 * paper: NUMA-Aware Reader-Writer locks
 *

 * Relative to the paper, there are two differences: The threads have no
 * particular order, which means this implementation is not NUMA-aware;
 * Threads attempting a read-lock for the first time are added to a list and
 * removed when the thread terminates, following the mechanism described below.
 * To manage the adding and removal of new Reader threads, we use a
 * ConcurrentLinkedQueue instance named {@code readersStateList} containing all
 * the references to ReadersEntry (Reader's states), which the Writer scans to
 * determine if the Readers have completed or not.
 * After a thread terminates, the {@code finalize()} of the associated
 * {@code ReaderEntry} instance will be called, which will remove the
 * Reader's state reference from the {@code readersStateList}, to avoid memory leaking.
 * Advantages:

 *
 Implements {@code java.util.concurrent.locks.ReadWriteLock}
 *
 When there are very few Writes, the performance scales with the
 * number of Reader threads
 *
 No need to call initialization/cleanup functions per thread
 *
 No limitation on the number of concurrent threads
 *

 * Disadvantages:

 *
 Not Reentrant
 *
 Has Writer-Preference
 *
 Memory footprint increases with number of threads by sizeof(ReadersEntry) x O(N_threads)
 *
 Does not support {@code lockInterruptibly()}
 *
 Does not support {@code newCondition()}
 *

 * For scenarios with few writes, the average case for {@code sharedLock()} is
 * two synchronized calls: an {@code AtomicInteger.set()} on a cache line that
 * is held in exclusive mode by the core where the current thread is running,
 * and an {@code AtomicLong.get()} on a shared cache line.

 * This means that when doing several sequential calls of sharedLock()/unlock()
 * on the same instance, the performance penalty will be small because the
 * accessed variables will most likely be in L1/L2 cache.
 *
 * @author Pedro Ramalhete
 * @author Andreia Correia
 */
public class ScalableRWLock implements ReadWriteLock, java.io.Serializable {

  private static final long serialVersionUID = -7552055681918630764L;

  // Definition of an invalid thread-id (must be negative)
  private final static int             SRWL_STATE_NOT_WRITING = 0;
  private final static int             SRWL_STATE_WRITING     = 1;
  private final static int             SRWL_STATE_NOT_READING = 0;
  private final static int             SRWL_STATE_READING     = 1;
  private final static AtomicInteger[] dummyArray             = new AtomicInteger[0];

  /**
   * List of Reader's states that the Writer will scan when attempting to
   * acquire the lock in write-mode
   */
  private transient final ConcurrentLinkedQueue<AtomicInteger> readersStateList;

  /**
   * The thread-id of the Writer currently holding the lock in write-mode, or
   * SRWL_INVALID_TID if there is no Writer holding or attempting to acquire
   * the lock in write mode.
   */
  private transient final StampedLock stampedLock;

  /**
   * Thread-local reference to the current thread's ReadersEntry instance.
   * It's from this instance that the current Reader thread is able
   * to determine where to store its own state, and the number of reentrant
   * read lock loops for that particular thread.
   */
  private transient final ThreadLocal<ReadersEntry> entry;

  private transient final AtomicReference<AtomicInteger[]> readersStateArrayRef;

  /**
   * The lock returned by method {@link ScalableRWLock#readLock}.
   */
  private final InnerReadLock readerLock;

  /**
   * The lock returned by method {@link ScalableRWLock#writeLock}.
   */
  private final InnerWriteLock writerLock;

  /**
   * Inner class that makes use of finalize() to remove the Reader's state
   * from the ConcurrentLinkedQueue {@code readersStateList}
   */
  final class ReadersEntry {
    public final AtomicInteger state;

    public ReadersEntry(AtomicInteger state) {
      this.state = state;
    }

    protected void finalize() throws Throwable {
      removeState(state);
      super.finalize();
    }
  }

  /**
   * Read-only lock
   */
  final class InnerReadLock implements Lock {
    public void lock() {
      sharedLock();
    }

    public void unlock() {
      sharedUnlock();
    }

    public boolean tryLock() {
      return sharedTryLock();
    }

    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
      if (Thread.interrupted())
        throw new InterruptedException();
      return sharedTryLockNanos(unit.toNanos(timeout));
    }

    public void lockInterruptibly() throws InterruptedException {
      // Not supported
      throw new UnsupportedOperationException();
    }

    public Condition newCondition() {
      // Not supported
      throw new UnsupportedOperationException();
    }
  }

  /**
   * Write-only lock
   */
  final class InnerWriteLock implements Lock {
    public void lock() {
      exclusiveLock();
    }

    public void unlock() {
      exclusiveUnlock();
    }

    public boolean tryLock() {
      return exclusiveTryLock();
    }

    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
      if (Thread.interrupted())
        throw new InterruptedException();
      return exclusiveTryLockNanos(unit.toNanos(timeout));
    }

    public void lockInterruptibly() throws InterruptedException {
      // Not supported
      throw new UnsupportedOperationException();
    }

    public Condition newCondition() {
      // Not supported
      throw new UnsupportedOperationException();
    }
  }

  /**
   * Default constructor
   */
  public ScalableRWLock() {
    // States of the Readers, one entry in the list per thread
    readersStateList = new ConcurrentLinkedQueue<AtomicInteger>();

    stampedLock = new StampedLock();

    // Default value for "entry" is null which is ok because, the thread
    // calling the constructor may never attempt to read-lock this
    // instance and, therefore, there is not point in allocating an
    // instance of ReadersEntry for it.
    entry = new ThreadLocal<ReadersEntry>();

    readersStateArrayRef = new AtomicReference<AtomicInteger[]>(null);

    readerLock = new ScalableRWLock.InnerReadLock();
    writerLock = new ScalableRWLock.InnerWriteLock();
  }

  public Lock readLock() {
    return readerLock;
  }

  public Lock writeLock() {
    return writerLock;
  }

  /**
   * This function should be called only from ReadersEntry.finalize()
   *
   * @param state The reader's state that we wish to remove from the ConcurrentLinkedQueue
   */
  protected void removeState(AtomicInteger state) {
    readersStateList.remove(state);
    readersStateArrayRef.set(null);
    // Paranoia: just in case someone forgot to call sharedUnlock()
    // and there is a Writer waiting on that state
    state.set(SRWL_STATE_NOT_READING);
  }

  /**
   * Creates a new ReadersEntry instance for the current thread and
   * its associated AtomicInteger to store the state of the Reader
   *
   * @return Returns a reference to the newly created instance of
   * {@code ReadersEntry}
   */
  private ReadersEntry addState() {
    final AtomicInteger state = new AtomicInteger(SRWL_STATE_NOT_READING);
    final ReadersEntry newEntry = new ReadersEntry(state);
    entry.set(newEntry);
    readersStateList.add(state);
    readersStateArrayRef.set(null);
    return newEntry;
  }

  /**
   * Acquires the read lock.
   *
Acquires the read lock if the write lock is not held by
   * another thread and returns immediately.
   *
If the write lock is held by another thread then
   * the current thread yields until the write lock is released.
   */
  public void sharedLock() {
    ReadersEntry localEntry = entry.get();
    // Initialize a new Reader-state for this thread if needed
    if (localEntry == null) {
      localEntry = addState();
    }

    final AtomicInteger currentReadersState = localEntry.state;
    // The "optimistic" code path takes only two synchronized calls:
    // a set() on a cache line that should be held in exclusive mode
    // by the current thread, and a get() on a cache line that is shared.
    while (true) {
      currentReadersState.set(SRWL_STATE_READING);
      if (!stampedLock.isWriteLocked()) {
        // Acquired lock in read-only mode
        return;
      } else {
        // Go back to SRWL_STATE_NOT_READING to avoid blocking a Writer
        currentReadersState.set(SRWL_STATE_NOT_READING);
        // Some (other) thread is holding the write-lock, we must wait
        while (stampedLock.isWriteLocked()) {
          Thread.yield();
        }
      }
    }
  }

  /**
   * Attempts to release the read lock.
   *
If the current thread is the holder of this lock then
   * the {@code reentrantReaderCount} is decremented. If the
   * {@code reentrantReaderCount} is now zero then the lock is released.
   * If the current thread is not the holder of this lock then {@link
   * IllegalMonitorStateException} is thrown.
   *
   * @throws IllegalMonitorStateException if the current thread does not
   *                                      hold this lock.
   */
  public void sharedUnlock() {
    final ReadersEntry localEntry = entry.get();
    if (localEntry == null) {
      // ERROR: Tried to unlock a non read-locked lock
      throw new IllegalMonitorStateException();
    } else {
      localEntry.state.set(SRWL_STATE_NOT_READING);
      return;
    }
  }

  /**
   * Acquires the write lock.
   *
Acquires the write lock if neither the read nor write lock
   * are held by another thread and returns immediately, setting
   * the write lock {@code reentrantWriterCount} to one.
   *
If the current thread already holds the write lock then the
   * {@code reentrantWriterCount} is incremented by one and the method
   * returns immediately.
   *
If the lock is held by another thread, then the current
   * thread yields and lies dormant until the write lock has been acquired,
   * at which time the {@code reentrantWriterCount} is set to one.
   */
  public void exclusiveLock() {
    // Try to acquire the lock in write-mode
    stampedLock.writeLock();

    // We can only do this after writerOwner has been set to the current thread
    AtomicInteger[] localReadersStateArray = readersStateArrayRef.get();
    if (localReadersStateArray == null) {
      // Set to dummyArray before scanning the readersStateList to impose
      // a linearizability condition
      readersStateArrayRef.set(dummyArray);
      // Copy readersStateList to an array
      localReadersStateArray = readersStateList.toArray(new AtomicInteger[readersStateList.size()]);
      readersStateArrayRef.compareAndSet(dummyArray, localReadersStateArray);
    }

    // Scan the array of Reader states
    for (AtomicInteger readerState : localReadersStateArray) {
      while (readerState != null && readerState.get() == SRWL_STATE_READING) {
        Thread.yield();
      }
    }
  }

  /**
   * Attempts to release the write lock.
   *
If the current thread is the holder of this lock then
   * the {@code reentrantWriterCount} is decremented. If {@code
   * reentrantWriterCount} is now zero then the lock is released.
   * If the current thread is not the holder of this lock then {@link
   * IllegalMonitorStateException} is thrown.
   *
   * @throws IllegalMonitorStateException if the current thread does not
   *                                      hold this lock.
   */
  public void exclusiveUnlock() {
    if (!stampedLock.isWriteLocked()) {
      // ERROR: tried to unlock a non write-locked instance
      throw new IllegalMonitorStateException();
    }

    stampedLock.asWriteLock().unlock();
  }

  /**
   * Acquires the read lock only if the write lock is not held by
   * another thread at the time of invocation.
   *
Acquires the read lock if the write lock is not held by
   * another thread and returns immediately with the value
   * {@code true}.
   *
If the write lock is held by another thread then
   * this method will return immediately with the value
   * {@code false}.
   *
   * @return {@code true} if the read lock was acquired
   */
  public boolean sharedTryLock() {
    ReadersEntry localEntry = entry.get();
    // Initialize a new Reader-state for this thread if needed
    if (localEntry == null) {
      localEntry = addState();
    }

    final AtomicInteger currentReadersState = localEntry.state;
    currentReadersState.set(SRWL_STATE_READING);
    if (!stampedLock.isWriteLocked()) {
      // Acquired lock in read-only mode
      return true;
    } else {
      // Go back to SRWL_STATE_NOT_READING and quit
      currentReadersState.set(SRWL_STATE_NOT_READING);
      return false;
    }
  }

  /**
   * Acquires the read lock if the write lock is not held by
   * another thread within the given waiting time.
   *
Acquires the read lock if the write lock is not held by
   * another thread and returns immediately with the value
   * {@code true}.
   *
If the write lock is held by another thread then the
   * current thread yields execution until one of two things happens:
   *

   *
The read lock is acquired by the current thread; or
   *
The specified waiting time elapses.
   *

   *
If the read lock is acquired then the value {@code true} is
   * returned.
   *
   * @param nanosTimeout the time to wait for the read lock in nanoseconds
   *
   * @return {@code true} if the read lock was acquired
   */
  public boolean sharedTryLockNanos(long nanosTimeout) {
    final long lastTime = System.nanoTime();
    ReadersEntry localEntry = entry.get();
    // Initialize a new Reader-state for this thread if needed
    if (localEntry == null) {
      localEntry = addState();
    }

    final AtomicInteger currentReadersState = localEntry.state;
    while (true) {
      currentReadersState.set(SRWL_STATE_READING);
      if (!stampedLock.isWriteLocked()) {
        // Acquired lock in read-only mode
        return true;
      } else {
        // Go back to SRWL_STATE_NOT_READING to avoid blocking a Writer
        // and then check if this is a downgrade.
        currentReadersState.set(SRWL_STATE_NOT_READING);

        if (nanosTimeout <= 0)
          return false;
        if (System.nanoTime() - lastTime < nanosTimeout) {
          Thread.yield();
        } else {
          return false;
        }
      }
    }
  }

  /**
   * Acquires the write lock only if it is not held by another thread
   * at the time of invocation.
   *
Acquires the write lock if the write lock is not
   * held by another thread and returns immediately with
   * the value {@code true} if and only if no other thread is attempting a
   * read lock, setting the write lock {@code writerLoop}
   * count to one.
   *
 If the current thread already holds this lock then the
   * {@code reentrantWriterCount} count is incremented by one and the method returns
   * {@code true}.
   *
If the write lock is held by another thread then this method
   * will return immediately with the value {@code false}.
   *
   * @return {@code true} if the write lock was free and was acquired
   * by the current thread, or the write lock was already held
   * by the current thread; and {@code false} otherwise.
   */
  public boolean exclusiveTryLock() {
    // Try to acquire the lock in write-mode
    if (stampedLock.tryWriteLock() == 0) {
      return false;
    }

    // We can only do this after writerOwner has been set to the current thread
    AtomicInteger[] localReadersStateArray = readersStateArrayRef.get();
    if (localReadersStateArray == null) {
      // Set to dummyArray before scanning the readersStateList to impose
      // a linearizability condition
      readersStateArrayRef.set(dummyArray);
      // Copy readersStateList to an array
      localReadersStateArray = readersStateList.toArray(new AtomicInteger[readersStateList.size()]);
      readersStateArrayRef.compareAndSet(dummyArray, localReadersStateArray);
    }

    // Scan the array of Reader states
    for (AtomicInteger readerState : localReadersStateArray) {
      if (readerState != null && readerState.get() == SRWL_STATE_READING) {
        // There is at least one ongoing Reader so give up
        stampedLock.asWriteLock().unlock();
        return false;
      }
    }

    return true;
  }

  /**
   * Acquires the write lock if it is not held by another thread
   * within the given waiting time.
   *
Acquires the write lock if the write lock is not
   * held by another thread and returns immediately with
   * the value {@code true} if and only if no other thread is attempting a
   * read lock, setting the write lock {@code reentrantWriterCount}
   * to one. If another thread is attempting a read lock, this
   * function may yield until the read lock is released.
   *
If the current thread already holds this lock then the
   * {@code reentrantWriterCount} is incremented by one and the method returns
   * {@code true}.
   *
If the write lock is held by another thread then the current
   * thread yields and lies dormant until one of two things happens:
   *

   *
The write lock is acquired by the current thread; or
   *
The specified waiting time elapses
   *

   *
If the write lock is acquired then the value {@code true} is
   * returned and the write lock {@code reentrantWriterCount} is set to one.
   *
   * @param nanosTimeout the time to wait for the write lock in nanoseconds
   *
   * @return {@code true} if the lock was free and was acquired
   * by the current thread, or the write lock was already held by the
   * current thread; and {@code false} if the waiting time
   * elapsed before the lock could be acquired.
   */
  public boolean exclusiveTryLockNanos(long nanosTimeout) throws InterruptedException {
    final long lastTime = System.nanoTime();
    // Try to acquire the lock in write-mode
    if (stampedLock.tryWriteLock(nanosTimeout, TimeUnit.NANOSECONDS) == 0) {
      return false;
    }

    // We can only do this after writerOwner has been set to the current thread
    AtomicInteger[] localReadersStateArray = readersStateArrayRef.get();
    if (localReadersStateArray == null) {
      // Set to dummyArray before scanning the readersStateList to impose
      // a linearizability condition
      readersStateArrayRef.set(dummyArray);
      // Copy readersStateList to an array
      localReadersStateArray = readersStateList.toArray(new AtomicInteger[readersStateList.size()]);
      readersStateArrayRef.compareAndSet(dummyArray, localReadersStateArray);
    }

    // Scan the array of Reader states
    for (AtomicInteger readerState : localReadersStateArray) {
      while (readerState != null && readerState.get() == SRWL_STATE_READING) {
        if (System.nanoTime() - lastTime < nanosTimeout) {
          Thread.yield();
        } else {
          // Time has expired and there is at least one ongoing Reader so give up
          stampedLock.asWriteLock().unlock();
          return false;
        }
      }
    }

    return true;
  }

}


/*
 *
 *  *  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.metadata.security;

import com.orientechnologies.common.concur.resource.OCloseable;
import com.orientechnologies.orient.core.command.OCommandRequest;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.OScenarioThreadLocal;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.db.record.OClassTrigger;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ORecordLazySet;
import com.orientechnologies.orient.core.exception.OSecurityAccessException;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.index.ONullOutputListener;
import com.orientechnologies.orient.core.metadata.OMetadataDefault;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OClass.INDEX_TYPE;
import com.orientechnologies.orient.core.metadata.schema.OClassImpl;
import com.orientechnologies.orient.core.metadata.schema.OProperty;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.metadata.security.OSecurityUser.STATUSES;
import com.orientechnologies.orient.core.metadata.sequence.OSequence;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.executor.OResultSet;
import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery;
import com.orientechnologies.orient.core.storage.OStorageProxy;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Shared security class. It's shared by all the database instances that point to the same storage.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 */
public class OSecurityShared implements OSecurity, OCloseable {
  private final AtomicLong version = new AtomicLong();

  public static final String RESTRICTED_CLASSNAME = "ORestricted";
  public static final String IDENTITY_CLASSNAME   = "OIdentity";

  /**
   * Uses the ORestrictedOperation ENUM instead.
   */
  @Deprecated
  public static final String ALLOW_ALL_FIELD = ORestrictedOperation.ALLOW_ALL.getFieldName();

  /**
   * Uses the ORestrictedOperation ENUM instead.
   */
  @Deprecated
  public static final String ALLOW_READ_FIELD = ORestrictedOperation.ALLOW_READ.getFieldName();

  /**
   * Uses the ORestrictedOperation ENUM instead.
   */
  @Deprecated
  public static final String ALLOW_UPDATE_FIELD = ORestrictedOperation.ALLOW_UPDATE.getFieldName();

  /**
   * Uses the ORestrictedOperation ENUM instead.
   */
  @Deprecated
  public static final String ALLOW_DELETE_FIELD = ORestrictedOperation.ALLOW_DELETE.getFieldName();

  public static final String ONCREATE_IDENTITY_TYPE = "onCreate.identityType";
  public static final String ONCREATE_FIELD         = "onCreate.fields";

  public static final Set<String> ALLOW_FIELDS = Collections.unmodifiableSet(new HashSet<String>() {
    {
      add(ORestrictedOperation.ALLOW_ALL.getFieldName());
      add(ORestrictedOperation.ALLOW_READ.getFieldName());
      add(ORestrictedOperation.ALLOW_UPDATE.getFieldName());
      add(ORestrictedOperation.ALLOW_DELETE.getFieldName());
    }
  });

  public OSecurityShared() {
  }

  @Override
  public OIdentifiable allowRole(final ODocument iDocument, final ORestrictedOperation iOperation, final String iRoleName) {
    final ORID role = getRoleRID(iRoleName);
    if (role == null)
      throw new IllegalArgumentException("Role '" + iRoleName + "' not found");

    return allowIdentity(iDocument, iOperation.getFieldName(), role);
  }

  @Override
  public OIdentifiable allowUser(final ODocument iDocument, final ORestrictedOperation iOperation, final String iUserName) {
    final ORID user = getUserRID(iUserName);
    if (user == null)
      throw new IllegalArgumentException("User '" + iUserName + "' not found");

    return allowIdentity(iDocument, iOperation.getFieldName(), user);
  }

  @Override
  public OIdentifiable allowUser(final ODocument iDocument, final String iAllowFieldName, final String iUserName) {
    final ORID user = getUserRID(iUserName);
    if (user == null)
      throw new IllegalArgumentException("User '" + iUserName + "' not found");

    return allowIdentity(iDocument, iAllowFieldName, user);
  }

  @Override
  public OIdentifiable allowRole(final ODocument iDocument, final String iAllowFieldName, final String iRoleName) {
    final ORID role = getRoleRID(iRoleName);
    if (role == null)
      throw new IllegalArgumentException("Role '" + iRoleName + "' not found");

    return allowIdentity(iDocument, iAllowFieldName, role);
  }

  public OIdentifiable allowIdentity(final ODocument iDocument, final String iAllowFieldName, final OIdentifiable iId) {
    Set<OIdentifiable> field = iDocument.field(iAllowFieldName);
    if (field == null) {
      field = new ORecordLazySet(iDocument);
      iDocument.field(iAllowFieldName, field);
    }
    field.add(iId);

    return iId;
  }

  @Override
  public OIdentifiable denyUser(final ODocument iDocument, final ORestrictedOperation iOperation, final String iUserName) {
    final ORID user = getUserRID(iUserName);
    if (user == null)
      throw new IllegalArgumentException("User '" + iUserName + "' not found");

    return disallowIdentity(iDocument, iOperation.getFieldName(), user);
  }

  @Override
  public OIdentifiable denyRole(final ODocument iDocument, final ORestrictedOperation iOperation, final String iRoleName) {
    final ORID role = getRoleRID(iRoleName);
    if (role == null)
      throw new IllegalArgumentException("Role '" + iRoleName + "' not found");

    return disallowIdentity(iDocument, iOperation.getFieldName(), role);
  }

  @Override
  public OIdentifiable disallowUser(final ODocument iDocument, final String iAllowFieldName, final String iUserName) {
    final ORID user = getUserRID(iUserName);
    if (user == null)
      throw new IllegalArgumentException("User '" + iUserName + "' not found");

    return disallowIdentity(iDocument, iAllowFieldName, user);
  }

  @Override
  public OIdentifiable disallowRole(final ODocument iDocument, final String iAllowFieldName, final String iRoleName) {
    final ORID role = getRoleRID(iRoleName);
    if (role == null)
      throw new IllegalArgumentException("Role '" + iRoleName + "' not found");

    return disallowIdentity(iDocument, iAllowFieldName, role);
  }

  public OIdentifiable disallowIdentity(final ODocument iDocument, final String iAllowFieldName, final OIdentifiable iId) {
    Set<OIdentifiable> field = iDocument.field(iAllowFieldName);
    if (field != null)
      field.remove(iId);
    return iId;
  }

  @Override
  public boolean isAllowed(final Set<OIdentifiable> iAllowAll, final Set<OIdentifiable> iAllowOperation) {
    if ((iAllowAll == null || iAllowAll.isEmpty()) && (iAllowOperation == null || iAllowOperation.isEmpty()))
      // NO AUTHORIZATION: CAN'T ACCESS
      return false;

    final OSecurityUser currentUser = ODatabaseRecordThreadLocal.instance().get().getUser();
    if (currentUser != null) {
      // CHECK IF CURRENT USER IS ENLISTED
      if (iAllowAll == null || (iAllowAll != null && !iAllowAll.contains(currentUser.getIdentity()))) {
        // CHECK AGAINST SPECIFIC _ALLOW OPERATION
        if (iAllowOperation != null && iAllowOperation.contains(currentUser.getIdentity()))
          return true;

        // CHECK IF AT LEAST ONE OF THE USER'S ROLES IS ENLISTED
        for (OSecurityRole r : currentUser.getRoles()) {
          // CHECK AGAINST GENERIC _ALLOW
          if (iAllowAll != null && iAllowAll.contains(r.getIdentity()))
            return true;
          // CHECK AGAINST SPECIFIC _ALLOW OPERATION
          if (iAllowOperation != null && iAllowOperation.contains(r.getIdentity()))
            return true;
          // CHECK inherited permissions from parent roles, fixes #1980: Record Level Security: permissions don't follow role's
          // inheritance
          OSecurityRole parentRole = r.getParentRole();
          while (parentRole != null) {
            if (iAllowAll != null && iAllowAll.contains(parentRole.getIdentity()))
              return true;
            if (iAllowOperation != null && iAllowOperation.contains(parentRole.getIdentity()))
              return true;
            parentRole = parentRole.getParentRole();
          }
        }
        return false;
      }
    }
    return true;
  }

  public OUser authenticate(final String iUserName, final String iUserPassword) {
    final String dbName = getDatabase().getName();
    final OUser user = getUser(iUserName);
    if (user == null)
      throw new OSecurityAccessException(dbName, "User or password not valid for database: '" + dbName + "'");

    if (user.getAccountStatus() != OSecurityUser.STATUSES.ACTIVE)
      throw new OSecurityAccessException(dbName, "User '" + iUserName + "' is not active");

    if (!(getDatabase().getStorage() instanceof OStorageProxy)) {
      // CHECK USER & PASSWORD
      if (!user.checkPassword(iUserPassword)) {
        // WAIT A BIT TO AVOID BRUTE FORCE
        try {
          Thread.sleep(200);
        } catch (InterruptedException ignore) {
          Thread.currentThread().interrupt();
        }
        throw new OSecurityAccessException(dbName, "User or password not valid for database: '" + dbName + "'");
      }
    }

    return user;
  }

  // Token MUST be validated before being passed to this method.
  public OUser authenticate(final OToken authToken) {
    final String dbName = getDatabase().getName();
    if (authToken.getIsValid() != true) {
      throw new OSecurityAccessException(dbName, "Token not valid");
    }

    OUser user = authToken.getUser(getDatabase());
    if (user == null && authToken.getUserName() != null) {
      // Token handler may not support returning an OUser so let's get username (subject) and query:
      user = getUser(authToken.getUserName());
    }

    if (user == null) {
      throw new OSecurityAccessException(dbName, "Authentication failed, could not load user from token");
    }
    if (user.getAccountStatus() != STATUSES.ACTIVE)
      throw new OSecurityAccessException(dbName, "User '" + user.getName() + "' is not active");

    return user;
  }

  public OUser getUser(final ORID iRecordId) {
    if (iRecordId == null)
      return null;

    ODocument result;
    result = getDatabase().load(iRecordId, "roles:1");
    if (!result.getClassName().equals(OUser.CLASS_NAME)) {
      result = null;
    }
    return new OUser(result);
  }

  public OUser createUser(final String iUserName, final String iUserPassword, final String... iRoles) {
    final OUser user = new OUser(iUserName, iUserPassword);

    if (iRoles != null)
      for (String r : iRoles) {
        user.addRole(r);
      }

    return user.save();
  }

  public OUser createUser(final String userName, final String userPassword, final ORole... roles) {
    final OUser user = new OUser(userName, userPassword);

    if (roles != null)
      for (ORole r : roles) {
        user.addRole(r);
      }

    return user.save();
  }

  public boolean dropUser(final String iUserName) {
    final Number removed = getDatabase().command("delete from OUser where name = ?", iUserName).next().getProperty("count");

    return removed != null && removed.intValue() > 0;
  }

  public ORole getRole(final OIdentifiable iRole) {
    final ODocument doc = iRole.getRecord();
    if (doc != null) {
      OClass clazz = doc.getSchemaClass();
      if (clazz != null && clazz.isSubClassOf("ORole")) {
        return new ORole(doc);
      }
    }

    return null;
  }

  public ORole getRole(final String iRoleName) {
    if (iRoleName == null)
      return null;

    final List<ODocument> result = getDatabase().<OCommandRequest>command(
        new OSQLSynchQuery<ODocument>("select from ORole where name = ? limit 1")).execute(iRoleName);

    if (result != null && !result.isEmpty())
      return new ORole(result.get(0));

    return null;
  }

  public ORID getRoleRID(final String iRoleName) {
    if (iRoleName == null)
      return null;

    final List<ODocument> result = getDatabase().<OCommandRequest>command(
        new OSQLSynchQuery<ODocument>("select rid from index:ORole.name where key = ? limit 1")).execute(iRoleName);

    if (result != null && !result.isEmpty())
      return result.get(0).rawField("rid");

    return null;
  }

  public ORole createRole(final String iRoleName, final ORole.ALLOW_MODES iAllowMode) {
    return createRole(iRoleName, null, iAllowMode);
  }

  public ORole createRole(final String iRoleName, final ORole iParent, final ORole.ALLOW_MODES iAllowMode) {
    final ORole role = new ORole(iRoleName, iParent, iAllowMode);
    return role.save();
  }

  public boolean dropRole(final String iRoleName) {
    final Number removed = getDatabase().command("delete from ORole where name = '" + iRoleName + "'").next().getProperty("count");

    return removed != null && removed.intValue() > 0;
  }

  public List<ODocument> getAllUsers() {
    return getDatabase().<OCommandRequest>command(new OSQLSynchQuery<ODocument>("select from OUser")).execute();
  }

  public List<ODocument> getAllRoles() {
    return getDatabase().<OCommandRequest>command(new OSQLSynchQuery<ODocument>("select from ORole")).execute();
  }

  public OUser create() {
    if (!getDatabase().getMetadata().getSchema().getClasses().isEmpty())
      return null;

    final OUser adminUser = createMetadata();

    final ORole readerRole = createRole("reader", ORole.ALLOW_MODES.DENY_ALL_BUT);
    readerRole.addRule(ORule.ResourceGeneric.DATABASE, null, ORole.PERMISSION_READ);
    readerRole.addRule(ORule.ResourceGeneric.SCHEMA, null, ORole.PERMISSION_READ);
    readerRole.addRule(ORule.ResourceGeneric.CLUSTER, OMetadataDefault.CLUSTER_INTERNAL_NAME, ORole.PERMISSION_READ);
    readerRole.addRule(ORule.ResourceGeneric.CLUSTER, "orole", ORole.PERMISSION_NONE);
    readerRole.addRule(ORule.ResourceGeneric.CLUSTER, "ouser", ORole.PERMISSION_NONE);
    readerRole.addRule(ORule.ResourceGeneric.CLASS, null, ORole.PERMISSION_READ);
    readerRole.addRule(ORule.ResourceGeneric.CLASS, "OUser", ORole.PERMISSION_NONE);
    readerRole.addRule(ORule.ResourceGeneric.CLUSTER, null, ORole.PERMISSION_READ);
    readerRole.addRule(ORule.ResourceGeneric.COMMAND, null, ORole.PERMISSION_READ);
    readerRole.addRule(ORule.ResourceGeneric.RECORD_HOOK, null, ORole.PERMISSION_READ);
    readerRole.addRule(ORule.ResourceGeneric.FUNCTION, null, ORole.PERMISSION_READ);
    readerRole.addRule(ORule.ResourceGeneric.SYSTEM_CLUSTERS, null, ORole.PERMISSION_NONE);
    readerRole.save();

    // This will return the global value if a local storage context configuration value does not exist.
    boolean createDefUsers = getDatabase().getStorage().getConfiguration().getContextConfiguration()
        .getValueAsBoolean(OGlobalConfiguration.CREATE_DEFAULT_USERS);

    if (createDefUsers)
      createUser("reader", "reader", new String[] { readerRole.getName() });

    final ORole writerRole = createRole("writer", ORole.ALLOW_MODES.DENY_ALL_BUT);
    writerRole.addRule(ORule.ResourceGeneric.DATABASE, null, ORole.PERMISSION_READ);
    writerRole
        .addRule(ORule.ResourceGeneric.SCHEMA, null, ORole.PERMISSION_READ + ORole.PERMISSION_CREATE + ORole.PERMISSION_UPDATE);
    writerRole.addRule(ORule.ResourceGeneric.CLUSTER, OMetadataDefault.CLUSTER_INTERNAL_NAME, ORole.PERMISSION_READ);
    readerRole.addRule(ORule.ResourceGeneric.CLUSTER, "orole", ORole.PERMISSION_NONE);
    readerRole.addRule(ORule.ResourceGeneric.CLUSTER, "ouser", ORole.PERMISSION_NONE);
    writerRole.addRule(ORule.ResourceGeneric.CLASS, null, ORole.PERMISSION_ALL);
    writerRole.addRule(ORule.ResourceGeneric.CLASS, "OUser", ORole.PERMISSION_NONE);
    writerRole.addRule(ORule.ResourceGeneric.CLUSTER, null, ORole.PERMISSION_ALL);
    writerRole.addRule(ORule.ResourceGeneric.COMMAND, null, ORole.PERMISSION_ALL);
    writerRole.addRule(ORule.ResourceGeneric.RECORD_HOOK, null, ORole.PERMISSION_ALL);
    writerRole.addRule(ORule.ResourceGeneric.FUNCTION, null, ORole.PERMISSION_READ);
    writerRole.addRule(ORule.ResourceGeneric.CLASS, OSequence.CLASS_NAME, ORole.PERMISSION_READ);
    writerRole.addRule(ORule.ResourceGeneric.CLASS, "OTriggered", ORole.PERMISSION_READ);
    writerRole.addRule(ORule.ResourceGeneric.CLASS, "OSchedule", ORole.PERMISSION_READ);
    writerRole.addRule(ORule.ResourceGeneric.SYSTEM_CLUSTERS, null, ORole.PERMISSION_NONE);
    writerRole.save();

    if (createDefUsers)
      createUser("writer", "writer", new String[] { writerRole.getName() });

    return adminUser;
  }

  /**
   * Repairs the security structure if broken by creating the ADMIN role and user with default password.
   *
   * @return
   */

  public OUser createMetadata() {
    final ODatabaseDocument database = getDatabase();

    OClass identityClass = database.getMetadata().getSchema().getClass(OIdentity.CLASS_NAME); // SINCE 1.2.0
    if (identityClass == null)
      identityClass = database.getMetadata().getSchema().createAbstractClass(OIdentity.CLASS_NAME);

    OClass roleClass = createOrUpdateORoleClass(database, identityClass);

    createOrUpdateOUserClass(database, identityClass, roleClass);

    // CREATE ROLES AND USERS
    ORole adminRole = getRole(ORole.ADMIN);
    if (adminRole == null) {
      adminRole = createRole(ORole.ADMIN, ORole.ALLOW_MODES.ALLOW_ALL_BUT);
      adminRole.addRule(ORule.ResourceGeneric.BYPASS_RESTRICTED, null, ORole.PERMISSION_ALL).save();
    }

    OUser adminUser = getUser(OUser.ADMIN);

    if (adminUser == null) {
      // This will return the global value if a local storage context configuration value does not exist.
      boolean createDefUsers = getDatabase().getStorage().getConfiguration().getContextConfiguration()
          .getValueAsBoolean(OGlobalConfiguration.CREATE_DEFAULT_USERS);

      if (createDefUsers) {
        adminUser = createUser(OUser.ADMIN, OUser.ADMIN, adminRole);
      }
    }

    // SINCE 1.2.0
    createOrUpdateORestrictedClass(database);

    return adminUser;
  }

  private void createOrUpdateORestrictedClass(final ODatabaseDocument database) {
    OClass restrictedClass = database.getMetadata().getSchema().getClass(RESTRICTED_CLASSNAME);
    boolean unsafe = false;
    if (restrictedClass == null) {
      restrictedClass = database.getMetadata().getSchema().createAbstractClass(RESTRICTED_CLASSNAME);
      unsafe = true;
    }
    if (!restrictedClass.existsProperty(ALLOW_ALL_FIELD))
      restrictedClass
          .createProperty(ALLOW_ALL_FIELD, OType.LINKSET, database.getMetadata().getSchema().getClass(OIdentity.CLASS_NAME),
              unsafe);
    if (!restrictedClass.existsProperty(ALLOW_READ_FIELD))
      restrictedClass
          .createProperty(ALLOW_READ_FIELD, OType.LINKSET, database.getMetadata().getSchema().getClass(OIdentity.CLASS_NAME),
              unsafe);
    if (!restrictedClass.existsProperty(ALLOW_UPDATE_FIELD))
      restrictedClass
          .createProperty(ALLOW_UPDATE_FIELD, OType.LINKSET, database.getMetadata().getSchema().getClass(OIdentity.CLASS_NAME),
              unsafe);
    if (!restrictedClass.existsProperty(ALLOW_DELETE_FIELD))
      restrictedClass
          .createProperty(ALLOW_DELETE_FIELD, OType.LINKSET, database.getMetadata().getSchema().getClass(OIdentity.CLASS_NAME),
              unsafe);
  }

  private void createOrUpdateOUserClass(final ODatabaseDocument database, OClass identityClass, OClass roleClass) {
    boolean unsafe = false;
    OClass userClass = database.getMetadata().getSchema().getClass("OUser");
    if (userClass == null) {
      userClass = database.getMetadata().getSchema().createClass("OUser", identityClass);
      unsafe = true;
    } else if (!userClass.getSuperClasses().contains(identityClass))
      // MIGRATE AUTOMATICALLY TO 1.2.0
      userClass.setSuperClasses(Arrays.asList(identityClass));

    if (!userClass.existsProperty("name")) {
      ((OClassImpl) userClass).createProperty("name", OType.STRING, (OType) null, unsafe).setMandatory(true).setNotNull(true)
          .setCollate("ci").setMin("1").setRegexp("\\S+(.*\\S+)*");
      userClass.createIndex("OUser.name", INDEX_TYPE.UNIQUE, ONullOutputListener.INSTANCE, "name");
    } else {
      final OProperty name = userClass.getProperty("name");
      if (name.getAllIndexes().isEmpty())
        userClass.createIndex("OUser.name", INDEX_TYPE.UNIQUE, ONullOutputListener.INSTANCE, "name");
    }
    if (!userClass.existsProperty(OUser.PASSWORD_FIELD))
      userClass.createProperty(OUser.PASSWORD_FIELD, OType.STRING, (OType) null, unsafe).setMandatory(true).setNotNull(true);
    if (!userClass.existsProperty("roles"))
      userClass.createProperty("roles", OType.LINKSET, roleClass, unsafe);
    if (!userClass.existsProperty("status"))
      userClass.createProperty("status", OType.STRING, (OType) null, unsafe).setMandatory(true).setNotNull(true);
  }

  private OClass createOrUpdateORoleClass(final ODatabaseDocument database, OClass identityClass) {
    OClass roleClass = database.getMetadata().getSchema().getClass("ORole");
    boolean unsafe = false;
    if (roleClass == null) {
      roleClass = database.getMetadata().getSchema().createClass("ORole", identityClass);
      unsafe = true;
    } else if (!roleClass.getSuperClasses().contains(identityClass))
      // MIGRATE AUTOMATICALLY TO 1.2.0
      roleClass.setSuperClasses(Arrays.asList(identityClass));

    if (!roleClass.existsProperty("name")) {
      roleClass.createProperty("name", OType.STRING, (OType) null, unsafe).setMandatory(true).setNotNull(true).setCollate("ci");
      roleClass.createIndex("ORole.name", INDEX_TYPE.UNIQUE, ONullOutputListener.INSTANCE, "name");
    } else {
      final OProperty name = roleClass.getProperty("name");
      if (name.getAllIndexes().isEmpty())
        roleClass.createIndex("ORole.name", INDEX_TYPE.UNIQUE, ONullOutputListener.INSTANCE, "name");
    }

    if (!roleClass.existsProperty("mode"))
      roleClass.createProperty("mode", OType.BYTE, (OType) null, unsafe);

    if (!roleClass.existsProperty("rules"))
      roleClass.createProperty("rules", OType.EMBEDDEDMAP, OType.BYTE, unsafe);
    if (!roleClass.existsProperty("inheritedRole"))
      roleClass.createProperty("inheritedRole", OType.LINK, roleClass, unsafe);
    return roleClass;
  }

  @Override
  public void close() {
  }

  @Override
  public void close(boolean onDelete) {
  }

  public void load() {
    final OClass userClass = getDatabase().getMetadata().getSchema().getClass("OUser");
    if (userClass != null) {
      // @COMPATIBILITY <1.3.0
      if (!userClass.existsProperty("status")) {
        userClass.createProperty("status", OType.STRING).setMandatory(true).setNotNull(true);
      }
      OProperty p = userClass.getProperty("name");
      if (p == null)
        p = userClass.createProperty("name", OType.STRING).setMandatory(true).setNotNull(true).setMin("1")
            .setRegexp("\\S+(.*\\S+)*");

      if (userClass.getInvolvedIndexes("name") == null)
        p.createIndex(INDEX_TYPE.UNIQUE);

      // ROLE
      final OClass roleClass = getDatabase().getMetadata().getSchema().getClass("ORole");

      final OProperty rules = roleClass.getProperty("rules");
      if (rules != null && !OType.EMBEDDEDMAP.equals(rules.getType())) {
        roleClass.dropProperty("rules");
      }

      if (!roleClass.existsProperty("inheritedRole")) {
        roleClass.createProperty("inheritedRole", OType.LINK, roleClass);
      }

      p = roleClass.getProperty("name");
      if (p == null)
        p = roleClass.createProperty("name", OType.STRING).setMandatory(true).setNotNull(true);

      if (roleClass.getInvolvedIndexes("name") == null)
        p.createIndex(INDEX_TYPE.UNIQUE);
    }
  }

  public void createClassTrigger() {
    final ODatabaseDocument db = ODatabaseRecordThreadLocal.instance().get();
    OClass classTrigger = db.getMetadata().getSchema().getClass(OClassTrigger.CLASSNAME);
    if (classTrigger == null)
      classTrigger = db.getMetadata().getSchema().createAbstractClass(OClassTrigger.CLASSNAME);
  }

  @Override
  public OSecurity getUnderlying() {
    return this;
  }

  @Override
  public OUser getUser(final String iUserName) {
    return (OUser) OScenarioThreadLocal.executeAsDistributed(() -> {
      try (OResultSet result = getDatabase().query("select from OUser where name = ? limit 1", iUserName)) {
        if (result.hasNext())
          return new OUser((ODocument) result.next().getElement().get());

      }
      return null;
    });
  }

  public ORID getUserRID(final String iUserName) {
    return (ORID) OScenarioThreadLocal.executeAsDistributed(() -> {
      try (OResultSet result = getDatabase().query("select rid from index:OUser.name where key = ? limit 1", iUserName)) {

        if (result.hasNext())
          return result.next().getProperty("rid");
      }

      return null;
    });
  }

  @Override
  public long getVersion() {
    return version.get();
  }

  @Override
  public void incrementVersion() {
    version.incrementAndGet();
  }

  protected ODatabaseDocumentInternal getDatabase() {
    return ODatabaseRecordThreadLocal.instance().get();
  }
}


/*
 *
 *  *  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.command.traverse;

import com.orientechnologies.orient.core.command.OCommand;
import com.orientechnologies.orient.core.command.OCommandExecutorAbstract;
import com.orientechnologies.orient.core.command.OCommandPredicate;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
 * Base class for traversing.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 */
public class OTraverse implements OCommand, Iterable<OIdentifiable>, Iterator<OIdentifiable> {
  private OCommandPredicate                 predicate;
  private Iterator<? extends OIdentifiable> target;
  private List<Object>                      fields      = new ArrayList<Object>();
  private long                              resultCount = 0;
  private long                              limit       = 0;
  private OIdentifiable                     lastTraversed;
  private STRATEGY                          strategy    = STRATEGY.DEPTH_FIRST;
  private OTraverseContext                  context     = new OTraverseContext();
  private int                               maxDepth    = -1;

  public enum STRATEGY {
    DEPTH_FIRST, BREADTH_FIRST
  }

  /*
   * Executes a traverse collecting all the result in the returning List<OIdentifiable>. This could be memory expensive because for
   * large results the list could be huge. it's always better to use it as an Iterable and lazy fetch each result on next() call.
   *
   * @see com.orientechnologies.orient.core.command.OCommand#execute()
   */
  public List<OIdentifiable> execute() {
    final List<OIdentifiable> result = new ArrayList<OIdentifiable>();
    while (hasNext())
      result.add(next());
    return result;
  }

  public OTraverseAbstractProcess<?> nextProcess() {
    return context.next();
  }

  public boolean hasNext() {
    if (limit > 0 && resultCount >= limit)
      return false;

    if (lastTraversed == null)
      // GET THE NEXT
      lastTraversed = next();

    if (lastTraversed == null && !context.isEmpty())
      throw new IllegalStateException("Traverse ended abnormally");

    if (!OCommandExecutorAbstract.checkInterruption(context))
      return false;

    // BROWSE ALL THE RECORDS
    return lastTraversed != null;
  }

  public OIdentifiable next() {
    if (Thread.interrupted())
      throw new OCommandExecutionException("The traverse execution has been interrupted");

    if (lastTraversed != null) {
      // RETURN LATEST AND RESET IT
      final OIdentifiable result = lastTraversed;
      lastTraversed = null;
      return result;
    }

    if (limit > 0 && resultCount >= limit)
      return null;

    OIdentifiable result;
    OTraverseAbstractProcess<?> toProcess;
    // RESUME THE LAST PROCESS
    while ((toProcess = nextProcess()) != null) {
      result = toProcess.process();
      if (result != null) {
        resultCount++;
        return result;
      }
    }

    return null;
  }

  public void remove() {
    throw new UnsupportedOperationException("remove()");
  }

  public Iterator<OIdentifiable> iterator() {
    return this;
  }

  public OTraverseContext getContext() {
    return context;
  }

  public OTraverse target(final Iterable<? extends OIdentifiable> iTarget) {
    return target(iTarget.iterator());
  }

  public OTraverse target(final OIdentifiable... iRecords) {
    final List<OIdentifiable> list = new ArrayList<OIdentifiable>();
    Collections.addAll(list, iRecords);
    return target(list.iterator());
  }

  @SuppressWarnings("unchecked")
  public OTraverse target(final Iterator<? extends OIdentifiable> iTarget) {
    target = iTarget;
    context.reset();
    new OTraverseRecordSetProcess(this, (Iterator<OIdentifiable>) target, OTraversePath.empty());
    return this;
  }

  public Iterator<? extends OIdentifiable> getTarget() {
    return target;
  }

  public OTraverse predicate(final OCommandPredicate iPredicate) {
    predicate = iPredicate;
    return this;
  }

  public OCommandPredicate getPredicate() {
    return predicate;
  }

  public OTraverse field(final Object iField) {
    if (!fields.contains(iField))
      fields.add(iField);
    return this;
  }

  public OTraverse fields(final Collection<Object> iFields) {
    for (Object f : iFields)
      field(f);
    return this;
  }

  public OTraverse fields(final String... iFields) {
    for (String f : iFields)
      field(f);
    return this;
  }

  public List<Object> getFields() {
    return fields;
  }

  public long getLimit() {
    return limit;
  }

  public OTraverse limit(final long iLimit) {
    if (iLimit < -1)
      throw new IllegalArgumentException("Limit cannot be negative. 0 = infinite");
    this.limit = iLimit;
    return this;
  }

  @Override
  public String toString() {
    return String.format("OTraverse.target(%s).fields(%s).limit(%d).predicate(%s)", target, fields, limit, predicate);
  }

  public long getResultCount() {
    return resultCount;
  }

  public OIdentifiable getLastTraversed() {
    return lastTraversed;
  }

  public STRATEGY getStrategy() {
    return strategy;
  }

  public void setStrategy(STRATEGY strategy) {
    this.strategy = strategy;
    context.setStrategy(strategy);
  }

  public int getMaxDepth() {
    return maxDepth;
  }

  public void setMaxDepth(final int maxDepth) {
    this.maxDepth = maxDepth;
  }
}


package com.orientechnologies.common.util;

import javax.management.MBeanServer;
import javax.management.ObjectName;

import java.lang.management.ManagementFactory;

public class HeapDumper {
  // This is the name of the HotSpot Diagnostic MBean
  private static final String                     HOTSPOT_BEAN_NAME = "com.sun.management:type=HotSpotDiagnostic";

  /**
   * Invoke {@code dumpHeap} operation on {@code com.sun.management:type=HotSpotDiagnostic} mbean.
   */
  public static void dumpHeap(String fileName, boolean live) {
    try {
      MBeanServer server = ManagementFactory.getPlatformMBeanServer();
      server.invoke(new ObjectName(HOTSPOT_BEAN_NAME),
          "dumpHeap",
          new Object[]{fileName, live},
          new String[]{String.class.getName(), Boolean.TYPE.getName()}
      );
    } catch (RuntimeException re) {
      throw re;
    } catch (Exception exp) {
      throw new RuntimeException(exp);
    }
  }
}


/*
 *
 *  *  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.concur.lock;

import com.orientechnologies.common.types.OModifiableInteger;

import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.LongAdder;
import java.util.concurrent.locks.AbstractOwnableSynchronizer;
import java.util.concurrent.locks.LockSupport;

/**
 * @author Andrey Lomakin (a.lomakin-at-orientdb.com)
 * @since 8/18/14
 */
public class OReadersWriterSpinLock extends AbstractOwnableSynchronizer {
  private static final long serialVersionUID = 7975120282194559960L;

  private final transient LongAdder distributedCounter;
  private final transient AtomicReference<WNode>          tail      = new AtomicReference<WNode>();
  private final transient ThreadLocal<OModifiableInteger> lockHolds = new InitOModifiableInteger();

  private final transient ThreadLocal<WNode> myNode = new InitWNode();

  public OReadersWriterSpinLock() {
    final WNode wNode = new WNode();
    wNode.locked = false;

    tail.set(wNode);

    distributedCounter = new LongAdder();
  }

  /**
   * Tries to acquire lock during provided interval of time and returns either if provided time interval was passed or
   * if lock was acquired.
   *
   * @param timeout Timeout during of which we should wait for read lock.
   *
   * @return true if read lock was acquired.
   */
  public boolean tryAcquireReadLock(long timeout) {
    final OModifiableInteger lHolds = lockHolds.get();

    final int holds = lHolds.intValue();
    if (holds > 0) {
      // we have already acquire read lock
      lHolds.increment();
      return true;
    } else if (holds < 0) {
      // write lock is acquired before, do nothing
      return true;
    }

    distributedCounter.increment();

    WNode wNode = tail.get();

    final long start = System.nanoTime();
    while (wNode.locked) {
      distributedCounter.decrement();

      while (wNode.locked && wNode == tail.get()) {
        wNode.waitingReaders.put(Thread.currentThread(), Boolean.TRUE);

        if (wNode.locked && wNode == tail.get()) {
          final long parkTimeout = timeout - (System.nanoTime() - start);
          if (parkTimeout > 0) {
            LockSupport.parkNanos(this, parkTimeout);
          } else {
            return false;
          }
        }

        wNode = tail.get();

        if (System.nanoTime() - start > timeout) {
          return false;
        }
      }

      distributedCounter.increment();

      wNode = tail.get();
      if (System.nanoTime() - start > timeout) {
        distributedCounter.decrement();

        return false;
      }
    }

    lHolds.increment();
    assert lHolds.intValue() == 1;

    return true;
  }

  public void acquireReadLock() {
    final OModifiableInteger lHolds = lockHolds.get();

    final int holds = lHolds.intValue();
    if (holds > 0) {
      // we have already acquire read lock
      lHolds.increment();
      return;
    } else if (holds < 0) {
      // write lock is acquired before, do nothing
      return;
    }

    distributedCounter.increment();

    WNode wNode = tail.get();
    while (wNode.locked) {
      distributedCounter.decrement();

      while (wNode.locked && wNode == tail.get()) {
        wNode.waitingReaders.put(Thread.currentThread(), Boolean.TRUE);

        if (wNode.locked && wNode == tail.get())
          LockSupport.park(this);

        wNode = tail.get();
      }

      distributedCounter.increment();

      wNode = tail.get();
    }

    lHolds.increment();
    assert lHolds.intValue() == 1;
  }

  public void releaseReadLock() {
    final OModifiableInteger lHolds = lockHolds.get();
    final int holds = lHolds.intValue();
    if (holds > 1) {
      lHolds.decrement();
      return;
    } else if (holds < 0) {
      // write lock was acquired before, do nothing
      return;
    }

    distributedCounter.decrement();

    lHolds.decrement();
    assert lHolds.intValue() == 0;
  }

  public void acquireWriteLock() {
    final OModifiableInteger lHolds = lockHolds.get();

    if (lHolds.intValue() < 0) {
      lHolds.decrement();
      return;
    }

    final WNode node = myNode.get();
    node.locked = true;

    final WNode pNode = tail.getAndSet(myNode.get());

    while (pNode.locked) {
      pNode.waitingWriter = Thread.currentThread();

      if (pNode.locked)
        LockSupport.park(this);
    }

    pNode.waitingWriter = null;

    while (distributedCounter.sum() != 0) {
      Thread.yield();
    }

    setExclusiveOwnerThread(Thread.currentThread());

    lHolds.decrement();
    assert lHolds.intValue() == -1;
  }

  public void releaseWriteLock() {
    final OModifiableInteger lHolds = lockHolds.get();

    if (lHolds.intValue() < -1) {
      lHolds.increment();
      return;
    }

    setExclusiveOwnerThread(null);

    final WNode node = myNode.get();
    myNode.set(new WNode());
    node.locked = false;

    final Thread waitingWriter = node.waitingWriter;
    if (waitingWriter != null)
      LockSupport.unpark(waitingWriter);

    while (!node.waitingReaders.isEmpty()) {
      final Set<Thread> readers = node.waitingReaders.keySet();
      final Iterator<Thread> threadIterator = readers.iterator();

      while (threadIterator.hasNext()) {
        final Thread reader = threadIterator.next();
        threadIterator.remove();

        LockSupport.unpark(reader);
      }
    }

    lHolds.increment();
    assert lHolds.intValue() == 0;
  }

  private static final class InitWNode extends ThreadLocal<WNode> {
    @Override
    protected WNode initialValue() {
      return new WNode();
    }
  }

  private static final class InitOModifiableInteger extends ThreadLocal<OModifiableInteger> {
    @Override
    protected OModifiableInteger initialValue() {
      return new OModifiableInteger();
    }
  }

  private final static class WNode {
    private final ConcurrentHashMap<Thread, Boolean> waitingReaders = new ConcurrentHashMap<Thread, Boolean>();

    private volatile boolean locked = true;
    private volatile Thread waitingWriter;
  }
}


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

package com.tinkerpop.blueprints.impls.orient;

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ridbag.ORidBag;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.tinkerpop.blueprints.Direction;
import com.tinkerpop.blueprints.Edge;
import com.tinkerpop.blueprints.Element;
import com.tinkerpop.blueprints.Index;
import com.tinkerpop.blueprints.util.ExceptionFactory;
import com.tinkerpop.blueprints.util.StringFactory;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.*;

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

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

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

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

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

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

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

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

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

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

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

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

    return edge.field(OrientElement.LABEL_FIELD_NAME);
  }

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

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

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

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

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

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

    setCurrentGraphInThreadLocal();

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

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

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

    setCurrentGraphInThreadLocal();

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

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

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

      setCurrentGraphInThreadLocal();

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

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

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

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

    setCurrentGraphInThreadLocal();

    return super.getId();
  }

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

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

    return super.getProperty(key);
  }

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

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

    setCurrentGraphInThreadLocal();

    final Set<String> result = new HashSet<String>();

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

    return result;
  }

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

    final OrientBaseGraph graph = setCurrentGraphInThreadLocal();

    final Map<String, Object> result = new HashMap<String, Object>();

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

    return result;
  }

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

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

    super.setProperty(key, value);
  }

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

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

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

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

    for (final Index<? extends Element> index : graph.getIndices()) {
      if (Edge.class.isAssignableFrom(index.getIndexClass())) {
        OrientIndex<OrientEdge> idx = (OrientIndex<OrientEdge>) index;
        idx.removeElement(this);
      }
    }

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

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

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

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

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

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

    return super.getRecord();
  }

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

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

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

    final ODocument doc = createDocument(label);

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

    final boolean useVertexFieldsForEdgeLabels = settings.isUseVertexFieldsForEdgeLabels();

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

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

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

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

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

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

    return OrientEdgeType.CLASS_NAME;
  }

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

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

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

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

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

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

    final ODocument doc = new ODocument(className);

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

    return doc;
  }

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

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

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

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

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

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

    return true;
  }

}


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

import com.orientechnologies.common.exception.OException;
import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest;
import com.orientechnologies.orient.core.command.OCommandRequest;
import com.orientechnologies.orient.core.command.OCommandRequestText;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.db.ODatabase;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.metadata.security.ORole;
import com.orientechnologies.orient.core.metadata.security.ORule;

import java.util.Arrays;
import java.util.Locale;
import java.util.Map;

/**
 * SQL ALTER DATABASE command: Changes an attribute of the current database.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 */
@SuppressWarnings("unchecked")
public class OCommandExecutorSQLAlterDatabase extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest {
  public static final String KEYWORD_ALTER    = "ALTER";
  public static final String KEYWORD_DATABASE = "DATABASE";

  private ODatabase.ATTRIBUTES attribute;
  private String               value;

  public OCommandExecutorSQLAlterDatabase parse(final OCommandRequest iRequest) {
    final OCommandRequestText textRequest = (OCommandRequestText) iRequest;

    String queryText = textRequest.getText();
    String originalQuery = queryText;
    try {
      queryText = preParse(queryText, iRequest);
      textRequest.setText(queryText);

      init((OCommandRequestText) iRequest);

      StringBuilder word = new StringBuilder();

      int oldPos = 0;
      int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true);
      if (pos == -1 || !word.toString().equals(KEYWORD_ALTER))
        throw new OCommandSQLParsingException("Keyword " + KEYWORD_ALTER + " not found. Use " + getSyntax(), parserText, oldPos);

      oldPos = pos;
      pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true);
      if (pos == -1 || !word.toString().equals(KEYWORD_DATABASE))
        throw new OCommandSQLParsingException("Keyword " + KEYWORD_DATABASE + " not found. Use " + getSyntax(), parserText, oldPos);

      oldPos = pos;
      pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true);
      if (pos == -1)
        throw new OCommandSQLParsingException("Missed the database's attribute to change. Use " + getSyntax(), parserText, oldPos);

      final String attributeAsString = word.toString();

      try {
        attribute = ODatabase.ATTRIBUTES.valueOf(attributeAsString.toUpperCase(Locale.ENGLISH));
      } catch (IllegalArgumentException e) {
        throw OException.wrapException(new OCommandSQLParsingException(
            "Unknown database's attribute '" + attributeAsString + "'. Supported attributes are: " + Arrays
                .toString(ODatabase.ATTRIBUTES.values()), parserText, oldPos), e);
      }

      value = parserText.substring(pos + 1).trim();

      if (value.length() == 0)
        throw new OCommandSQLParsingException("Missed the database's value to change for attribute '" + attribute + "'. Use "
            + getSyntax(), parserText, oldPos);

      if (value.equalsIgnoreCase("null"))
        value = null;
    } finally {
      textRequest.setText(originalQuery);
    }

    return this;
  }

  @Override
  public long getDistributedTimeout() {
    return getDatabase().getConfiguration().getValueAsLong(OGlobalConfiguration.DISTRIBUTED_COMMAND_QUICK_TASK_SYNCH_TIMEOUT);
  }

  /**
   * Execute the ALTER DATABASE.
   */
  public Object execute(final Map<Object, Object> iArgs) {
    if (attribute == null)
      throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet");

    final ODatabaseDocumentInternal database = getDatabase();
    database.checkSecurity(ORule.ResourceGeneric.DATABASE, ORole.PERMISSION_UPDATE);

    database.setInternal(attribute, value);
    return null;
  }

  @Override
  public QUORUM_TYPE getQuorumType() {
    return QUORUM_TYPE.ALL;
  }

  public String getSyntax() {
    return "ALTER DATABASE <attribute-name> <attribute-value>";
  }
}


/*
 *
 *  *  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.
 *
 * @author Ilya Bershadskiy (ibersh20-at-gmail.com)
 * @since 20.01.12
 */
public class ODateTimeSerializer implements OBinarySerializer<Date> {
  public static final byte                ID       = 5;
  public static final ODateTimeSerializer INSTANCE = new ODateTimeSerializer();

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

  public void serialize(Date object, byte[] stream, int startPosition, Object... hints) {
    final Calendar calendar = Calendar.getInstance();
    calendar.setTime(object);
    OLongSerializer.INSTANCE.serializeLiteral(calendar.getTimeInMillis(), stream, startPosition);
  }

  public Date deserialize(byte[] stream, int startPosition) {
    final Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(OLongSerializer.INSTANCE.deserializeLiteral(stream, startPosition));
    return calendar.getTime();
  }

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

  @Override
  public void serializeNativeObject(Date object, byte[] stream, int startPosition, Object... hints) {
    final Calendar calendar = Calendar.getInstance();
    calendar.setTime(object);
    OLongSerializer.INSTANCE.serializeNative(calendar.getTimeInMillis(), stream, startPosition);
  }

  @Override
  public Date deserializeNativeObject(byte[] stream, int startPosition) {
    final Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(OLongSerializer.INSTANCE.deserializeNative(stream, startPosition));
    return calendar.getTime();
  }

  public boolean isFixedLength() {
    return true;
  }

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

  @Override
  public Date preprocess(Date value, Object... hints) {
    return value;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void serializeInByteBufferObject(Date object, ByteBuffer buffer, Object... hints) {
    final Calendar calendar = Calendar.getInstance();
    calendar.setTime(object);
    buffer.putLong(calendar.getTimeInMillis());
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Date deserializeFromByteBufferObject(ByteBuffer buffer) {
    final Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(buffer.getLong());
    return calendar.getTime();
  }

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

  /**
   * {@inheritDoc}
   */
  @Override
  public Date deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) {
    final Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(walChanges.getLongValue(buffer, offset));
    return calendar.getTime();
  }

  /**
   * {@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.server.distributed.sql;

import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest;
import com.orientechnologies.orient.core.command.OCommandRequest;
import com.orientechnologies.orient.core.command.OCommandRequestText;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.metadata.security.ORole;
import com.orientechnologies.orient.core.metadata.security.ORule;
import com.orientechnologies.orient.core.sql.OCommandExecutorSQLAbstract;
import com.orientechnologies.orient.core.sql.OCommandSQLParsingException;
import com.orientechnologies.orient.core.sql.parser.OHaRemoveServerStatement;
import com.orientechnologies.orient.core.sql.parser.OStatementCache;
import com.orientechnologies.orient.server.OServer;
import com.orientechnologies.orient.server.distributed.impl.ODatabaseDocumentDistributed;
import com.orientechnologies.orient.server.hazelcast.OHazelcastPlugin;

import java.util.Map;

/**
 * SQL HA REMOVE SERVER command: removes a server from ha configuration.
 *
 * @author Luca Garulli
 */
@SuppressWarnings("unchecked")
public class OCommandExecutorSQLHARemoveServer extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest {
  public static final String NAME = "HA REMOVE SERVER";

  OHaRemoveServerStatement parsedStatement;

  public OCommandExecutorSQLHARemoveServer parse(final OCommandRequest iRequest) {
    init((OCommandRequestText) iRequest);
    try {
      parsedStatement = (OHaRemoveServerStatement) OStatementCache.get(this.parserText, getDatabase());
      preParsedStatement = parsedStatement;
    } catch (OCommandSQLParsingException sqlx) {
      throw sqlx;
    } catch (Exception e) {
      throwParsingException("Error parsing query: \n" + this.parserText + "\n" + e.getMessage(), e);
    }
    return this;
  }

  /**
   * Execute the command.
   */
  public Object execute(final Map<Object, Object> iArgs) {
    final ODatabaseDocumentInternal database = getDatabase();
    database.checkSecurity(ORule.ResourceGeneric.SERVER, "remove", ORole.PERMISSION_EXECUTE);

    if (!(database instanceof ODatabaseDocumentDistributed)) {
      throw new OCommandExecutionException("OrientDB is not started in distributed mode");
    }

    final OHazelcastPlugin dManager = (OHazelcastPlugin) ((ODatabaseDocumentDistributed) database).getDistributedManager();
    if (dManager == null || !dManager.isEnabled())
      throw new OCommandExecutionException("OrientDB is not started in distributed mode");

    final String databaseName = database.getName();

    // The last parameter (true) indicates to set the node's database status to OFFLINE.
    // If this is changed to false, the node will be set to NOT_AVAILABLE, and then the auto-repairer will
    // re-synchronize the database on the node, and then set it to ONLINE.
    return dManager.removeNodeFromConfiguration(parsedStatement.serverName.getStringValue(), databaseName, false, true);
  }

  @Override
  public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() {
    return DISTRIBUTED_EXECUTION_MODE.LOCAL;
  }

  @Override
  public QUORUM_TYPE getQuorumType() {
    return QUORUM_TYPE.NONE;
  }

  @Override
  public String getSyntax() {
    return "HA REMOVE SERVER <server-name>";
  }
}