/*
 *
 *  *  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.common.serialization.OBinaryConverter;
import com.orientechnologies.common.serialization.OBinaryConverterFactory;
import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * Serializer for {@link Short}.
 *
 * @author Ilya Bershadskiy (ibersh20-at-gmail.com)
 * @since 18.01.12
 */
public class OShortSerializer implements OBinarySerializer<Short> {
  public static final  byte             ID         = 12;
  /**
   * size of short value in bytes
   */
  public static final  int              SHORT_SIZE = 2;
  private static final OBinaryConverter CONVERTER  = OBinaryConverterFactory.getConverter();
  public static final  OShortSerializer INSTANCE   = new OShortSerializer();

  public int getObjectSize(Short object, Object... hints) {
    return SHORT_SIZE;
  }

  public void serialize(final Short object, final byte[] stream, final int startPosition, final Object... hints) {
    serializeLiteral(object.shortValue(), stream, startPosition);
  }

  public void serializeLiteral(final short value, final byte[] stream, final int startPosition) {
    stream[startPosition] = (byte) ((value >>> 8) & 0xFF);
    stream[startPosition + 1] = (byte) ((value >>> 0) & 0xFF);
  }

  public Short deserialize(final byte[] stream, final int startPosition) {
    return deserializeLiteral(stream, startPosition);
  }

  public short deserializeLiteral(final byte[] stream, final int startPosition) {
    return (short) ((stream[startPosition] << 8) | (stream[startPosition + 1] & 0xff));
  }

  public int getObjectSize(final byte[] stream, final int startPosition) {
    return SHORT_SIZE;
  }

  public byte getId() {
    return ID;
  }

  public int getObjectSizeNative(final byte[] stream, final int startPosition) {
    return SHORT_SIZE;
  }

  @Override
  public void serializeNativeObject(final Short object, final byte[] stream, final int startPosition, final Object... hints) {
    CONVERTER.putShort(stream, startPosition, object, ByteOrder.nativeOrder());
  }

  @Override
  public Short deserializeNativeObject(byte[] stream, int startPosition) {
    return CONVERTER.getShort(stream, startPosition, ByteOrder.nativeOrder());
  }

  public void serializeNative(final short object, final byte[] stream, final int startPosition, final Object... hints) {
    CONVERTER.putShort(stream, startPosition, object, ByteOrder.nativeOrder());
  }

  public short deserializeNative(byte[] stream, int startPosition) {
    return CONVERTER.getShort(stream, startPosition, ByteOrder.nativeOrder());
  }

  public boolean isFixedLength() {
    return true;
  }

  public int getFixedLength() {
    return SHORT_SIZE;
  }

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

  /**
   * {@inheritDoc}
   */
  @Override
  public void serializeInByteBufferObject(Short object, ByteBuffer buffer, Object... hints) {
    buffer.putShort(object);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Short deserializeFromByteBufferObject(ByteBuffer buffer) {
    return buffer.getShort();
  }

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

  /**
   * {@inheritDoc}
   */
  @Override
  public Short deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) {
    return walChanges.getShortValue(buffer, offset);
  }

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


package com.orientechnologies.orient.object.db;

import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.ODatabaseInternal;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx;

/**
 * @author Wouter de Vaal
 */
public class OLazyCollectionUtil {

    /**
     * Gets the current thread database as a ODatabasePojoAbstract, wrapping it where necessary.
     */
  protected static OObjectDatabaseTx getDatabase() {
    ODatabaseInternal<?> databaseOwner = ODatabaseRecordThreadLocal.instance().get().getDatabaseOwner();
    if (databaseOwner instanceof OObjectDatabaseTx) {
      return (OObjectDatabaseTx) databaseOwner;
    } else if (databaseOwner instanceof ODatabaseDocumentInternal) {
      return new OObjectDatabaseTx((ODatabaseDocumentInternal) databaseOwner);
    }
    throw new IllegalStateException("Current database not of expected type");
  }
}


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

import com.orientechnologies.orient.core.collate.ODefaultCollate;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ORecordElement;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.OCommandExecutorSQLCreateIndex;

import java.util.Collections;
import java.util.List;

/**
 * Index implementation bound to one schema class property.
 *
 */
public class OPropertyIndexDefinition extends OAbstractIndexDefinition {
  private static final long serialVersionUID = 7395728581151922197L;
  protected String className;
  protected String field;
  protected OType  keyType;

  public OPropertyIndexDefinition(final String iClassName, final String iField, final OType iType) {
    super();
    className = iClassName;
    field = iField;
    keyType = iType;
  }

  /**
   * Constructor used for index unmarshalling.
   */
  public OPropertyIndexDefinition() {
  }

  public String getClassName() {
    return className;
  }

  public List<String> getFields() {
    return Collections.singletonList(field);
  }

  public List<String> getFieldsToIndex() {
    if (collate == null || collate.getName().equals(ODefaultCollate.NAME))
      return Collections.singletonList(field);

    return Collections.singletonList(field + " collate " + collate.getName());
  }

  public Object getDocumentValueToIndex(final ODocument iDocument) {
    if (OType.LINK.equals(keyType)) {
      final OIdentifiable identifiable = iDocument.field(field);
      if (identifiable != null)
        return createValue(identifiable.getIdentity());
      else
        return null;
    }
    return createValue(iDocument.<Object>field(field));
  }

  @Override
  public boolean equals(final Object o) {
    if (this == o)
      return true;
    if (o == null || getClass() != o.getClass())
      return false;

    if (!super.equals(o))
      return false;

    final OPropertyIndexDefinition that = (OPropertyIndexDefinition) o;

    if (!className.equals(that.className))
      return false;
    if (!field.equals(that.field))
      return false;
    if (keyType != that.keyType)
      return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result = super.hashCode();
    result = 31 * result + className.hashCode();
    result = 31 * result + field.hashCode();
    result = 31 * result + keyType.hashCode();
    return result;
  }

  @Override
  public String toString() {
    return "OPropertyIndexDefinition{" + "className='" + className + '\'' + ", field='" + field + '\'' + ", keyType=" + keyType
        + ", collate=" + collate + ", null values ignored = " + isNullValuesIgnored() + '}';
  }

  public Object createValue(final List<?> params) {
    return OType.convert(params.get(0), keyType.getDefaultJavaType());
  }

  /**
   * {@inheritDoc}
   */
  public Object createValue(final Object... params) {
    return OType.convert(params[0], keyType.getDefaultJavaType());
  }

  public int getParamCount() {
    return 1;
  }

  public OType[] getTypes() {
    return new OType[] { keyType };
  }

  @Override
  protected final void fromStream() {
    serializeFromStream();
  }

  @Override
  public final ODocument toStream() {
    document.setInternalStatus(ORecordElement.STATUS.UNMARSHALLING);

    try {
      serializeToStream();
    } finally {
      document.setInternalStatus(ORecordElement.STATUS.LOADED);
    }

    return document;
  }

  protected void serializeToStream() {
    super.serializeToStream();

    document.field("className", className);
    document.field("field", field);
    document.field("keyType", keyType.toString());
    document.field("collate", collate.getName());
    document.field("nullValuesIgnored", isNullValuesIgnored());
  }

  protected void serializeFromStream() {
    super.serializeFromStream();

    className = document.field("className");
    field = document.field("field");

    final String keyTypeStr = document.field("keyType");
    keyType = OType.valueOf(keyTypeStr);

    setCollate((String) document.field("collate"));
    setNullValuesIgnored(!Boolean.FALSE.equals(document.<Boolean> field("nullValuesIgnored")));
  }

  /**
   * {@inheritDoc}
   *
   * @param indexName
   * @param indexType
   */
  public String toCreateIndexDDL(final String indexName, final String indexType, final String engine) {
    return createIndexDDLWithFieldType(indexName, indexType, engine).toString();
  }

  protected StringBuilder createIndexDDLWithFieldType(String indexName, String indexType, String engine) {
    final StringBuilder ddl = createIndexDDLWithoutFieldType(indexName, indexType,engine);
    ddl.append(' ').append(keyType.name());
    return ddl;
  }

  protected StringBuilder createIndexDDLWithoutFieldType(final String indexName, final String indexType,final String engine) {
    final StringBuilder ddl = new StringBuilder("create index `");

    ddl.append(indexName).append("` on `");
    ddl.append(className).append("` ( `").append(field).append("`");

    if (!collate.getName().equals(ODefaultCollate.NAME))
      ddl.append(" collate ").append(collate.getName());

    ddl.append(" ) ");
    ddl.append(indexType);

    if (engine != null)
      ddl.append(' ').append(OCommandExecutorSQLCreateIndex.KEYWORD_ENGINE + " " + engine);
    return ddl;
  }

  @Override
  public boolean isAutomatic() {
    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.tinkerpop.blueprints.impls.orient;

import com.orientechnologies.orient.client.remote.OStorageRemote;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import org.apache.commons.configuration.Configuration;

import com.orientechnologies.orient.core.intent.OIntent;

import java.util.Map;

/**
 * Base class to manage graph settings.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com) (http://orientdb.com)
 *
 */
public abstract class OrientConfigurableGraph {
  protected Settings settings = new Settings();

  protected static final boolean     USE_LIGHTWEIGHT_EDGES_DEFAULT                    = false;
  protected static final boolean     USE_CLASS_FOR_EDGE_LABEL_DEFAULT                 = true;
  protected static final boolean     USE_CLASS_FOR_VERTEX_LABEL_DEFAULT               = true;
  protected static final boolean     KEEP_IN_MEMORY_REFERENCES_DEFAULT                = false;
  protected static final boolean     USE_VERTEX_FIELDS_FOR_EDGE_LABELS                = true;
  protected static final boolean     SAVE_ORIGINAL_IDS_DEFAULT                        = false;
  protected static final boolean     STANDARD_ELEMENT_CONSTRAINTS_DEFAULT             = true;
  protected static final boolean     STANDARD_EXCEPTIONS                              = false;
  protected static final boolean     WARN_ON_FORCE_CLOSING_TX_DEFAULT                 = true;
  protected static final boolean     AUTO_SCALE_EDGE_TYPE_DEFAULT                     = false;
  protected static final boolean     USE_LOG_DEFAULT                                  = true;
  protected static final int         EDGE_CONTAINER_EMBEDDED_2_TREE_THRESHOLD_DEFAULT = -1;
  protected static final int         EDGE_CONTAINER_TREE_2_EMBEDDED_THRESHOLD_DEFAULT = -1;
  protected static final THREAD_MODE THREAD_MODE_DEFAULT                              = THREAD_MODE.AUTOSET_IFNULL;
  protected static final boolean     AUTO_START_TX_DEFAULT                            = true;
  protected static final boolean     REQUIRE_TRANSACTION_DEFAULT                      = false;
  protected static final boolean     STANDARD_TX_REQUIRE_FOR_SQL_OPERATIONS           = true;
  protected static final int         STANDARD_MAX_RETRIES                             = 50;

  public enum THREAD_MODE {
    MANUAL, AUTOSET_IFNULL, ALWAYS_AUTOSET
  }

  public static class Settings {

    private Boolean     useLightweightEdges                 = null;
    private Boolean     useClassForEdgeLabel                = null;
    private Boolean     useClassForVertexLabel              = null;
    private Boolean     keepInMemoryReferences              = null;
    private Boolean     useVertexFieldsForEdgeLabels        = null;
    private Boolean     saveOriginalIds                     = null;
    private Boolean     standardElementConstraints          = null;
    private Boolean     standardExceptions                  = null;
    private Boolean     warnOnForceClosingTx                = null;
    private Boolean     autoScaleEdgeType                   = null;
    private Integer     edgeContainerEmbedded2TreeThreshold = null;
    private Integer     edgeContainerTree2EmbeddedThreshold = null;
    private THREAD_MODE threadMode                          = null;
    private Boolean     autoStartTx                         = null;
    private Boolean     txRequiredForSQLGraphOperations     = null;
    private Boolean     requireTransaction                  = null;
    private Boolean     useLog                              = null;
    private String      connectionStrategy                  = "STICKY";
    private Integer     maxRetries                          = null;

    public Settings copy() {
      final Settings copy = new Settings();
      copy.useLightweightEdges = useLightweightEdges;
      copy.useClassForEdgeLabel = useClassForEdgeLabel;
      copy.useClassForVertexLabel = useClassForVertexLabel;
      copy.keepInMemoryReferences = keepInMemoryReferences;
      copy.useVertexFieldsForEdgeLabels = useVertexFieldsForEdgeLabels;
      copy.saveOriginalIds = saveOriginalIds;
      copy.standardElementConstraints = standardElementConstraints;
      copy.standardExceptions = standardExceptions;
      copy.warnOnForceClosingTx = warnOnForceClosingTx;
      copy.autoScaleEdgeType = autoScaleEdgeType;
      copy.edgeContainerEmbedded2TreeThreshold = edgeContainerEmbedded2TreeThreshold;
      copy.edgeContainerTree2EmbeddedThreshold = edgeContainerTree2EmbeddedThreshold;
      copy.threadMode = threadMode;
      copy.autoStartTx = autoStartTx;
      copy.requireTransaction = requireTransaction;
      copy.txRequiredForSQLGraphOperations = txRequiredForSQLGraphOperations;
      copy.useLog = useLog;
      copy.connectionStrategy = connectionStrategy;
      copy.maxRetries = maxRetries;
      return copy;
    }

    /**
     * copies only not null settings from the input settings object
     *
     * @param settings
     */
    public void copyFrom(final Settings settings) {
      if (settings.useLightweightEdges != null) {
        useLightweightEdges = settings.useLightweightEdges;
      }
      if (settings.useClassForEdgeLabel != null) {
        useClassForEdgeLabel = settings.useClassForEdgeLabel;
      }
      if (settings.useClassForVertexLabel != null) {
        useClassForVertexLabel = settings.useClassForVertexLabel;
      }
      if (settings.keepInMemoryReferences != null) {
        keepInMemoryReferences = settings.keepInMemoryReferences;
      }
      if (settings.useVertexFieldsForEdgeLabels != null) {
        useVertexFieldsForEdgeLabels = settings.useVertexFieldsForEdgeLabels;
      }
      if (settings.saveOriginalIds != null) {
        saveOriginalIds = settings.saveOriginalIds;
      }
      if (settings.standardElementConstraints != null) {
        standardElementConstraints = settings.standardElementConstraints;
      }
      if (settings.standardExceptions != null) {
        standardExceptions = settings.standardExceptions;
      }
      if (settings.warnOnForceClosingTx != null) {
        warnOnForceClosingTx = settings.warnOnForceClosingTx;
      }
      if (settings.autoScaleEdgeType != null) {
        autoScaleEdgeType = settings.autoScaleEdgeType;
      }
      if (settings.edgeContainerEmbedded2TreeThreshold != null) {
        edgeContainerEmbedded2TreeThreshold = settings.edgeContainerEmbedded2TreeThreshold;
      }
      if (settings.edgeContainerTree2EmbeddedThreshold != null) {
        edgeContainerTree2EmbeddedThreshold = settings.edgeContainerTree2EmbeddedThreshold;
      }
      if (settings.threadMode != null) {
        threadMode = settings.threadMode;
      }
      if (settings.autoStartTx != null) {
        autoStartTx = settings.autoStartTx;
      }
      if (settings.requireTransaction != null) {
        requireTransaction = settings.requireTransaction;
      }
      if (settings.txRequiredForSQLGraphOperations != null) {
        txRequiredForSQLGraphOperations = settings.txRequiredForSQLGraphOperations;
      }
      if (settings.useLog != null) {
        useLog = settings.useLog;
      }
      if (settings.connectionStrategy != null) {
        connectionStrategy = settings.connectionStrategy;
      }
      if (settings.maxRetries != null) {
        maxRetries = settings.maxRetries;
      }
    }

    /**
     * Returns true if is using lightweight edges, otherwise false.
     */
    public boolean isUseLightweightEdges() {
      if (useLightweightEdges == null) {
        return USE_LIGHTWEIGHT_EDGES_DEFAULT;
      }
      return useLightweightEdges;
    }

    /**
     * Changes the setting about usage of lightweight edges.
     */
    public void setUseLightweightEdges(final boolean useDynamicEdges) {
      useLightweightEdges = useDynamicEdges;
    }

    /**
     * Returns true if is using auto scale edge type, otherwise false.
     */
    public boolean isAutoScaleEdgeType() {
      if (autoScaleEdgeType == null) {
        return AUTO_SCALE_EDGE_TYPE_DEFAULT;
      }

      return autoScaleEdgeType;
    }

    /**
     * Changes the setting about usage of auto scale edge type.
     */
    public void setAutoScaleEdgeType(final boolean autoScaleEdgeType) {
      this.autoScaleEdgeType = autoScaleEdgeType;

    }

    /**
     * Returns true if is using transaction logs.
     */
    public boolean isUseLog() {
      if (useLog == null) {
        return USE_LOG_DEFAULT;
      }

      return useLog;
    }

    /**
     * Changes the setting about usage of transaction log.
     */
    public void setUseLog(final boolean useLog) {
      this.useLog = useLog;

    }

    public String getConnectionStrategy() {
      return this.connectionStrategy;
    }

    public void setConnectionStrategy(final String connectionStrategy) {
      this.connectionStrategy = connectionStrategy;
    }

    /**
     * Returns the minimum number of edges for edge containers to transform the underlying structure from embedded to tree.
     */
    public int getEdgeContainerEmbedded2TreeThreshold() {
      if (edgeContainerEmbedded2TreeThreshold == null) {
        return EDGE_CONTAINER_EMBEDDED_2_TREE_THRESHOLD_DEFAULT;
      }
      return edgeContainerEmbedded2TreeThreshold;
    }

    /**
     * Changes the minimum number of edges for edge containers to transform the underlying structure from embedded to tree. Use -1
     * to disable transformation.
     */
    public void setEdgeContainerEmbedded2TreeThreshold(final int edgeContainerEmbedded2TreeThreshold) {
      this.edgeContainerEmbedded2TreeThreshold = edgeContainerEmbedded2TreeThreshold;
    }

    /**
     * Returns the minimum number of edges for edge containers to transform the underlying structure from tree to embedded.
     */
    public int getEdgeContainerTree2EmbeddedThreshold() {
      if (edgeContainerTree2EmbeddedThreshold == null) {
        return EDGE_CONTAINER_TREE_2_EMBEDDED_THRESHOLD_DEFAULT;
      }
      return edgeContainerTree2EmbeddedThreshold;
    }

    /**
     * Changes the minimum number of edges for edge containers to transform the underlying structure from tree to embedded. Use -1
     * to disable transformation.
     */
    public void setEdgeContainerTree2EmbeddedThreshold(final int edgeContainerTree2EmbeddedThreshold) {
      this.edgeContainerTree2EmbeddedThreshold = edgeContainerTree2EmbeddedThreshold;
    }

    /**
     * Tells if a transaction is started automatically when the graph is changed. This affects only when a transaction hasn't been
     * started. Default is true.
     *
     * @return
     */
    public boolean isAutoStartTx() {
      if (autoStartTx == null) {
        return AUTO_START_TX_DEFAULT;
      }
      return autoStartTx;
    }

    /**
     * If enabled auto starts a new transaction right before the graph is changed. This affects only when a transaction hasn't been
     * started. Default is true.
     *
     * @param autoStartTx
     */
    public void setAutoStartTx(final boolean autoStartTx) {
      this.autoStartTx = autoStartTx;
    }

    /**
     * Returns true if it is required that all modification operations are executed inside a transaction.
     */
    public boolean isRequireTransaction() {
      if (requireTransaction == null) {
        return REQUIRE_TRANSACTION_DEFAULT;
      }
      return requireTransaction;
    }

    /**
     * Changes the setting about if all modification operations are executed inside a transaction.
     */
    public void setRequireTransaction(final boolean requireTransaction) {
      this.requireTransaction = requireTransaction;
    }

    /**
     * Changes the setting about usage of transactions on graph modification for SQL commands (create/remove vertex, create/remove
     * edge).
     *
     * @since v2.2.0
     */
    public void setTxRequiredForSQLGraphOperations(final boolean iValue) {
      this.txRequiredForSQLGraphOperations = iValue;
    }

    /**
     * Returns true if usage of transactions is needed on graph modification for SQL commands (create/remove vertex, create/remove
     * edge).
     *
     * @since v2.2.0
     */
    public boolean isTxRequiredForSQLGraphOperations() {
      if (txRequiredForSQLGraphOperations == null) {
        return STANDARD_TX_REQUIRE_FOR_SQL_OPERATIONS;
      }
      return txRequiredForSQLGraphOperations;
    }

    /**
     * Returns true if it saves the original Id, otherwise false.
     */
    public boolean isSaveOriginalIds() {
      if (saveOriginalIds == null) {
        return SAVE_ORIGINAL_IDS_DEFAULT;
      }
      return saveOriginalIds;
    }

    /**
     * Changes the setting about usage of lightweight edges.
     */
    public void setSaveOriginalIds(final boolean saveIds) {
      saveOriginalIds = saveIds;
    }

    /**
     * Returns true if the references are kept in memory.
     */
    public boolean isKeepInMemoryReferences() {
      if (keepInMemoryReferences == null) {
        return KEEP_IN_MEMORY_REFERENCES_DEFAULT;
      }
      return keepInMemoryReferences;
    }

    /**
     * Changes the setting about using references in memory.
     */
    public void setKeepInMemoryReferences(boolean useReferences) {
      keepInMemoryReferences = useReferences;
    }

    /**
     * Returns true if the class are use for Edge labels.
     */
    public boolean isUseClassForEdgeLabel() {
      if (useClassForEdgeLabel == null) {
        return USE_CLASS_FOR_EDGE_LABEL_DEFAULT;
      }
      return useClassForEdgeLabel;
    }

    /**
     * Changes the setting to use the Edge class for Edge labels.
     */
    public void setUseClassForEdgeLabel(final boolean useCustomClassesForEdges) {
      useClassForEdgeLabel = useCustomClassesForEdges;
    }

    /**
     * Returns true if the class are use for Vertex labels.
     */
    public boolean isUseClassForVertexLabel() {
      if (useClassForVertexLabel == null) {
        return USE_CLASS_FOR_VERTEX_LABEL_DEFAULT;
      }
      return useClassForVertexLabel;
    }

    /**
     * Changes the setting to use the Vertex class for Vertex labels.
     */
    public void setUseClassForVertexLabel(final boolean useCustomClassesForVertex) {
      this.useClassForVertexLabel = useCustomClassesForVertex;
    }

    /**
     * Returns true if the out/in fields in vertex are post-fixed with edge labels. This improves traversal time by partitioning
     * edges on different collections, one per Edge's class.
     */
    public boolean isUseVertexFieldsForEdgeLabels() {
      if (useVertexFieldsForEdgeLabels == null) {
        return USE_VERTEX_FIELDS_FOR_EDGE_LABELS;
      }
      return useVertexFieldsForEdgeLabels;
    }

    /**
     * Changes the setting to postfix vertices fields with edge labels. This improves traversal time by partitioning edges on
     * different collections, one per Edge's class.
     */
    public void setUseVertexFieldsForEdgeLabels(final boolean useVertexFieldsForEdgeLabels) {
      this.useVertexFieldsForEdgeLabels = useVertexFieldsForEdgeLabels;
    }

    /**
     * Returns true if Blueprints standard exceptions are used:
     *

     *

     * IllegalStateException instead of ORecordNotFoundException when the record was not found
     *

     *

     */
    public boolean isStandardElementConstraints() {
      if (standardElementConstraints == null) {
        return STANDARD_ELEMENT_CONSTRAINTS_DEFAULT;
      }
      return standardElementConstraints;
    }

    /**
     * Changes the setting to apply the Blueprints standard constraints against elements.
     */
    public void setStandardElementConstraints(final boolean allowsPropertyValueNull) {
      this.standardElementConstraints = allowsPropertyValueNull;
    }

    /**
     * Returns true if the warning is generated on force the graph closing.
     */
    public boolean isStandardExceptions() {
      if (standardExceptions == null) {
        return STANDARD_EXCEPTIONS;
      }
      return standardExceptions;
    }

    /**
     * Changes the setting to throw Blueprints standard exceptions:
     *

     *

     * IllegalStateException instead of ORecordNotFoundException when the record was not found
     *

     *

     */
    public void setStandardExceptions(final boolean stdExceptions) {
      this.standardExceptions = stdExceptions;
    }

    /**
     * Returns true if the warning is generated on force the graph closing.
     */
    public boolean isWarnOnForceClosingTx() {
      if (warnOnForceClosingTx == null) {
        return WARN_ON_FORCE_CLOSING_TX_DEFAULT;
      }
      return warnOnForceClosingTx;
    }

    /**
     * Changes the setting to generate a warning if the graph closing has been forced.
     */
    public void setWarnOnForceClosingTx(final boolean warnOnSchemaChangeInTx) {
      this.warnOnForceClosingTx = warnOnSchemaChangeInTx;
    }

    /**
     * Returns the current thread mode:
     *

     *
MANUAL the user has to manually invoke the current database in Thread Local:
     * ODatabaseRecordThreadLocal.instance().set(graph.getRawGraph());

     *
AUTOSET_IFNULL (default) each call assures the current graph instance is set in the Thread Local only if no one
     * was set before

     *
ALWAYS_AUTOSET each call assures the current graph instance is set in the Thread Local

     *

     *
     * @return Current Graph instance to allow calls in chain (fluent interface)
     *
     * @see #setThreadMode(THREAD_MODE)
     */

    public THREAD_MODE getThreadMode() {
      if (threadMode == null) {
        return THREAD_MODE_DEFAULT;
      }
      return threadMode;
    }

    /**
     * Changes the thread mode:
     *

     *
MANUAL the user has to manually invoke the current database in Thread Local:
     * ODatabaseRecordThreadLocal.instance().set(graph.getRawGraph());

     *
AUTOSET_IFNULL (default) each call assures the current graph instance is set in the Thread Local only if no one
     * was set before

     *
ALWAYS_AUTOSET each call assures the current graph instance is set in the Thread Local

     *

     *
     * @param iControl Value to set
     *
     * @return Current Graph instance to allow calls in chain (fluent interface)
     *
     * @see #getThreadMode()
     */
    public void setThreadMode(final THREAD_MODE iControl) {
      this.threadMode = iControl;
    }

    /**
     * Returns the maximum number of retry in case of auto managed OConcurrentModificationException (like addEdge).
     */
    public int getMaxRetries() {
      if (maxRetries == null) {
        return STANDARD_MAX_RETRIES;
      }
      return maxRetries;
    }

    /**
     * Changes the maximum number of retry in case of auto managed OConcurrentModificationException (like addEdge).
     */
    public void setMaxRetries(final int maxRetries) {
      this.maxRetries = maxRetries;
    }
  }

  protected OrientConfigurableGraph() {
  }

  public abstract void declareIntent(OIntent iIntent);

  /**
   * Returns true if is using lightweight edges, otherwise false.
   */
  public boolean isUseLightweightEdges() {
    return settings.isUseLightweightEdges();
  }

  /**
   * Changes the setting about usage of lightweight edges.
   */
  public OrientConfigurableGraph setUseLightweightEdges(final boolean useDynamicEdges) {
    settings.setUseLightweightEdges(useDynamicEdges);
    return this;
  }

  /**
   * Returns true if usage of transactions is needed on graph modification for SQL commands (create/remove vertex, create/remove
   * edge).
   *
   * @since v2.2.0
   */
  public boolean isTxRequiredForSQLGraphOperations() {
    return settings.isTxRequiredForSQLGraphOperations();
  }

  /**
   * Changes the setting about usage of transactions on graph modification for SQL commands (create/remove vertex, create/remove
   * edge).
   *
   * @since v2.2.0
   */
  public OrientConfigurableGraph setTxRequiredForSQLGraphOperations(final boolean useTransaction) {
    settings.setTxRequiredForSQLGraphOperations(useTransaction);
    return this;
  }

  /**
   * Returns true if is using auto scale edge type, otherwise false.
   */
  public boolean isAutoScaleEdgeType() {
    return settings.isAutoScaleEdgeType();
  }

  /**
   * Changes the setting about usage of auto scale edge type.
   */
  public OrientConfigurableGraph setAutoScaleEdgeType(final boolean autoScaleEdgeType) {
    settings.setAutoScaleEdgeType(autoScaleEdgeType);
    return this;
  }

  /**
   * Returns the minimum number of edges for edge containers to transform the underlying structure from embedded to tree.
   */
  public int getEdgeContainerEmbedded2TreeThreshold() {
    return settings.getEdgeContainerEmbedded2TreeThreshold();
  }

  /**
   * Changes the minimum number of edges for edge containers to transform the underlying structure from embedded to tree. Use -1 to
   * disable transformation.
   */
  public OrientConfigurableGraph setEdgeContainerEmbedded2TreeThreshold(final int edgeContainerEmbedded2TreeThreshold) {
    this.settings.setEdgeContainerEmbedded2TreeThreshold(edgeContainerEmbedded2TreeThreshold);
    return this;
  }

  /**
   * Returns the minimum number of edges for edge containers to transform the underlying structure from tree to embedded.
   */
  public int getEdgeContainerTree2EmbeddedThreshold() {
    return settings.getEdgeContainerTree2EmbeddedThreshold();
  }

  /**
   * Changes the minimum number of edges for edge containers to transform the underlying structure from tree to embedded. Use -1 to
   * disable transformation.
   */
  public OrientConfigurableGraph setEdgeContainerTree2EmbeddedThreshold(final int edgeContainerTree2EmbeddedThreshold) {
    this.settings.edgeContainerTree2EmbeddedThreshold = edgeContainerTree2EmbeddedThreshold;
    return this;
  }

  /**
   * Tells if a transaction is started automatically when the graph is changed. This affects only when a transaction hasn't been
   * started. Default is true.
   *
   * @return
   */
  public boolean isAutoStartTx() {
    return settings.isAutoStartTx();
  }

  /**
   * If enabled auto starts a new transaction right before the graph is changed. This affects only when a transaction hasn't been
   * started. Default is true.
   *
   * @param autoStartTx
   */
  public void setAutoStartTx(final boolean autoStartTx) {
    this.settings.setAutoStartTx(autoStartTx);
  }

  public boolean isRequireTransaction() {
    return settings.isRequireTransaction();
  }

  public void setRequireTransaction(final boolean requireTransaction) {
    this.settings.setRequireTransaction(requireTransaction);
  }

  /**
   * Returns true if it saves the original Id, otherwise false.
   */
  public boolean isSaveOriginalIds() {
    return settings.isSaveOriginalIds();
  }

  /**
   * Changes the setting about usage of lightweight edges.
   */
  public OrientConfigurableGraph setSaveOriginalIds(final boolean saveIds) {
    settings.setSaveOriginalIds(saveIds);
    return this;
  }

  /**
   * Returns true if the references are kept in memory.
   */
  public boolean isKeepInMemoryReferences() {
    return settings.isKeepInMemoryReferences();
  }

  /**
   * Changes the setting about using references in memory.
   */
  public OrientConfigurableGraph setKeepInMemoryReferences(boolean useReferences) {
    settings.setKeepInMemoryReferences(useReferences);
    return this;
  }

  /**
   * Returns true if the class are use for Edge labels.
   */
  public boolean isUseClassForEdgeLabel() {
    return settings.isUseClassForEdgeLabel();
  }

  /**
   * Changes the setting to use the Edge class for Edge labels.
   */
  public OrientConfigurableGraph setUseClassForEdgeLabel(final boolean useCustomClassesForEdges) {
    settings.setUseClassForEdgeLabel(useCustomClassesForEdges);
    return this;
  }

  /**
   * Returns true if the class are use for Vertex labels.
   */
  public boolean isUseClassForVertexLabel() {
    return settings.isUseClassForVertexLabel();
  }

  /**
   * Changes the setting to use the Vertex class for Vertex labels.
   */
  public OrientConfigurableGraph setUseClassForVertexLabel(final boolean useCustomClassesForVertex) {
    this.settings.setUseClassForVertexLabel(useCustomClassesForVertex);
    return this;
  }

  /**
   * Returns true if the out/in fields in vertex are post-fixed with edge labels. This improves traversal time by partitioning edges
   * on different collections, one per Edge's class.
   */
  public boolean isUseVertexFieldsForEdgeLabels() {
    return settings.isUseVertexFieldsForEdgeLabels();
  }

  /**
   * Changes the setting to postfix vertices fields with edge labels. This improves traversal time by partitioning edges on
   * different collections, one per Edge's class.
   */
  @Deprecated
  public OrientConfigurableGraph setUseVertexFieldsForEdgeLabels(final boolean useVertexFieldsForEdgeLabels) {
//    this.settings.setUseVertexFieldsForEdgeLabels(useVertexFieldsForEdgeLabels);
    return this;
  }

  /**
   * Returns true if Blueprints standard constraints are applied to elements.
   */
  public boolean isStandardElementConstraints() {
    return settings.isStandardElementConstraints();
  }

  /**
   * Changes the setting to apply the Blueprints standard constraints against elements.
   */
  public OrientConfigurableGraph setStandardElementConstraints(final boolean allowsPropertyValueNull) {
    this.settings.setStandardElementConstraints(allowsPropertyValueNull);
    return this;
  }

  /**
   * Returns true if Blueprints standard exceptions are used:
   *

   *

   * IllegalStateException instead of ORecordNotFoundException when the record was not found
   *

   *

   */
  public boolean isStandardExceptions() {
    return settings.isStandardExceptions();
  }

  /**
   * Changes the setting to throw Blueprints standard exceptions:
   *

   *

   * IllegalStateException instead of ORecordNotFoundException when the record was not found
   *

   *

   */
  public OrientConfigurableGraph setStandardExceptions(final boolean stdExceptions) {
    this.settings.setStandardExceptions(stdExceptions);
    return this;
  }

  /**
   * Returns true if the warning is generated on force the graph closing.
   */
  public boolean isWarnOnForceClosingTx() {
    return settings.isWarnOnForceClosingTx();
  }

  /**
   * Changes the setting to generate a warning if the graph closing has been forced.
   */
  public OrientConfigurableGraph setWarnOnForceClosingTx(final boolean warnOnSchemaChangeInTx) {
    this.settings.setWarnOnForceClosingTx(warnOnSchemaChangeInTx);
    return this;
  }

  /**
   * Returns the current thread mode:
   *

   *
MANUAL the user has to manually invoke the current database in Thread Local:
   * ODatabaseRecordThreadLocal.instance().set(graph.getRawGraph());

   *
AUTOSET_IFNULL (default) each call assures the current graph instance is set in the Thread Local only if no one was
   * set before

   *
ALWAYS_AUTOSET each call assures the current graph instance is set in the Thread Local

   *

   *
   * @return Current Graph instance to allow calls in chain (fluent interface)
   *
   * @see #setThreadMode(THREAD_MODE)
   */

  public THREAD_MODE getThreadMode() {
    return settings.getThreadMode();
  }

  /**
   * Changes the thread mode:
   *

   *
MANUAL the user has to manually invoke the current database in Thread Local:
   * ODatabaseRecordThreadLocal.instance().set(graph.getRawGraph());

   *
AUTOSET_IFNULL (default) each call assures the current graph instance is set in the Thread Local only if no one was
   * set before

   *
ALWAYS_AUTOSET each call assures the current graph instance is set in the Thread Local

   *

   *
   * @param iControl Value to set
   *
   * @return Current Graph instance to allow calls in chain (fluent interface)
   *
   * @see #getThreadMode()
   */
  public OrientConfigurableGraph setThreadMode(final THREAD_MODE iControl) {
    this.settings.setThreadMode(iControl);
    return this;
  }

  public OrientConfigurableGraph setUseLog(final boolean useLog) {
    this.settings.useLog = useLog;
    return this;
  }

  public String getConnectionStrategy() {
    Object val = getProperty(OGlobalConfiguration.CLIENT_CONNECTION_STRATEGY.getKey());
    return val == null ? null : val.toString();
  }

  public void setConnectionStrategy(final String connectionStrategy) {
    this.setProperty(OGlobalConfiguration.CLIENT_CONNECTION_STRATEGY.getKey(),connectionStrategy);
    this.settings.setConnectionStrategy(connectionStrategy);
  }

  /**
   * Returns the maximum number of retries in case of auto managed OConcurrentModificationException (like addEdge).
   */
  public int getMaxRetries() {
    return this.settings.getMaxRetries();
  }

  /**
   * Changes the maximum number of retries in case of auto managed OConcurrentModificationException (like addEdge).
   */
  public void setMaxRetries(final int maxRetries) {
    this.settings.setMaxRetries(maxRetries);
  }

  /**
   * Builds a OrientGraph instance passing a configuration. Supported configuration settings are:
   *

   *

   * Name
   * Description
   * Default value
   *

   *

   * blueprints.orientdb.url
   * Database URL
   * -
   *

   *

   * blueprints.orientdb.username
   * User name
   * admin
   *

   *

   * blueprints.orientdb.password
   * User password
   * admin
   *

   *

   * blueprints.orientdb.saveOriginalIds
   * Saves the original element IDs by using the property origId. This could be useful on import of graph to preserve original
   * ids
   * false
   *

   *

   * blueprints.orientdb.keepInMemoryReferences
   * Avoid to keep records in memory but only RIDs
   * false
   *

   *

   * blueprints.orientdb.useCustomClassesForEdges
   * Use Edge's label as OrientDB class. If doesn't exist create it under the hood
   * true
   *

   *

   * blueprints.orientdb.useCustomClassesForVertex
   * Use Vertex's label as OrientDB class. If doesn't exist create it under the hood
   * true
   *

   *

   * blueprints.orientdb.useVertexFieldsForEdgeLabels
   * Store the edge relationships in vertex by using the Edge's class. This allow to use multiple fields and make faster
   * traversal by edge's label (class)
   * true
   *

   *

   * blueprints.orientdb.lightweightEdges
   * Uses lightweight edges. This avoid to create a physical document per edge. Documents are created only when they have
   * properties
   * true
   *

   *

   * blueprints.orientdb.autoScaleEdgeType
   * Set auto scale of edge type. True means one edge is managed as LINK, 2 or more are managed with a LINKBAG
   * false
   *

   *

   * blueprints.orientdb.edgeContainerEmbedded2TreeThreshold
   * Changes the minimum number of edges for edge containers to transform the underlying structure from embedded to tree. Use -1
   * to disable transformation
   * -1
   *

   *

   * blueprints.orientdb.edgeContainerTree2EmbeddedThreshold
   * Changes the minimum number of edges for edge containers to transform the underlying structure from tree to embedded. Use -1
   * to disable transformation
   * -1
   *

   *

   *
   * @param configuration of graph
   */
  protected void init(final Configuration configuration) {
    final Boolean saveOriginalIds = configuration.getBoolean("blueprints.orientdb.saveOriginalIds", null);
    if (saveOriginalIds != null)
      setSaveOriginalIds(saveOriginalIds);

    final Boolean keepInMemoryReferences = configuration.getBoolean("blueprints.orientdb.keepInMemoryReferences", null);
    if (keepInMemoryReferences != null)
      setKeepInMemoryReferences(keepInMemoryReferences);

    final Boolean useCustomClassesForEdges = configuration.getBoolean("blueprints.orientdb.useCustomClassesForEdges", null);
    if (useCustomClassesForEdges != null)
      setUseClassForEdgeLabel(useCustomClassesForEdges);

    final Boolean useCustomClassesForVertex = configuration.getBoolean("blueprints.orientdb.useCustomClassesForVertex", null);
    if (useCustomClassesForVertex != null)
      setUseClassForVertexLabel(useCustomClassesForVertex);

    final Boolean useVertexFieldsForEdgeLabels = configuration.getBoolean("blueprints.orientdb.useVertexFieldsForEdgeLabels", null);
    if (useVertexFieldsForEdgeLabels != null)
      setUseVertexFieldsForEdgeLabels(useVertexFieldsForEdgeLabels);

    final Boolean lightweightEdges = configuration.getBoolean("blueprints.orientdb.lightweightEdges", null);
    if (lightweightEdges != null)
      setUseLightweightEdges(lightweightEdges);

    final Boolean autoScaleEdgeType = configuration.getBoolean("blueprints.orientdb.autoScaleEdgeType", null);
    if (autoScaleEdgeType != null)
      setAutoScaleEdgeType(autoScaleEdgeType);

    final Boolean requireTransaction = configuration.getBoolean("blueprints.orientdb.requireTransaction", null);
    if (requireTransaction != null)
      setRequireTransaction(requireTransaction);

    final Boolean txRequiredForSQLGraphOperations = configuration
        .getBoolean("blueprints.orientdb.txRequiredForSQLGraphOperations", null);
    if (txRequiredForSQLGraphOperations != null)
      setTxRequiredForSQLGraphOperations(txRequiredForSQLGraphOperations);

    final Integer maxRetries = configuration.getInt("blueprints.orientdb.maxRetries", 50);
    if (maxRetries != null)
      setMaxRetries(maxRetries);
  }

  protected abstract Object setProperty(final String iName, final Object iValue);

  protected abstract Object getProperty(final String iName);

  protected abstract Map<String,Object> getProperties();

}


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

import com.orientechnologies.common.exception.OException;
import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.common.profiler.OAbstractProfiler.OProfilerHookValue;
import com.orientechnologies.common.profiler.OProfiler.METRIC_TYPE;
import com.orientechnologies.orient.core.Orient;
import com.orientechnologies.orient.core.command.OCommandRequestText;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.metadata.security.OToken;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSerializer;
import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSerializerFactory;
import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinary;
import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinaryProtocol;
import com.orientechnologies.orient.enterprise.channel.binary.OTokenSecurityException;
import com.orientechnologies.orient.server.network.protocol.ONetworkProtocol;
import com.orientechnologies.orient.server.network.protocol.binary.ONetworkProtocolBinary;
import com.orientechnologies.orient.server.plugin.OServerPluginHelper;

import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

public class OClientConnectionManager {
  private static final long TIMEOUT_PUSH = 3000;

  protected final ConcurrentMap<Integer, OClientConnection>  connections      = new ConcurrentHashMap<Integer, OClientConnection>();
  protected       AtomicInteger                              connectionSerial = new AtomicInteger(0);
  protected final ConcurrentMap<OHashToken, OClientSessions> sessions         = new ConcurrentHashMap<OHashToken, OClientSessions>();
  protected final TimerTask                                  timerTask;
  private         OServer                                    server;

  public OClientConnectionManager(OServer server) {
    final int delay = OGlobalConfiguration.SERVER_CHANNEL_CLEAN_DELAY.getValueAsInteger();

    timerTask = Orient.instance().scheduleTask(() -> {
      try {
        cleanExpiredConnections();
      } catch (Exception e) {
        OLogManager.instance().debug(this, "Error on client connection purge task", e);
      }
    }, delay, delay);

    Orient.instance().getProfiler()
        .registerHookValue("server.connections.actives", "Number of active network connections", METRIC_TYPE.COUNTER,
            new OProfilerHookValue() {
              public Object getValue() {
                return (long) connections.size();
              }
            });
    this.server = server;
  }

  public void cleanExpiredConnections() {
    final Iterator<Entry<Integer, OClientConnection>> iterator = connections.entrySet().iterator();
    while (iterator.hasNext()) {
      final Entry<Integer, OClientConnection> entry = iterator.next();

      if (entry.getValue().tryAcquireForExpire()) {
        try {

          final Socket socket;
          if (entry.getValue().getProtocol() == null || entry.getValue().getProtocol().getChannel() == null)
            socket = null;
          else
            socket = entry.getValue().getProtocol().getChannel().socket;

          if (socket == null || socket.isClosed() || socket.isInputShutdown()) {
            OLogManager.instance()
                .debug(this, "[OClientConnectionManager] found and removed pending closed channel %d (%s)", entry.getKey(), socket);
            try {
              OCommandRequestText command = entry.getValue().getData().command;
              if (command != null && command.isIdempotent()) {
                entry.getValue().getProtocol().sendShutdown();
                entry.getValue().getProtocol().interrupt();
              }
              removeConnectionFromSession(entry.getValue());
              entry.getValue().close();

            } catch (Exception e) {
              OLogManager.instance().error(this, "Error during close of connection for close channel", e);
            }
            iterator.remove();
          } else if (Boolean.TRUE.equals(entry.getValue().getTokenBased())) {
            if (entry.getValue().getToken() != null && !entry.getValue().getToken().isNowValid() && !entry.getValue().getToken()
                .getIsValid()) {
              // Close the current session but not the network because can be used by another session.
              removeConnectionFromSession(entry.getValue());
              entry.getValue().close();
              iterator.remove();
            }
          }
        } finally {
          entry.getValue().release();
        }
      }
    }
    server.getPushManager().cleanPushSockets();
  }

  /**
   * Create a connection.
   *
   * @param iProtocol protocol which will be used by connection
   *
   * @return new connection
   */
  public OClientConnection connect(final ONetworkProtocol iProtocol) {

    final OClientConnection connection;

    connection = new OClientConnection(connectionSerial.incrementAndGet(), iProtocol);

    connections.put(connection.getId(), connection);
    OLogManager.instance().config(this, "Remote client connected from: " + connection);
    OServerPluginHelper.invokeHandlerCallbackOnClientConnection(iProtocol.getServer(), connection);
    return connection;
  }

  /**
   * Create a connection.
   *
   * @param iProtocol protocol which will be used by connection
   *
   * @return new connection
   */
  public OClientConnection connect(final ONetworkProtocol iProtocol, final OClientConnection connection, final byte[] tokenBytes,
      final OTokenHandler handler) {

    final OToken token;
    try {
      token = handler.parseBinaryToken(tokenBytes);
    } catch (Exception e) {
      throw OException.wrapException(new OTokenSecurityException("Error on token parsing"), e);
    }
    OClientSessions session;
    synchronized (sessions) {
      session = new OClientSessions(tokenBytes, token);
      sessions.put(new OHashToken(tokenBytes), session);
    }
    connection.setTokenBytes(tokenBytes);
    connection.setTokenBased(true);
    connection.setToken(token);
    session.addConnection(connection);
    OLogManager.instance().config(this, "Remote client connected from: " + connection);
    OServerPluginHelper.invokeHandlerCallbackOnClientConnection(iProtocol.getServer(), connection);
    return connection;
  }

  public OClientConnection reConnect(final ONetworkProtocol iProtocol, final byte[] tokenBytes, final OToken token) {

    final OClientConnection connection;
    connection = new OClientConnection(connectionSerial.incrementAndGet(), iProtocol);

    connections.put(connection.getId(), connection);
    OHashToken key = new OHashToken(tokenBytes);
    OClientSessions sess;
    synchronized (sessions) {
      sess = sessions.get(key);
      if (sess == null) {
        // RECONNECT
        sess = new OClientSessions(tokenBytes, token);
        sessions.put(new OHashToken(tokenBytes), sess);
      }
    }
    connection.setTokenBytes(tokenBytes);
    connection.setTokenBased(true);
    connection.setToken(token);
    sess.addConnection(connection);
    OServerPluginHelper.invokeHandlerCallbackOnClientConnection(iProtocol.getServer(), connection);
    return connection;
  }

  /**
   * Retrieves the connection by id.
   *
   * @param iChannelId id of connection
   *
   * @return The connection if any, otherwise null
   */
  public OClientConnection getConnection(final int iChannelId, ONetworkProtocol protocol) {
    // SEARCH THE CONNECTION BY ID
    OClientConnection connection = connections.get(iChannelId);
    if (connection != null)
      connection.setProtocol(protocol);

    return connection;
  }

  /**
   * Retrieves the connection by address/port.
   *
   * @param iAddress The address as string in the format address as format <ip>:<port>
   *
   * @return The connection if any, otherwise null
   */
  public OClientConnection getConnection(final String iAddress) {
    for (OClientConnection conn : connections.values()) {
      if (iAddress.equals(conn.getRemoteAddress()))
        return conn;
    }
    return null;
  }

  /**
   * Disconnects and kill the associated network manager.
   *
   * @param iChannelId id of connection
   */
  public void kill(final int iChannelId) {
    kill(connections.get(iChannelId));
  }

  /**
   * Disconnects and kill the associated network manager.
   *
   * @param connection connection to kill
   */
  public void kill(final OClientConnection connection) {
    if (connection != null) {
      final ONetworkProtocol protocol = connection.getProtocol();

      try {
        // INTERRUPT THE NEWTORK MANAGER TOO
        protocol.interrupt();
      } catch (Exception e) {
        OLogManager.instance().error(this, "Error during interruption of binary protocol", e);
      }

      disconnect(connection);

      // KILL THE NETWORK MANAGER TOO
      protocol.sendShutdown();
    }
  }

  public boolean has(final int id) {
    return connections.containsKey(id);
  }

  /**
   * Interrupt the associated network manager.
   *
   * @param iChannelId id of connection
   */
  public void interrupt(final int iChannelId) {
    final OClientConnection connection = connections.get(iChannelId);
    if (connection != null) {
      final ONetworkProtocol protocol = connection.getProtocol();
      if (protocol != null)
        // INTERRUPT THE NEWTORK MANAGER
        protocol.softShutdown();
    }
  }

  /**
   * Disconnects a client connections
   *
   * @param iChannelId id of connection
   *
   * @return true if was last one, otherwise false
   */
  public boolean disconnect(final int iChannelId) {
    OLogManager.instance().debug(this, "Disconnecting connection with id=%d", iChannelId);

    final OClientConnection connection = connections.remove(iChannelId);

    if (connection != null) {
      OServerPluginHelper.invokeHandlerCallbackOnClientDisconnection(server, connection);
      connection.close();
      removeConnectionFromSession(connection);

      // CHECK IF THERE ARE OTHER CONNECTIONS
      for (Entry<Integer, OClientConnection> entry : connections.entrySet()) {
        if (entry.getValue().getProtocol().equals(connection.getProtocol())) {
          OLogManager.instance()
              .debug(this, "Disconnected connection with id=%d but are present other active channels", iChannelId);
          return false;
        }
      }

      OLogManager.instance().debug(this, "Disconnected connection with id=%d, no other active channels found", iChannelId);
      return true;
    }

    OLogManager.instance().debug(this, "Cannot find connection with id=%d", iChannelId);
    return false;
  }

  private void removeConnectionFromSession(OClientConnection connection) {
    if (connection.getProtocol() instanceof ONetworkProtocolBinary) {
      byte[] tokenBytes = connection.getTokenBytes();
      OHashToken hashToken = new OHashToken(tokenBytes);
      synchronized (sessions) {
        OClientSessions sess = sessions.get(hashToken);
        if (sess != null) {
          sess.removeConnection(connection);
          if (!sess.isActive()) {
            sessions.remove(hashToken);
          }
        }
      }
    }
  }

  public void disconnect(final OClientConnection iConnection) {
    OLogManager.instance().debug(this, "Disconnecting connection %s...", iConnection);
    OServerPluginHelper.invokeHandlerCallbackOnClientDisconnection(server, iConnection);
    removeConnectionFromSession(iConnection);
    iConnection.close();

    int totalRemoved = 0;
    for (Entry<Integer, OClientConnection> entry : new HashMap<Integer, OClientConnection>(connections).entrySet()) {
      final OClientConnection conn = entry.getValue();
      if (conn != null && conn.equals(iConnection)) {
        connections.remove(entry.getKey());
        totalRemoved++;
      }
    }

    OLogManager.instance().debug(this, "Disconnected connection %s found %d channels", iConnection, totalRemoved);

  }

  public List<OClientConnection> getConnections() {
    return new ArrayList<OClientConnection>(connections.values());
  }

  public int getTotal() {
    return connections.size();
  }

  /**
   * Pushes the distributed configuration to all the connected clients.
   */
  public void pushDistribCfg2Clients(final ODocument iConfig) {
    if (iConfig == null)
      return;

    final Set<String> pushed = new HashSet<String>();
    for (OClientConnection c : connections.values()) {
      if (!c.getData().supportsLegacyPushMessages)
        continue;

      try {
        final String remoteAddress = c.getRemoteAddress();
        if (pushed.contains(remoteAddress))
          // ALREADY SENT: JUMP IT
          continue;

      } catch (Exception e) {
        // SOCKET EXCEPTION SKIP IT
        continue;
      }

      if (!(c.getProtocol() instanceof ONetworkProtocolBinary) || c.getData().getSerializationImpl() == null)
        // INVOLVE ONLY BINARY PROTOCOLS
        continue;

      final ONetworkProtocolBinary p = (ONetworkProtocolBinary) c.getProtocol();
      final OChannelBinary channel = p.getChannel();
      final ORecordSerializer ser = ORecordSerializerFactory.instance().getFormat(c.getData().getSerializationImpl());
      if (ser == null)
        return;

      final byte[] content = ser.toStream(iConfig, false);

      try {
        // TRY ACQUIRING THE LOCK FOR MAXIMUM 3 SECS TO AVOID TO FREEZE CURRENT THREAD
        if (channel.tryAcquireWriteLock(TIMEOUT_PUSH)) {
          try {
            channel.writeByte(OChannelBinaryProtocol.PUSH_DATA);
            channel.writeInt(Integer.MIN_VALUE);
            channel.writeByte(OChannelBinaryProtocol.REQUEST_PUSH_DISTRIB_CONFIG);
            channel.writeBytes(content);
            channel.flush();

            pushed.add(c.getRemoteAddress());
            OLogManager.instance().debug(this, "Sent updated cluster configuration to the remote client %s", c.getRemoteAddress());

          } finally {
            channel.releaseWriteLock();
          }
        } else {
          OLogManager.instance()
              .info(this, "Timeout on sending updated cluster configuration to the remote client %s", c.getRemoteAddress());
        }
      } catch (Exception e) {
        OLogManager.instance().warn(this, "Cannot push cluster configuration to the client %s", e, c.getRemoteAddress());
      }
    }
  }

  public void shutdown() {
    timerTask.cancel();

    List<ONetworkProtocol> toWait = new ArrayList<ONetworkProtocol>();

    final Iterator<Entry<Integer, OClientConnection>> iterator = connections.entrySet().iterator();
    while (iterator.hasNext()) {
      final Entry<Integer, OClientConnection> entry = iterator.next();

      final ONetworkProtocol protocol = entry.getValue().getProtocol();

      if (protocol != null)
        protocol.sendShutdown();

      OLogManager.instance().debug(this, "Sending shutdown to thread %s", protocol);

      OCommandRequestText command = entry.getValue().getData().command;
      if (command != null && command.isIdempotent()) {
        protocol.interrupt();
      } else {
        if (protocol instanceof ONetworkProtocolBinary
            && ((ONetworkProtocolBinary) protocol).getRequestType() == OChannelBinaryProtocol.REQUEST_SHUTDOWN) {
          continue;
        }

        final Socket socket;
        if (protocol == null || protocol.getChannel() == null)
          socket = null;
        else
          socket = protocol.getChannel().socket;

        if (socket != null && !socket.isClosed() && !socket.isInputShutdown()) {
          try {
            OLogManager.instance().debug(this, "Closing input socket of thread %s", protocol);
            if (!(socket instanceof SSLSocket)) // An SSLSocket will throw an UnsupportedOperationException.
              socket.shutdownInput();
          } catch (IOException e) {
            OLogManager.instance()
                .debug(this, "Error on closing connection of %s client during shutdown", e, entry.getValue().getRemoteAddress());
          }
        }
        if (protocol.isAlive()) {
          if (protocol instanceof ONetworkProtocolBinary && ((ONetworkProtocolBinary) protocol).getRequestType() == -1) {
            try {
              OLogManager.instance().debug(this, "Closing socket of thread %s", protocol);
              protocol.getChannel().close();
            } catch (Exception e) {
              OLogManager.instance().debug(this, "Error during chanel close at shutdown", e);
            }
            OLogManager.instance().debug(this, "Sending interrupt signal to thread %s", protocol);
            protocol.interrupt();
          }
          toWait.add(protocol);
        }
      }
    }

    for (ONetworkProtocol protocol : toWait) {
      try {
        protocol.join(server.getContextConfiguration().getValueAsInteger(OGlobalConfiguration.SERVER_CHANNEL_CLEAN_DELAY));
        if (protocol.isAlive()) {
          protocol.interrupt();
          protocol.join();
        }
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
      }
    }

  }

  public void killAllChannels() {
    for (Map.Entry<Integer, OClientConnection> entry : connections.entrySet()) {
      try {
        ONetworkProtocol protocol = entry.getValue().getProtocol();

        protocol.getChannel().close();

        final Socket socket;
        if (protocol == null || protocol.getChannel() == null)
          socket = null;
        else
          socket = protocol.getChannel().socket;

        if (socket != null && !socket.isClosed() && !socket.isInputShutdown()) {
          if (!(socket instanceof SSLSocket)) // An SSLSocket will throw an UnsupportedOperationException.
            socket.shutdownInput();
        }

      } catch (Exception e) {
        OLogManager.instance().debug(this, "Error on killing connection to %s client", e, entry.getValue().getRemoteAddress());
      }
    }
  }

  public OClientSessions getSession(OClientConnection connection) {
    OHashToken key = new OHashToken(connection.getTokenBytes());
    synchronized (sessions) {
      return sessions.get(key);
    }
  }

}


/*
 *
 *  *  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.db.record.ridbag.embedded;

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.common.serialization.types.OIntegerSerializer;
import com.orientechnologies.common.util.OCommonConst;
import com.orientechnologies.common.util.OResettable;
import com.orientechnologies.common.util.OSizeable;
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.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.OMultiValueChangeEvent;
import com.orientechnologies.orient.core.db.record.OMultiValueChangeListener;
import com.orientechnologies.orient.core.db.record.ridbag.ORidBagDelegate;
import com.orientechnologies.orient.core.exception.OSerializationException;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.ORecordInternal;
import com.orientechnologies.orient.core.serialization.serializer.binary.impl.OLinkSerializer;
import com.orientechnologies.orient.core.storage.ridbag.sbtree.Change;

import java.util.*;

public class OEmbeddedRidBag implements ORidBagDelegate {
  private boolean contentWasChanged = false;

  private Object[] entries       = OCommonConst.EMPTY_OBJECT_ARRAY;
  private int      entriesLength = 0;

  private boolean convertToRecord = true;
  private int     size            = 0;

  private transient ORecord owner;

  private List<OMultiValueChangeListener<OIdentifiable, OIdentifiable>> changeListeners;

  @Override
  public void setSize(int size) {
    this.size = size;
  }

  private static enum Tombstone {
    TOMBSTONE
  }

  public Object[] getEntries() {
    return entries;
  }

  private final class EntriesIterator implements Iterator<OIdentifiable>, OResettable, OSizeable {
    private final boolean convertToRecord;
    private       int     currentIndex = -1;
    private       int     nextIndex    = -1;
    private       boolean currentRemoved;

    private EntriesIterator(boolean convertToRecord) {
      reset();
      this.convertToRecord = convertToRecord;
    }

    @Override
    public boolean hasNext() {
      //we may remove items in ridbag during iteration so we need to be sure that pointed item is not removed.
      if (nextIndex > -1) {
        if (entries[nextIndex] instanceof OIdentifiable)
          return true;

        nextIndex = nextIndex();
      }

      return nextIndex > -1;
    }

    @Override
    public OIdentifiable next() {
      currentRemoved = false;

      currentIndex = nextIndex;
      if (currentIndex == -1)
        throw new NoSuchElementException();

      Object nextValue = entries[currentIndex];

      //we may remove items in ridbag during iteration so we need to be sure that pointed item is not removed.
      if (!(nextValue instanceof OIdentifiable)) {
        nextIndex = nextIndex();

        currentIndex = nextIndex;
        if (currentIndex == -1)
          throw new NoSuchElementException();

        nextValue = entries[currentIndex];
      }

      if (!convertToRecord && nextValue != null) {
        if (((OIdentifiable) nextValue).getIdentity().isPersistent())
          entries[currentIndex] = ((OIdentifiable) nextValue).getIdentity();
      }

      nextIndex = nextIndex();

      final OIdentifiable identifiable = (OIdentifiable) nextValue;
      if (convertToRecord)
        return identifiable.getRecord();

      return identifiable;
    }

    @Override
    public void remove() {
      if (currentRemoved)
        throw new IllegalStateException("Current element has already been removed");

      if (currentIndex == -1)
        throw new IllegalStateException("Next method was not called for given iterator");

      currentRemoved = true;

      final OIdentifiable nextValue = (OIdentifiable) entries[currentIndex];
      entries[currentIndex] = Tombstone.TOMBSTONE;

      size--;
      contentWasChanged = true;
      if (OEmbeddedRidBag.this.owner != null)
        ORecordInternal.unTrack(OEmbeddedRidBag.this.owner, nextValue);

      fireCollectionChangedEvent(
          new OMultiValueChangeEvent<OIdentifiable, OIdentifiable>(OMultiValueChangeEvent.OChangeType.REMOVE, nextValue, null,
              nextValue));
    }

    protected void swapValueOnCurrent(OIdentifiable newValue) {
      if (currentRemoved)
        throw new IllegalStateException("Current element has already been removed");

      if (currentIndex == -1)
        throw new IllegalStateException("Next method was not called for given iterator");

      final OIdentifiable oldValue = (OIdentifiable) entries[currentIndex];
      entries[currentIndex] = newValue;

      contentWasChanged = true;

      fireCollectionChangedEvent(
          new OMultiValueChangeEvent<>(OMultiValueChangeEvent.OChangeType.UPDATE, oldValue, oldValue, newValue));
    }

    @Override
    public void reset() {
      currentIndex = -1;
      nextIndex = -1;
      currentRemoved = false;

      nextIndex = nextIndex();
    }

    @Override
    public int size() {
      return size;
    }

    private int nextIndex() {
      for (int i = currentIndex + 1; i < entriesLength; i++) {
        Object entry = entries[i];
        if (entry instanceof OIdentifiable)
          return i;
      }

      return -1;
    }
  }

  @Override
  public ORecord getOwner() {
    return owner;
  }

  @Override
  public boolean contains(OIdentifiable identifiable) {
    if (identifiable == null)
      return false;

    for (int i = 0; i < entriesLength; i++) {
      if (identifiable.equals(entries[i]))
        return true;
    }

    return false;
  }

  @Override
  public void setOwner(ORecord owner) {
    if (owner != null && this.owner != null && !this.owner.equals(owner)) {
      throw new IllegalStateException("This data structure is owned by document " + owner
          + " if you want to use it in other document create new rid bag instance and copy content of current one.");
    }
    if (this.owner != null) {
      for (int i = 0; i < entriesLength; i++) {
        final Object entry = entries[i];
        if (entry instanceof OIdentifiable) {
          ORecordInternal.unTrack(this.owner, (OIdentifiable) entry);
        }
      }
    }

    this.owner = owner;
    if (this.owner != null) {
      for (int i = 0; i < entriesLength; i++) {
        final Object entry = entries[i];
        if (entry instanceof OIdentifiable) {
          ORecordInternal.track(this.owner, (OIdentifiable) entry);
        }
      }
    }
  }

  @Override
  public void addAll(Collection<OIdentifiable> values) {
    for (OIdentifiable value : values)
      add(value);
  }

  @Override
  public void add(final OIdentifiable identifiable) {
    if (identifiable == null)
      throw new IllegalArgumentException("Impossible to add a null identifiable in a ridbag");

    addEntry(identifiable);

    size++;
    contentWasChanged = true;

    fireCollectionChangedEvent(
        new OMultiValueChangeEvent<OIdentifiable, OIdentifiable>(OMultiValueChangeEvent.OChangeType.ADD, identifiable,
            identifiable));
  }

  public OEmbeddedRidBag copy() {
    final OEmbeddedRidBag copy = new OEmbeddedRidBag();
    copy.contentWasChanged = contentWasChanged;
    copy.entries = entries;
    copy.entriesLength = entriesLength;
    copy.convertToRecord = convertToRecord;
    copy.size = size;
    copy.owner = owner;
    if (changeListeners != null) {
      copy.changeListeners = new LinkedList<OMultiValueChangeListener<OIdentifiable, OIdentifiable>>(changeListeners);
    }
    return copy;
  }

  @Override
  public void remove(OIdentifiable identifiable) {

    if (removeEntry(identifiable)) {
      size--;
      contentWasChanged = true;

      if (this.owner != null)
        ORecordInternal.unTrack(this.owner, identifiable);

      fireCollectionChangedEvent(
          new OMultiValueChangeEvent<OIdentifiable, OIdentifiable>(OMultiValueChangeEvent.OChangeType.REMOVE, identifiable, null,
              identifiable));
    }
  }

  /**
   * for internal use only
   *
   * @param index
   * @param newValue
   *
   * @return
   */
  public boolean swap(int index, OIdentifiable newValue) {
    EntriesIterator iter = (EntriesIterator) rawIterator();
    int currIndex = 0;
    while (iter.hasNext()) {
      iter.next();
      if (index == currIndex) {
        iter.swapValueOnCurrent(newValue);
        return true;
      }
      currIndex++;
    }
    return false;
  }

  @Override
  public boolean isEmpty() {
    return size == 0;
  }

  @Override
  public Iterator<OIdentifiable> iterator() {
    return new EntriesIterator(convertToRecord);
  }

  @Override
  public Iterator<OIdentifiable> rawIterator() {
    return new EntriesIterator(false);
  }

  @Override
  public void convertLinks2Records() {
    for (int i = 0; i < entriesLength; i++) {
      final Object entry = entries[i];

      if (entry instanceof OIdentifiable) {
        final OIdentifiable identifiable = (OIdentifiable) entry;
        ORecord record = identifiable.getRecord();
        if (record != null) {
          if (this.owner != null) {
            ORecordInternal.unTrack(this.owner, identifiable);
            ORecordInternal.track(this.owner, record);
          }
          entries[i] = record;
        }
      }
    }
  }

  @Override
  public boolean convertRecords2Links() {
    for (int i = 0; i < entriesLength; i++) {
      final Object entry = entries[i];

      if (entry instanceof OIdentifiable) {
        final OIdentifiable identifiable = (OIdentifiable) entry;
        if (identifiable instanceof ORecord) {
          final ORecord record = (ORecord) identifiable;

          entries[i] = record.getIdentity();
        }
      }
    }

    return true;
  }

  @Override
  public boolean isAutoConvertToRecord() {
    return convertToRecord;
  }

  @Override
  public void setAutoConvertToRecord(boolean convertToRecord) {
    this.convertToRecord = convertToRecord;
  }

  @Override
  public boolean detach() {
    return convertRecords2Links();
  }

  @Override
  public int size() {
    return size;
  }

  @Override
  public String toString() {
    if (size < 10) {
      final StringBuilder sb = new StringBuilder(256);
      sb.append('[');
      for (final Iterator<OIdentifiable> it = this.iterator(); it.hasNext(); ) {
        try {
          OIdentifiable e = it.next();
          if (e != null) {
            if (sb.length() > 1)
              sb.append(", ");

            sb.append(e.getIdentity());
          }
        } catch (NoSuchElementException ignore) {
          // IGNORE THIS
        }
      }
      return sb.append(']').toString();

    } else
      return "[size=" + size + "]";
  }

  @Override
  public void addChangeListener(final OMultiValueChangeListener<OIdentifiable, OIdentifiable> changeListener) {
    if (changeListeners == null)
      changeListeners = new LinkedList<OMultiValueChangeListener<OIdentifiable, OIdentifiable>>();
    changeListeners.add(changeListener);
  }

  @Override
  public void removeRecordChangeListener(final OMultiValueChangeListener<OIdentifiable, OIdentifiable> changeListener) {
    if (changeListeners != null)
      changeListeners.remove(changeListener);
  }

  @Override
  public Object returnOriginalState(List<OMultiValueChangeEvent<OIdentifiable, OIdentifiable>> multiValueChangeEvents) {
    final OEmbeddedRidBag reverted = new OEmbeddedRidBag();
    for (OIdentifiable identifiable : this)
      reverted.add(identifiable);

    final ListIterator<OMultiValueChangeEvent<OIdentifiable, OIdentifiable>> listIterator = multiValueChangeEvents
        .listIterator(multiValueChangeEvents.size());

    while (listIterator.hasPrevious()) {
      final OMultiValueChangeEvent<OIdentifiable, OIdentifiable> event = listIterator.previous();
      switch (event.getChangeType()) {
      case ADD:
        reverted.remove(event.getKey());
        break;
      case REMOVE:
        reverted.add(event.getOldValue());
        break;
      default:
        throw new IllegalArgumentException("Invalid change type : " + event.getChangeType());
      }
    }

    return reverted;
  }

  @Override
  public int getSerializedSize() {
    int size;

    size = OIntegerSerializer.INT_SIZE;

    size += this.size * OLinkSerializer.RID_SIZE;

    return size;
  }

  @Override
  public int getSerializedSize(byte[] stream, int offset) {
    return OIntegerSerializer.INSTANCE.deserializeLiteral(stream, offset) * OLinkSerializer.RID_SIZE + OIntegerSerializer.INT_SIZE;
  }

  @Override
  public int serialize(byte[] stream, int offset, UUID ownerUuid) {
    OIntegerSerializer.INSTANCE.serializeLiteral(size, stream, offset);
    offset += OIntegerSerializer.INT_SIZE;
    ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.instance().getIfDefined();
    final int totEntries = entries.length;
    for (int i = 0; i < totEntries; ++i) {
      final Object entry = entries[i];
      if (entry instanceof OIdentifiable) {
        OIdentifiable link = (OIdentifiable) entry;
        final ORID rid = link.getIdentity();
        if (db != null && !db.isClosed() && db.getTransaction().isActive()) {
          if (!link.getIdentity().isPersistent()) {
            link = db.getTransaction().getRecord(link.getIdentity());
          }
        }

        if (link == null)
          throw new OSerializationException("Found null entry in ridbag with rid=" + rid);

        entries[i] = link.getIdentity();
        OLinkSerializer.INSTANCE.serialize(link, stream, offset);
        offset += OLinkSerializer.RID_SIZE;
      }
    }

    return offset;
  }

  @Override
  public int deserialize(final byte[] stream, int offset) {
    this.size = OIntegerSerializer.INSTANCE.deserializeLiteral(stream, offset);
    int entriesSize = OIntegerSerializer.INSTANCE.deserializeLiteral(stream, offset);
    offset += OIntegerSerializer.INT_SIZE;

    for (int i = 0; i < entriesSize; i++) {
      ORID rid = OLinkSerializer.INSTANCE.deserialize(stream, offset);
      offset += OLinkSerializer.RID_SIZE;

      OIdentifiable identifiable = null;
      if (rid.isTemporary())
        identifiable = rid.getRecord();

      if (identifiable == null)
        identifiable = rid;

      if (identifiable == null)
        OLogManager.instance().warn(this, "Found null reference during ridbag deserialization (rid=%s)", rid);
      else
        addEntry(identifiable);
    }

    return offset;
  }

  @Override
  public void requestDelete() {
  }

  @Override
  public Class<?> getGenericClass() {
    return OIdentifiable.class;
  }

  @Override
  public List<OMultiValueChangeListener<OIdentifiable, OIdentifiable>> getChangeListeners() {
    if (changeListeners == null)
      return Collections.emptyList();
    return Collections.unmodifiableList(changeListeners);
  }

  @Override
  public void fireCollectionChangedEvent(final OMultiValueChangeEvent<OIdentifiable, OIdentifiable> event) {
    if (changeListeners != null) {
      for (final OMultiValueChangeListener<OIdentifiable, OIdentifiable> changeListener : changeListeners) {
        if (changeListener != null)
          changeListener.onAfterRecordChanged(event);
      }
    }
  }

  public void addEntry(final OIdentifiable identifiable) {
    if (entries.length == entriesLength) {
      if (entriesLength == 0) {
        final int cfgValue = OGlobalConfiguration.RID_BAG_EMBEDDED_TO_SBTREEBONSAI_THRESHOLD.getValueAsInteger();
        entries = new Object[cfgValue > 0 ? Math.min(cfgValue, 40) : 40];
      } else {
        final Object[] oldEntries = entries;
        entries = new Object[entries.length << 1];
        System.arraycopy(oldEntries, 0, entries, 0, oldEntries.length);
      }
    }
    if (this.owner != null)
      ORecordInternal.track(this.owner, identifiable);

    entries[entriesLength] = identifiable;
    entriesLength++;
  }

  private boolean removeEntry(OIdentifiable identifiable) {
    int i = 0;
    for (; i < entriesLength; i++) {
      final Object entry = entries[i];
      if (entry.equals(identifiable)) {
        entries[i] = Tombstone.TOMBSTONE;
        break;
      }
    }

    return i < entriesLength;
  }

  @Override
  public NavigableMap<OIdentifiable, Change> getChanges() {
    return null;
  }

  @Override
  public void replace(OMultiValueChangeEvent<Object, Object> event, Object newValue) {
    // do nothing not needed
  }
}


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

import com.orientechnologies.common.concur.lock.OLockException;
import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.cache.OLocalRecordCache;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ORecordOperation;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.storage.OStorage;
import com.orientechnologies.orient.core.storage.OStorageProxy;
import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage;

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

public abstract class OTransactionAbstract implements OTransaction {
  protected ODatabaseDocumentInternal           database;
  protected TXSTATUS                            status         = TXSTATUS.INVALID;
  protected ISOLATION_LEVEL                     isolationLevel = ISOLATION_LEVEL.READ_COMMITTED;
  protected Map<ORID, LockedRecordMetadata> locks          = new HashMap<ORID, LockedRecordMetadata>();

  public static final class LockedRecordMetadata {
    private final OStorage.LOCKING_STRATEGY strategy;
    private       int                       locksCount;

    public LockedRecordMetadata(OStorage.LOCKING_STRATEGY strategy) {
      this.strategy = strategy;
    }
  }

  protected OTransactionAbstract(final ODatabaseDocumentInternal iDatabase) {
    database = iDatabase;
  }

  public static void updateCacheFromEntries(final ODatabaseDocumentInternal database,
      final Iterable<? extends ORecordOperation> entries, final boolean updateStrategy) {
    final OLocalRecordCache dbCache = database.getLocalCache();

    for (ORecordOperation txEntry : entries) {
      if (!updateStrategy)
        // ALWAYS REMOVE THE RECORD FROM CACHE
        dbCache.deleteRecord(txEntry.getRecord().getIdentity());
      else if (txEntry.type == ORecordOperation.DELETED)
        // DELETION
        dbCache.deleteRecord(txEntry.getRecord().getIdentity());
      else if (txEntry.type == ORecordOperation.UPDATED || txEntry.type == ORecordOperation.CREATED)
        // UDPATE OR CREATE
        dbCache.updateRecord(txEntry.getRecord());
    }
  }

  @Override
  public ISOLATION_LEVEL getIsolationLevel() {
    return isolationLevel;
  }

  @Override
  public OTransaction setIsolationLevel(final ISOLATION_LEVEL isolationLevel) {
    if (isolationLevel == ISOLATION_LEVEL.REPEATABLE_READ && getDatabase().getStorage() instanceof OStorageProxy)
      throw new IllegalArgumentException("Remote storage does not support isolation level '" + isolationLevel + "'");

    this.isolationLevel = isolationLevel;
    return this;
  }

  public boolean isActive() {
    return status != TXSTATUS.INVALID && status != TXSTATUS.COMPLETED && status != TXSTATUS.ROLLED_BACK;
  }

  public TXSTATUS getStatus() {
    return status;
  }

  public ODatabaseDocumentInternal getDatabase() {
    return database;
  }

  /**
   * Closes the transaction and releases all the acquired locks.
   */
  @Override
  public void close() {
    for (Map.Entry<ORID, LockedRecordMetadata> lock : locks.entrySet()) {
      try {
        final LockedRecordMetadata lockedRecordMetadata = lock.getValue();

        if (lockedRecordMetadata.strategy.equals(OStorage.LOCKING_STRATEGY.EXCLUSIVE_LOCK)) {
          ((OAbstractPaginatedStorage) getDatabase().getStorage().getUnderlying()).releaseWriteLock(lock.getKey());
        } else if (lockedRecordMetadata.strategy.equals(OStorage.LOCKING_STRATEGY.SHARED_LOCK)) {
          ((OAbstractPaginatedStorage) getDatabase().getStorage().getUnderlying()).releaseReadLock(lock.getKey());
        }
      } catch (Exception e) {
        OLogManager.instance().debug(this, "Error on releasing lock against record " + lock.getKey(), e);
      }
    }
    locks.clear();
  }

  @Override
  public OTransaction lockRecord(final OIdentifiable iRecord, final OStorage.LOCKING_STRATEGY lockingStrategy) {
    database.internalLockRecord(iRecord, lockingStrategy);
    return this;
  }

  @Override
  public boolean isLockedRecord(final OIdentifiable iRecord) {
    final ORID rid = iRecord.getIdentity();
    final LockedRecordMetadata lockedRecordMetadata = locks.get(rid);

    if (lockedRecordMetadata == null || lockedRecordMetadata.locksCount == 0)
      return false;
    else
      return true;
  }

  @Override
  public OStorage.LOCKING_STRATEGY lockingStrategy(OIdentifiable record) {
    final ORID rid = record.getIdentity();
    final LockedRecordMetadata lockedRecordMetadata = locks.get(rid);

    if (lockedRecordMetadata == null || lockedRecordMetadata.locksCount == 0)
      return null;

    return lockedRecordMetadata.strategy;
  }

  @Override
  public OTransaction unlockRecord(final OIdentifiable iRecord) {
    database.internalUnlockRecord(iRecord);
    return this;
  }

  public abstract void internalRollback();

  public void trackLockedRecord(ORID rid, OStorage.LOCKING_STRATEGY lockingStrategy) {
    OTransactionAbstract.LockedRecordMetadata lockedRecordMetadata = locks.get(rid);
    boolean addItem = false;

    if (lockedRecordMetadata == null) {
      lockedRecordMetadata = new OTransactionAbstract.LockedRecordMetadata(lockingStrategy);
      addItem = true;
    } else if (lockedRecordMetadata.strategy != lockingStrategy) {
      assert lockedRecordMetadata.locksCount == 0;
      lockedRecordMetadata = new OTransactionAbstract.LockedRecordMetadata(lockingStrategy);
      addItem = true;
    }
    lockedRecordMetadata.locksCount++;
    if (addItem) {
      locks.put(rid, lockedRecordMetadata);
    }
  }

  public OStorage.LOCKING_STRATEGY trackUnlockRecord(ORID rid) {
    final LockedRecordMetadata lockedRecordMetadata = locks.get(rid);
    if (lockedRecordMetadata != null && lockedRecordMetadata.locksCount > 0) {
      lockedRecordMetadata.locksCount--;
      if (lockedRecordMetadata.locksCount == 0) {
        locks.remove(rid);
        return lockedRecordMetadata.strategy;
      }
    } else {
      throw new OLockException("Cannot unlock a never acquired lock");
    }
    return null;
  }

  public Map<ORID, LockedRecordMetadata> getInternalLocks() {
    return locks;
  }

  protected void setLocks(Map<ORID, LockedRecordMetadata> locks) {
    this.locks = locks;
  }

  public Set<ORID> getLockedRecords() {
    return locks.keySet();
  }

  public void setDatabase(ODatabaseDocumentInternal database) {
    this.database = database;
  }
}


package com.orientechnologies.orient.core.sql.executor;

import com.orientechnologies.common.concur.OTimeoutException;
import com.orientechnologies.orient.core.command.OCommandContext;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ridbag.ORidBag;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.record.impl.ODocument;

import java.util.Collection;
import java.util.Map;
import java.util.Optional;

/**
 * after an update of an edge, this step updates edge pointers on vertices to make the graph consistent again
 */
public class UpdateEdgePointersStep extends AbstractExecutionStep {

  public UpdateEdgePointersStep(OCommandContext ctx, boolean profilingEnabled) {
    super(ctx, profilingEnabled);
  }

  @Override
  public OResultSet syncPull(OCommandContext ctx, int nRecords) throws OTimeoutException {
    OResultSet upstream = getPrev().get().syncPull(ctx, nRecords);
    return new OResultSet() {
      @Override
      public boolean hasNext() {
        return upstream.hasNext();
      }

      @Override
      public OResult next() {
        OResult result = upstream.next();
        if (result instanceof OResultInternal) {
          handleUpdateEdge((ODocument) result.getElement().get().getRecord());
        }
        return result;
      }

      private void updateIn(OResult item) {

      }

      private void updateOut(OResult item) {

      }

      @Override
      public void close() {
        upstream.close();
      }

      @Override
      public Optional<OExecutionPlan> getExecutionPlan() {
        return null;
      }

      @Override
      public Map<String, Long> getQueryStats() {
        return null;
      }
    };
  }

  @Override
  public String prettyPrint(int depth, int indent) {
    String spaces = OExecutionStepInternal.getIndent(depth, indent);
    StringBuilder result = new StringBuilder();
    result.append(spaces);
    result.append("+ UPDATE EDGE POINTERS");
    return result.toString();
  }

  /**
   * handles vertex consistency after an UPDATE EDGE
   *
   * @param record the edge record
   */
  private void handleUpdateEdge(ODocument record) {
    Object currentOut = record.field("out");
    Object currentIn = record.field("in");

    Object prevOut = record.getOriginalValue("out");
    Object prevIn = record.getOriginalValue("in");

    // to manage subqueries
    if (currentOut instanceof Collection && ((Collection) currentOut).size() == 1) {
      currentOut = ((Collection) currentOut).iterator().next();
      record.setProperty("out", currentOut);
    }
    if (currentIn instanceof Collection && ((Collection) currentIn).size() == 1) {
      currentIn = ((Collection) currentIn).iterator().next();
      record.setProperty("in", currentIn);
    }

    validateOutInForEdge(record, currentOut, currentIn);

    changeVertexEdgePointer(record, (OIdentifiable) prevIn, (OIdentifiable) currentIn, "in");
    changeVertexEdgePointer(record, (OIdentifiable) prevOut, (OIdentifiable) currentOut, "out");
  }

  /**
   * updates old and new vertices connected to an edge after out/in update on the edge itself
   *
   * @param edge          the edge
   * @param prevVertex    the previously connected vertex
   * @param currentVertex the currently connected vertex
   * @param direction     the direction ("out" or "in")
   */
  private void changeVertexEdgePointer(ODocument edge, OIdentifiable prevVertex, OIdentifiable currentVertex, String direction) {
    if (prevVertex != null && !prevVertex.equals(currentVertex)) {
      String edgeClassName = edge.getClassName();
      if (edgeClassName.equalsIgnoreCase("E")) {
        edgeClassName = "";
      }
      String vertexFieldName = direction + "_" + edgeClassName;
      ODocument prevOutDoc = ((OIdentifiable) prevVertex).getRecord();
      ORidBag prevBag = prevOutDoc.field(vertexFieldName);
      if (prevBag != null) {
        prevBag.remove(edge);
        prevOutDoc.save();
      }

      ODocument currentVertexDoc = ((OIdentifiable) currentVertex).getRecord();
      ORidBag currentBag = currentVertexDoc.field(vertexFieldName);
      if (currentBag == null) {
        currentBag = new ORidBag();
        currentVertexDoc.field(vertexFieldName, currentBag);
      }
      currentBag.add(edge);
    }
  }

  private void validateOutInForEdge(ODocument record, Object currentOut, Object currentIn) {
    if (!isRecordInstanceOf(currentOut, "V")) {
      throw new OCommandExecutionException("Error updating edge: 'out' is not a vertex - " + currentOut + "");
    }
    if (!isRecordInstanceOf(currentIn, "V")) {
      throw new OCommandExecutionException("Error updating edge: 'in' is not a vertex - " + currentIn + "");
    }
  }

  /**
   * checks if an object is an OIdentifiable and an instance of a particular (schema) class
   *
   * @param iRecord     The record object
   * @param orientClass The schema class
   *
   * @return
   */
  private boolean isRecordInstanceOf(Object iRecord, String orientClass) {
    if (iRecord == null) {
      return false;
    }
    if (!(iRecord instanceof OIdentifiable)) {
      return false;
    }
    ODocument record = ((OIdentifiable) iRecord).getRecord();
    if (iRecord == null) {
      return false;
    }
    return (record.getSchemaClass().isSubClassOf(orientClass));
  }
}


/*
 *
 *  *  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.common.serialization.OBinaryConverter;
import com.orientechnologies.common.serialization.OBinaryConverterFactory;
import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * @author Ilya Bershadskiy (ibersh20-at-gmail.com)
 * @since 18.01.12
 */
public class OCharSerializer implements OBinarySerializer<Character> {
  /**
   * size of char value in bytes
   */
  public static final  int              CHAR_SIZE        = 2;
  public static final  byte             ID               = 3;
  private static final OBinaryConverter BINARY_CONVERTER = OBinaryConverterFactory.getConverter();
  public static final  OCharSerializer  INSTANCE         = new OCharSerializer();

  public int getObjectSize(final Character object, Object... hints) {
    return CHAR_SIZE;
  }

  public void serialize(final Character object, final byte[] stream, final int startPosition, final Object... hints) {
    serializeLiteral(object.charValue(), stream, startPosition);
  }

  public void serializeLiteral(final char value, final byte[] stream, final int startPosition) {
    stream[startPosition] = (byte) (value >>> 8);
    stream[startPosition + 1] = (byte) (value);
  }

  public Character deserialize(final byte[] stream, final int startPosition) {
    return deserializeLiteral(stream, startPosition);
  }

  public char deserializeLiteral(final byte[] stream, final int startPosition) {
    return (char) (((stream[startPosition] & 0xFF) << 8) + (stream[startPosition + 1] & 0xFF));
  }

  public int getObjectSize(final byte[] stream, final int startPosition) {
    return CHAR_SIZE;
  }

  public byte getId() {
    return ID;
  }

  public int getObjectSizeNative(byte[] stream, int startPosition) {
    return CHAR_SIZE;
  }

  @Override
  public void serializeNativeObject(Character object, byte[] stream, int startPosition, Object... hints) {
    BINARY_CONVERTER.putChar(stream, startPosition, object, ByteOrder.nativeOrder());
  }

  @Override
  public Character deserializeNativeObject(final byte[] stream, final int startPosition) {
    return BINARY_CONVERTER.getChar(stream, startPosition, ByteOrder.nativeOrder());
  }

  public void serializeNative(final char object, final byte[] stream, final int startPosition, final Object... hints) {
    BINARY_CONVERTER.putChar(stream, startPosition, object, ByteOrder.nativeOrder());
  }

  public char deserializeNative(final byte[] stream, final int startPosition) {
    return BINARY_CONVERTER.getChar(stream, startPosition, ByteOrder.nativeOrder());
  }

  public boolean isFixedLength() {
    return true;
  }

  public int getFixedLength() {
    return CHAR_SIZE;
  }

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

  /**
   * {@inheritDoc}
   */
  @Override
  public void serializeInByteBufferObject(Character object, ByteBuffer buffer, Object... hints) {
    buffer.putChar(object);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Character deserializeFromByteBufferObject(ByteBuffer buffer) {
    return buffer.getChar();
  }

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

  /**
   * {@inheritDoc}
   */
  @Override
  public Character deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) {
    return (char) walChanges.getShortValue(buffer, offset);
  }

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