Package com.orientechnologies.orient.core.metadata.schema

Source Code of com.orientechnologies.orient.core.metadata.schema.OClassImpl

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

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.orientechnologies.common.listener.OProgressListener;
import com.orientechnologies.common.util.OArrays;
import com.orientechnologies.orient.core.annotation.OBeforeSerialization;
import com.orientechnologies.orient.core.command.OCommandResultListener;
import com.orientechnologies.orient.core.db.ODatabaseComplex;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.OScenarioThreadLocal;
import com.orientechnologies.orient.core.db.record.ODatabaseRecord;
import com.orientechnologies.orient.core.db.record.ODatabaseRecordInternal;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ORecordElement;
import com.orientechnologies.orient.core.exception.OSchemaException;
import com.orientechnologies.orient.core.exception.OSecurityAccessException;
import com.orientechnologies.orient.core.exception.OSecurityException;
import com.orientechnologies.orient.core.id.OClusterPosition;
import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.index.OIndex;
import com.orientechnologies.orient.core.index.OIndexDefinition;
import com.orientechnologies.orient.core.index.OIndexDefinitionFactory;
import com.orientechnologies.orient.core.index.OIndexException;
import com.orientechnologies.orient.core.index.OIndexManager;
import com.orientechnologies.orient.core.metadata.schema.clusterselection.OClusterSelectionStrategy;
import com.orientechnologies.orient.core.metadata.security.ODatabaseSecurityResources;
import com.orientechnologies.orient.core.metadata.security.ORole;
import com.orientechnologies.orient.core.metadata.security.OSecurityShared;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.ORecordInternal;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.serialization.OBinaryProtocol;
import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper;
import com.orientechnologies.orient.core.serialization.serializer.record.ORecordSerializerFactory;
import com.orientechnologies.orient.core.serialization.serializer.record.string.ORecordSerializerSchemaAware2CSV;
import com.orientechnologies.orient.core.sql.OCommandSQL;
import com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery;
import com.orientechnologies.orient.core.storage.OAutoshardedStorage;
import com.orientechnologies.orient.core.storage.OPhysicalPosition;
import com.orientechnologies.orient.core.storage.ORawBuffer;
import com.orientechnologies.orient.core.storage.OStorage;
import com.orientechnologies.orient.core.storage.OStorageEmbedded;
import com.orientechnologies.orient.core.storage.OStorageProxy;
import com.orientechnologies.orient.core.type.ODocumentWrapper;
import com.orientechnologies.orient.core.type.ODocumentWrapperNoClass;

/**
* Schema Class implementation.
*
* @author Luca Garulli (l.garulli--at--orientechnologies.com)
*
*/
@SuppressWarnings("unchecked")
public class OClassImpl extends ODocumentWrapperNoClass implements OClass {
  private static final long                  serialVersionUID        = 1L;
  private static final int                   NOT_EXISTENT_CLUSTER_ID = -1;

  private int                                defaultClusterId        = NOT_EXISTENT_CLUSTER_ID;
  final OSchemaShared                        owner;
  private final Map<String, OProperty>       properties              = new HashMap<String, OProperty>();
  private String                             name;
  private Class<?>                           javaClass;
  private int[]                              clusterIds;
  private OClassImpl                         superClass;
  private int[]                              polymorphicClusterIds;
  private List<OClass>                       baseClasses;
  private float                              overSize                = 0f;
  private String                             shortName;
  private boolean                            strictMode              = false;                           // @SINCE v1.0rc8
  private boolean                            abstractClass           = false;                           // @SINCE v1.2.0
  private Map<String, String>                customFields;
  private volatile OClusterSelectionStrategy clusterSelection;                                          // @SINCE 1.7

  /**
   * Constructor used in unmarshalling.
   */
  protected OClassImpl(final OSchemaShared iOwner) {
    this(iOwner, new ODocument());
  }

  protected OClassImpl(final OSchemaShared iOwner, final String iName, final int[] iClusterIds) {
    this(iOwner);
    name = iName;
    setClusterIds(iClusterIds);
    defaultClusterId = iClusterIds[0];
    if (defaultClusterId == NOT_EXISTENT_CLUSTER_ID)
      abstractClass = true;

    if (abstractClass)
      setPolymorphicClusterIds(new int[0]);
    else
      setPolymorphicClusterIds(iClusterIds);

    clusterSelection = owner.getClusterSelectionFactory().newInstanceOfDefaultClass();
  }

  /**
   * Constructor used in unmarshalling.
   */
  protected OClassImpl(final OSchemaShared iOwner, final ODocument iDocument) {
    document = iDocument;
    owner = iOwner;
  }

  public static int[] readableClusters(final ODatabaseRecord iDatabase, final int[] iClusterIds) {
    List<Integer> listOfReadableIds = new ArrayList<Integer>();

    boolean all = true;
    for (int clusterId : iClusterIds) {
      try {
        String clusterName = iDatabase.getClusterNameById(clusterId);
        iDatabase.checkSecurity(ODatabaseSecurityResources.CLUSTER, ORole.PERMISSION_READ, clusterName);
        listOfReadableIds.add(clusterId);
      } catch (OSecurityAccessException securityException) {
        all = false;
        // if the cluster is inaccessible it's simply not processed in the list.add
      }
    }

    if (all)
      // JUST RETURN INPUT ARRAY (FASTER)
      return iClusterIds;

    int[] readableClusterIds = new int[listOfReadableIds.size()];
    int index = 0;
    for (int clusterId : listOfReadableIds) {
      readableClusterIds[index++] = clusterId;
    }

    return readableClusterIds;
  }

  @Override
  public OClusterSelectionStrategy getClusterSelection() {
    acquireSchemaReadLock();
    try {
      return clusterSelection;
    } finally {
      releaseSchemaReadLock();
    }
  }

  @Override
  public OClass setClusterSelection(OClusterSelectionStrategy clusterSelection) {
    return setClusterSelection(clusterSelection.getName());
  }

  @Override
  public OClass setClusterSelection(final String value) {
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      final ODatabaseRecordInternal database = getDatabase();
      final OStorage storage = database.getStorage();

      if (storage instanceof OStorageProxy) {
        final String cmd = String.format("alter class %s clusterselection %s", name, value);
        database.command(new OCommandSQL(cmd)).execute();
      } else if (isDistributedCommand()) {
        final String cmd = String.format("alter class %s clusterselection %s", name, value);
        OCommandSQL commandSQL = new OCommandSQL(cmd);
        commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId());
        database.command(new OCommandSQL(cmd)).execute();

        setClusterSelectionInternal(value);
      } else
        setClusterSelectionInternal(value);

      return this;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  @Override
  public <T> T newInstance() throws InstantiationException, IllegalAccessException {
    acquireSchemaReadLock();
    try {
      if (javaClass == null)
        throw new IllegalArgumentException("Cannot create an instance of class '" + name + "' since no Java class was specified");

      return (T) javaClass.newInstance();
    } finally {
      releaseSchemaReadLock();
    }
  }

  @Override
  public <RET extends ODocumentWrapper> RET reload() {
    return (RET) owner.reload();
  }

  public String getCustom(final String iName) {
    acquireSchemaReadLock();
    try {
      if (customFields == null)
        return null;

      return customFields.get(iName);
    } finally {
      releaseSchemaReadLock();
    }
  }

  public OClassImpl setCustom(final String name, final String value) {
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      final ODatabaseRecordInternal database = getDatabase();
      final OStorage storage = database.getStorage();

      if (storage instanceof OStorageProxy) {
        final String cmd = String.format("alter class %s custom %s=%s", getName(), name, value);
        database.command(new OCommandSQL(cmd)).execute();
      } else if (isDistributedCommand()) {
        final String cmd = String.format("alter class %s custom %s=%s", getName(), name, value);
        final OCommandSQL commandSQL = new OCommandSQL(cmd);
        commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId());

        database.command(new OCommandSQL(cmd)).execute();

        setCustomInternal(name, value);
      } else
        setCustomInternal(name, value);

      return this;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public Map<String, String> getCustomInternal() {
    acquireSchemaReadLock();
    try {
      if (customFields != null)
        return Collections.unmodifiableMap(customFields);
      return null;
    } finally {
      releaseSchemaReadLock();
    }
  }

  public void removeCustom(final String name) {
    setCustom(name, null);
  }

  public void clearCustom() {
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      final ODatabaseRecordInternal database = getDatabase();
      final OStorage storage = database.getStorage();

      if (storage instanceof OStorageProxy) {
        final String cmd = String.format("alter class %s custom clear", getName());
        database.command(new OCommandSQL(cmd)).execute();
      } else if (isDistributedCommand()) {
        final String cmd = String.format("alter class %s custom clear", getName());
        final OCommandSQL commandSQL = new OCommandSQL(cmd);
        commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId());
        database.command(new OCommandSQL(cmd)).execute();

        clearCustomInternal();
      } else
        clearCustomInternal();

    } finally {
      releaseSchemaWriteLock();
    }
  }

  public Set<String> getCustomKeys() {
    acquireSchemaReadLock();
    try {
      if (customFields != null)
        return Collections.unmodifiableSet(customFields.keySet());
      return new HashSet<String>();
    } finally {
      releaseSchemaReadLock();
    }
  }

  @Override
  public boolean hasClusterId(int clusterId) {
    return Arrays.binarySearch(clusterIds, clusterId) >= 0;
  }

  public OClass getSuperClass() {
    acquireSchemaReadLock();
    try {
      return superClass;
    } finally {
      releaseSchemaReadLock();
    }
  }

  /**
   * Set the super class.
   *
   * @param superClass
   *          Super class as OClass instance
   * @return the object itself.
   */
  public OClass setSuperClass(final OClass superClass) {
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_UPDATE);
    acquireSchemaWriteLock();
    try {
      final ODatabaseRecordInternal database = getDatabase();
      final OStorage storage = database.getStorage();

      if (storage instanceof OStorageProxy) {
        final String cmd = String.format("alter class %s superclass %s", name, superClass != null ? superClass.getName() : null);
        database.command(new OCommandSQL(cmd)).execute();
      } else if (isDistributedCommand()) {
        final String cmd = String.format("alter class %s superclass %s", name, superClass != null ? superClass.getName() : null);
        final OCommandSQL commandSQL = new OCommandSQL(cmd);
        commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId());

        database.command(commandSQL).execute();

        setSuperClassInternal(superClass);
      } else
        setSuperClassInternal(superClass);

    } finally {
      releaseSchemaWriteLock();
    }
    return this;
  }

  void setSuperClassInternal(final OClass superClass) {
    acquireSchemaWriteLock();
    try {
      final OClassImpl cls;

      if (superClass instanceof OClassAbstractDelegate)
        cls = (OClassImpl) ((OClassAbstractDelegate) superClass).delegate;
      else
        cls = (OClassImpl) superClass;

      if (cls != null)
        cls.addBaseClasses(this);
      else if (this.superClass != null)
        // REMOVE THE PREVIOUS ONE
        this.superClass.removeBaseClassInternal(this);

      this.superClass = cls;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public String getName() {
    acquireSchemaReadLock();
    try {
      return name;
    } finally {
      releaseSchemaReadLock();
    }
  }

  public OClass setName(final String name) {
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_UPDATE);
    acquireSchemaWriteLock();
    try {
      final ODatabaseRecordInternal database = getDatabase();
      final OStorage storage = database.getStorage();

      if (storage instanceof OStorageProxy) {
        final String cmd = String.format("alter class %s name %s", this.name, name);
        database.command(new OCommandSQL(cmd)).execute();
      } else if (isDistributedCommand()) {
        final String cmd = String.format("alter class %s name %s", this.name, name);
        final OCommandSQL commandSQL = new OCommandSQL(cmd);
        commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId());

        database.command(commandSQL).execute();

        setNameInternal(name);
      } else
        setNameInternal(name);

    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  public long getSize() {
    acquireSchemaReadLock();
    try {
      long size = 0;
      for (int clusterId : clusterIds)
        size += getDatabase().getClusterRecordSizeById(clusterId);

      return size;
    } finally {
      releaseSchemaReadLock();
    }
  }

  public String getShortName() {
    acquireSchemaReadLock();
    try {
      return shortName;
    } finally {
      releaseSchemaReadLock();
    }
  }

  public OClass setShortName(String shortName) {
    if (shortName != null) {
      shortName = shortName.trim();
      if (shortName.isEmpty())
        shortName = null;
    }
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      final ODatabaseRecordInternal database = getDatabase();
      final OStorage storage = database.getStorage();

      if (storage instanceof OStorageProxy) {
        final String cmd = String.format("alter class %s shortname %s", name, shortName);
        database.command(new OCommandSQL(cmd)).execute();
      } else if (isDistributedCommand()) {

        final String cmd = String.format("alter class %s shortname %s", name, shortName);
        final OCommandSQL commandSQL = new OCommandSQL(cmd);
        commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId());

        database.command(commandSQL).execute();

        setShortNameInternal(shortName);
      } else
        setShortNameInternal(shortName);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  public String getStreamableName() {
    acquireSchemaReadLock();
    try {
      return shortName != null ? shortName : name;
    } finally {
      releaseSchemaReadLock();
    }
  }

  public Collection<OProperty> declaredProperties() {
    acquireSchemaReadLock();
    try {
      return Collections.unmodifiableCollection(properties.values());
    } finally {
      releaseSchemaReadLock();
    }
  }

  public Map<String, OProperty> propertiesMap() {
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_READ);

    acquireSchemaReadLock();
    try {
      final Map<String, OProperty> props = new HashMap<String, OProperty>(20);

      OClassImpl currentClass = this;
      do {

        for (OProperty p : currentClass.properties.values()) {
          final String propName = p.getName();

          if (!props.containsKey(propName))
            props.put(propName, p);
        }

        currentClass = (OClassImpl) currentClass.getSuperClass();

      } while (currentClass != null);

      return props;
    } finally {
      releaseSchemaReadLock();
    }
  }

  public Collection<OProperty> properties() {
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_READ);

    acquireSchemaReadLock();
    try {
      final Collection<OProperty> props = new ArrayList<OProperty>();

      OClassImpl currentClass = this;
      do {
        props.addAll(currentClass.properties.values());

        currentClass = (OClassImpl) currentClass.getSuperClass();

      } while (currentClass != null);

      return props;
    } finally {
      releaseSchemaReadLock();
    }
  }

  public Collection<OProperty> getIndexedProperties() {
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_READ);

    acquireSchemaReadLock();
    try {
      Collection<OProperty> indexedProps = null;

      OClassImpl currentClass = this;

      do {
        for (OProperty p : currentClass.properties.values())
          if (areIndexed(p.getName())) {
            if (indexedProps == null)
              indexedProps = new ArrayList<OProperty>();
            indexedProps.add(p);
          }

        currentClass = (OClassImpl) currentClass.getSuperClass();

      } while (currentClass != null);

      return (Collection<OProperty>) (indexedProps != null ? indexedProps : Collections.emptyList());
    } finally {
      releaseSchemaReadLock();
    }
  }

  public OProperty getProperty(String propertyName) {
    acquireSchemaReadLock();
    try {
      propertyName = propertyName.toLowerCase();

      OClassImpl currentClass = this;
      do {
        final OProperty p = currentClass.properties.get(propertyName);

        if (p != null)
          return p;

        currentClass = (OClassImpl) currentClass.getSuperClass();

      } while (currentClass != null);

      return null;
    } finally {
      releaseSchemaReadLock();
    }
  }

  public OProperty createProperty(final String iPropertyName, final OType iType) {
    return addProperty(iPropertyName, iType, null, null);
  }

  public OProperty createProperty(final String iPropertyName, final OType iType, final OClass iLinkedClass) {
    if (iLinkedClass == null)
      throw new OSchemaException("Missing linked class");

    return addProperty(iPropertyName, iType, null, iLinkedClass);
  }

  public OProperty createProperty(final String iPropertyName, final OType iType, final OType iLinkedType) {
    return addProperty(iPropertyName, iType, iLinkedType, null);
  }

  public boolean existsProperty(final String iPropertyName) {
    acquireSchemaReadLock();
    try {
      return properties.containsKey(iPropertyName.toLowerCase());
    } finally {
      releaseSchemaReadLock();
    }
  }

  public void dropProperty(final String propertyName) {
    if (getDatabase().getTransaction().isActive())
      throw new IllegalStateException("Cannot drop a property inside a transaction");

    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_DELETE);

    final String lowerName = propertyName.toLowerCase();

    acquireSchemaWriteLock();
    try {
      if (!properties.containsKey(lowerName))
        throw new OSchemaException("Property '" + propertyName + "' not found in class " + name + "'");

      final ODatabaseRecordInternal database = getDatabase();
      final OStorage storage = database.getStorage();
      if (storage instanceof OStorageProxy) {
        database.command(new OCommandSQL("drop property " + name + '.' + propertyName)).execute();
      } else if (isDistributedCommand()) {
        final OCommandSQL commandSQL = new OCommandSQL("drop property " + name + '.' + propertyName);
        commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId());

        database.command(commandSQL).execute();

        dropPropertyInternal(propertyName);
      } else
        dropPropertyInternal(propertyName);

    } finally {
      releaseSchemaWriteLock();
    }
  }

  @Override
  public void fromStream() {
    baseClasses = null;
    superClass = null;

    name = document.field("name");
    if (document.containsField("shortName"))
      shortName = document.field("shortName");
    else
      shortName = null;
    defaultClusterId = document.field("defaultClusterId");
    if (document.containsField("strictMode"))
      strictMode = document.field("strictMode");
    else
      strictMode = false;

    if (document.containsField("abstract"))
      abstractClass = document.field("abstract");
    else
      abstractClass = false;

    if (document.field("overSize") != null)
      overSize = document.field("overSize");
    else
      overSize = 0f;

    final Object cc = document.field("clusterIds");
    if (cc instanceof Collection<?>) {
      final Collection<Integer> coll = document.field("clusterIds");
      clusterIds = new int[coll.size()];
      int i = 0;
      for (final Integer item : coll)
        clusterIds[i++] = item;
    } else
      clusterIds = (int[]) cc;
    Arrays.sort(clusterIds);

    if (clusterIds.length == 1 && clusterIds[0] == -1)
      setPolymorphicClusterIds(new int[0]);
    else
      setPolymorphicClusterIds(clusterIds);

    // READ PROPERTIES
    OPropertyImpl prop;

    final Map<String, OProperty> newProperties = new HashMap<String, OProperty>();
    final Collection<ODocument> storedProperties = document.field("properties");

    if (storedProperties != null)
      for (ODocument p : storedProperties) {
        prop = new OPropertyImpl(this, p);
        prop.fromStream();

        if (properties.containsKey(prop.getName())) {
          prop = (OPropertyImpl) properties.get(prop.getName().toLowerCase());
          prop.fromStream(p);
        }

        newProperties.put(prop.getName().toLowerCase(), prop);
      }

    properties.clear();
    properties.putAll(newProperties);
    customFields = document.field("customFields", OType.EMBEDDEDMAP);
    clusterSelection = owner.getClusterSelectionFactory().getStrategy((String) document.field("clusterSelection"));
  }

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

    try {
      document.field("name", name);
      document.field("shortName", shortName);
      document.field("defaultClusterId", defaultClusterId);
      document.field("clusterIds", clusterIds);
      document.field("clusterSelection", clusterSelection.getName());
      document.field("overSize", overSize);
      document.field("strictMode", strictMode);
      document.field("abstract", abstractClass);

      final Set<ODocument> props = new LinkedHashSet<ODocument>();
      for (final OProperty p : properties.values()) {
        props.add(((OPropertyImpl) p).toStream());
      }
      document.field("properties", props, OType.EMBEDDEDSET);

      document.field("superClass", superClass != null ? superClass.getName() : null);
      document.field("customFields", customFields != null && customFields.size() > 0 ? customFields : null, OType.EMBEDDEDMAP);

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

    return document;
  }

  public Class<?> getJavaClass() {
    acquireSchemaReadLock();
    try {
      return javaClass;
    } finally {
      releaseSchemaReadLock();
    }
  }

  @Override
  public int getClusterForNewInstance() {
    acquireSchemaReadLock();
    try {
      return clusterSelection.getCluster(this);
    } finally {
      releaseSchemaReadLock();
    }
  }

  public int getDefaultClusterId() {
    acquireSchemaReadLock();
    try {
      return defaultClusterId;
    } finally {
      releaseSchemaReadLock();
    }
  }

  public void setDefaultClusterId(final int defaultClusterId) {
    acquireSchemaWriteLock();
    try {
      checkEmbedded();
      this.defaultClusterId = defaultClusterId;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public int[] getClusterIds() {
    acquireSchemaReadLock();
    try {
      return clusterIds;
    } finally {
      releaseSchemaReadLock();
    }
  }

  public int[] getPolymorphicClusterIds() {
    acquireSchemaReadLock();
    try {
      return polymorphicClusterIds;
    } finally {
      releaseSchemaReadLock();
    }
  }

  private void setPolymorphicClusterIds(final int[] iClusterIds) {
    polymorphicClusterIds = iClusterIds;
    Arrays.sort(polymorphicClusterIds);
  }

  public void renameProperty(final String iOldName, final String iNewName) {
    final OProperty p = properties.remove(iOldName);
    if (p != null)
      properties.put(iNewName, p);
  }

  public OClass addClusterId(final int clusterId) {
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {

      final ODatabaseRecordInternal database = getDatabase();
      final OStorage storage = database.getStorage();

      if (storage instanceof OStorageProxy) {
        final String cmd = String.format("alter class %s addcluster %d", name, clusterId);
        database.command(new OCommandSQL(cmd)).execute();
      } else if (isDistributedCommand()) {

        final String cmd = String.format("alter class %s addcluster %d", name, clusterId);
        final OCommandSQL commandSQL = new OCommandSQL(cmd);
        commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId());

        database.command(commandSQL).execute();

        addClusterIdInternal(clusterId);
      } else
        addClusterIdInternal(clusterId);

    } finally {
      releaseSchemaWriteLock();
    }
    return this;
  }

  @Override
  public OClass addCluster(final String clusterNameOrId) {
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      final ODatabaseRecordInternal database = getDatabase();
      final OStorage storage = database.getStorage();

      if (storage instanceof OStorageProxy) {
        final String cmd = String.format("alter class %s addcluster %s", name, clusterNameOrId);
        database.command(new OCommandSQL(cmd)).execute();
      } else if (isDistributedCommand()) {
        final String cmd = String.format("alter class %s addcluster %s", name, clusterNameOrId);

        final OCommandSQL commandSQL = new OCommandSQL(cmd);
        commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId());

        database.command(commandSQL).execute();

        final int clusterId = createClusterIfNeeded(clusterNameOrId);
        addClusterIdInternal(clusterId);
      } else {
        final int clusterId = createClusterIfNeeded(clusterNameOrId);
        addClusterIdInternal(clusterId);
      }
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  public OClass removeClusterId(final int clusterId) {
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      final ODatabaseRecordInternal database = getDatabase();
      final OStorage storage = database.getStorage();

      if (storage instanceof OStorageProxy) {
        final String cmd = String.format("alter class %s removecluster %d", name, clusterId);
        database.command(new OCommandSQL(cmd)).execute();
      } else if (isDistributedCommand()) {
        final String cmd = String.format("alter class %s removecluster %d", name, clusterId);

        final OCommandSQL commandSQL = new OCommandSQL(cmd);
        commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId());

        database.command(commandSQL).execute();

        removeClusterIdInternal(clusterId);
      } else
        removeClusterIdInternal(clusterId);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  public Collection<OClass> getBaseClasses() {
    acquireSchemaReadLock();
    try {
      if (baseClasses == null || baseClasses.size() == 0)
        return Collections.emptyList();

      return Collections.unmodifiableCollection(baseClasses);
    } finally {
      releaseSchemaReadLock();
    }
  }

  public Collection<OClass> getAllBaseClasses() {
    acquireSchemaReadLock();
    try {
      final Set<OClass> set = new HashSet<OClass>();
      if (baseClasses != null) {
        set.addAll(baseClasses);

        for (OClass c : baseClasses)
          set.addAll(c.getAllBaseClasses());
      }
      return set;
    } finally {
      releaseSchemaReadLock();
    }
  }

  OClass removeBaseClassInternal(final OClass baseClass) {
    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      if (baseClasses == null)
        return this;

      if (baseClasses.remove(baseClass))
        removePolymorphicClusterIds((OClassImpl) baseClass);

      return this;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public float getOverSize() {
    acquireSchemaReadLock();
    try {
      if (overSize > 0)
        // CUSTOM OVERSIZE SETTED
        return overSize;

      if (superClass != null)
        // RETURN THE OVERSIZE OF THE SUPER CLASS
        return superClass.getOverSize();

      // NO OVERSIZE
      return 0;
    } finally {
      releaseSchemaReadLock();
    }
  }

  public OClass setOverSize(final float overSize) {
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_UPDATE);
    acquireSchemaWriteLock();
    try {
      final ODatabaseRecordInternal database = getDatabase();
      final OStorage storage = database.getStorage();

      if (storage instanceof OStorageProxy) {
        // FORMAT FLOAT LOCALE AGNOSTIC
        final String cmd = String.format("alter class %s oversize %s", name, new Float(overSize).toString());
        database.command(new OCommandSQL(cmd)).execute();
      } else if (isDistributedCommand()) {
        // FORMAT FLOAT LOCALE AGNOSTIC
        final String cmd = String.format("alter class %s oversize %s", name, new Float(overSize).toString());
        final OCommandSQL commandSQL = new OCommandSQL(cmd);
        commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId());

        database.command(commandSQL).execute();

        setOverSizeInternal(overSize);
      } else
        setOverSizeInternal(overSize);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  public float getOverSizeInternal() {
    acquireSchemaReadLock();
    try {
      return overSize;
    } finally {
      releaseSchemaReadLock();
    }
  }

  private void setOverSizeInternal(final float overSize) {
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_UPDATE);
    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      this.overSize = overSize;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public boolean isAbstract() {
    acquireSchemaReadLock();
    try {
      return abstractClass;
    } finally {
      releaseSchemaReadLock();
    }
  }

  public OClass setAbstract(boolean isAbstract) {
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      final ODatabaseRecordInternal database = getDatabase();
      final OStorage storage = database.getStorage();

      if (storage instanceof OStorageProxy) {
        final String cmd = String.format("alter class %s abstract %s", name, isAbstract);
        database.command(new OCommandSQL(cmd)).execute();
      } else if (isDistributedCommand()) {
        final String cmd = String.format("alter class %s abstract %s", name, isAbstract);
        final OCommandSQL commandSQL = new OCommandSQL(cmd);
        commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId());

        database.command(new OCommandSQL(cmd)).execute();

        setAbstractInternal(isAbstract);
      } else
        setAbstractInternal(isAbstract);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  public boolean isStrictMode() {
    acquireSchemaReadLock();
    try {
      return strictMode;
    } finally {
      releaseSchemaReadLock();
    }
  }

  public OClass setStrictMode(final boolean isStrict) {
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      final ODatabaseRecordInternal database = getDatabase();
      final OStorage storage = database.getStorage();

      if (storage instanceof OStorageProxy) {
        final String cmd = String.format("alter class %s strictmode %s", name, isStrict);
        database.command(new OCommandSQL(cmd)).execute();
      } else if (isDistributedCommand()) {
        final String cmd = String.format("alter class %s strictmode %s", name, isStrict);

        final OCommandSQL commandSQL = new OCommandSQL(cmd);
        commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId());

        database.command(commandSQL).execute();

        setStrictModeInternal(isStrict);
      } else
        setStrictModeInternal(isStrict);

    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  @Override
  public String toString() {
    acquireSchemaReadLock();
    try {
      return name;
    } finally {
      releaseSchemaReadLock();
    }
  }

  @Override
  public int hashCode() {
    acquireSchemaReadLock();
    try {
      final int prime = 31;
      int result = super.hashCode();
      result = prime * result + ((owner == null) ? 0 : owner.hashCode());
      return result;
    } finally {
      releaseSchemaReadLock();
    }
  }

  @Override
  public boolean equals(final Object obj) {
    acquireSchemaReadLock();
    try {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (!OClass.class.isAssignableFrom(obj.getClass()))
        return false;
      final OClass other = (OClass) obj;
      if (name == null) {
        if (other.getName() != null)
          return false;
      } else if (!name.equals(other.getName()))
        return false;
      return true;
    } finally {
      releaseSchemaReadLock();
    }
  }

  public int compareTo(final OClass o) {
    acquireSchemaReadLock();
    try {
      return name.compareTo(o.getName());
    } finally {
      releaseSchemaReadLock();
    }
  }

  public long count() {
    return count(true);
  }

  public long count(final boolean isPolymorphic) {
    acquireSchemaReadLock();
    try {
      if (isPolymorphic)
        return getDatabase().countClusterElements(readableClusters(getDatabase(), polymorphicClusterIds));

      return getDatabase().countClusterElements(readableClusters(getDatabase(), clusterIds));
    } finally {
      releaseSchemaReadLock();
    }
  }

  /**
   * Truncates all the clusters the class uses.
   *
   * @throws IOException
   */
  public void truncate() throws IOException {
    getDatabase().checkSecurity(ODatabaseSecurityResources.CLASS, ORole.PERMISSION_UPDATE);

    if (isSubClassOf(OSecurityShared.RESTRICTED_CLASSNAME))
      throw new OSecurityException("Class " + getName()
          + " cannot be truncated because has record level security enabled (extends " + OSecurityShared.RESTRICTED_CLASSNAME + ")");

    final OStorage storage = getDatabase().getStorage();
    acquireSchemaReadLock();
    try {
      for (int id : clusterIds)
        storage.getClusterById(id).truncate();

      for (OIndex<?> index : getClassIndexes())
        index.clear();
    } finally {
      releaseSchemaReadLock();
    }
  }

  /**
   * Check if the current instance extends specified schema class.
   *
   * @param iClassName
   *          of class that should be checked
   * @return Returns true if the current instance extends the passed schema class (iClass)
   * @see #isSuperClassOf(OClass)
   */
  public boolean isSubClassOf(final String iClassName) {
    acquireSchemaReadLock();
    try {
      if (iClassName == null)
        return false;

      OClass cls = this;
      do {
        if (iClassName.equalsIgnoreCase(cls.getName()) || iClassName.equalsIgnoreCase(cls.getShortName()))
          return true;

        cls = cls.getSuperClass();
      } while (cls != null);

      return false;
    } finally {
      releaseSchemaReadLock();
    }
  }

  /**
   * Check if the current instance extends specified schema class.
   *
   * @param clazz
   *          to check
   * @return true if the current instance extends the passed schema class (iClass)
   * @see #isSuperClassOf(OClass)
   */
  public boolean isSubClassOf(final OClass clazz) {
    acquireSchemaReadLock();
    try {
      if (clazz == null)
        return false;

      OClass cls = this;
      while (cls != null) {
        if (cls.equals(clazz))
          return true;
        cls = cls.getSuperClass();
      }
      return false;
    } finally {
      releaseSchemaReadLock();
    }
  }

  /**
   * Returns true if the passed schema class (iClass) extends the current instance.
   *
   * @param clazz
   *          to check
   * @return Returns true if the passed schema class extends the current instance
   * @see #isSubClassOf(OClass)
   */
  public boolean isSuperClassOf(final OClass clazz) {
    return clazz != null && clazz.isSubClassOf(this);
  }

  public Object get(final ATTRIBUTES iAttribute) {
    if (iAttribute == null)
      throw new IllegalArgumentException("attribute is null");

    switch (iAttribute) {
    case NAME:
      return getName();
    case SHORTNAME:
      return getShortName();
    case SUPERCLASS:
      return getSuperClass();
    case OVERSIZE:
      return getOverSize();
    case STRICTMODE:
      return isStrictMode();
    case ABSTRACT:
      return isAbstract();
    case CLUSTERSELECTION:
      return getClusterSelection();
    case CUSTOM:
      return getCustomInternal();
    }

    throw new IllegalArgumentException("Cannot find attribute '" + iAttribute + "'");
  }

  public OClass set(final ATTRIBUTES attribute, final Object iValue) {
    if (attribute == null)
      throw new IllegalArgumentException("attribute is null");

    final String stringValue = iValue != null ? iValue.toString() : null;
    final boolean isNull = stringValue == null || stringValue.equalsIgnoreCase("NULL");

    switch (attribute) {
    case NAME:
      setName(stringValue);
      break;
    case SHORTNAME:
      setShortName(stringValue);
      break;
    case SUPERCLASS:
      setSuperClass(getDatabase().getMetadata().getSchema().getClass(stringValue));
      break;
    case OVERSIZE:
      setOverSize(Float.parseFloat(stringValue));
      break;
    case STRICTMODE:
      setStrictMode(Boolean.parseBoolean(stringValue));
      break;
    case ABSTRACT:
      setAbstract(Boolean.parseBoolean(stringValue));
      break;
    case ADDCLUSTER: {
      addCluster(stringValue);
      break;
    }
    case REMOVECLUSTER:
      int clId = getClusterId(stringValue);
      if (clId == NOT_EXISTENT_CLUSTER_ID)
        throw new IllegalArgumentException("Cluster id '" + stringValue + "' cannot be removed");
      removeClusterId(clId);
      break;
    case CLUSTERSELECTION:
      setClusterSelection(stringValue);
      break;
    case CUSTOM:
      int indx = stringValue != null ? stringValue.indexOf('=') : -1;
      if (indx < 0) {
        if (isNull || "clear".equalsIgnoreCase(stringValue)) {
          clearCustom();
        } else
          throw new IllegalArgumentException("Syntax error: expected <name> = <value> or clear, instead found: " + iValue);
      } else {
        String customName = stringValue.substring(0, indx).trim();
        String customValue = stringValue.substring(indx + 1).trim();
        if (customValue.isEmpty())
          removeCustom(customName);
        else
          setCustom(customName, customValue);
      }
      break;
    }
    return this;
  }

  public OPropertyImpl addPropertyInternal(final String name, final OType type, final OType linkedType, final OClass linkedClass) {
    if (name == null || name.length() == 0)
      throw new OSchemaException("Found property name null");

    final Character wrongCharacter = OSchemaShared.checkNameIfValid(name);
    if (wrongCharacter != null)
      throw new OSchemaException("Invalid property name found. Character '" + wrongCharacter + "' cannot be used in property name.");

    final String lowerName = name.toLowerCase();

    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      if (properties.containsKey(lowerName))
        throw new OSchemaException("Class " + this.name + " already has property '" + name + "'");

      OGlobalProperty global = owner.findOrCreateGlobalProperty(name, type);

      final OPropertyImpl prop = new OPropertyImpl(this, global);

      properties.put(lowerName, prop);

      if (linkedType != null)
        prop.setLinkedTypeInternal(linkedType);
      else if (linkedClass != null)
        prop.setLinkedClassInternal(linkedClass);
      return prop;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public OIndex<?> createIndex(final String iName, final INDEX_TYPE iType, final String... fields) {
    return createIndex(iName, iType.name(), fields);
  }

  public OIndex<?> createIndex(final String iName, final String iType, final String... fields) {
    return createIndex(iName, iType, null, null, fields);
  }

  public OIndex<?> createIndex(final String iName, final INDEX_TYPE iType, final OProgressListener iProgressListener,
      final String... fields) {
    return createIndex(iName, iType.name(), iProgressListener, null, fields);
  }

  public OIndex<?> createIndex(String iName, String iType, OProgressListener iProgressListener, ODocument metadata,
      String... fields) {
    return createIndex(iName, iType, iProgressListener, metadata, null, fields);
  }

  public OIndex<?> createIndex(final String name, String type, final OProgressListener progressListener, ODocument metadata,
      String algorithm, final String... fields) {
    if (type == null)
      throw new IllegalArgumentException("Index type is null");

    type = type.toUpperCase();

    if (fields.length == 0) {
      throw new OIndexException("List of fields to index cannot be empty.");
    }

    acquireSchemaReadLock();
    try {
      final Set<String> existingFieldNames = new HashSet<String>();
      OClassImpl currentClass = this;
      do {
        existingFieldNames.addAll(currentClass.properties.keySet());
        currentClass = (OClassImpl) currentClass.getSuperClass();
      } while (currentClass != null);

      for (final String fieldToIndex : fields) {
        final String fieldName = OIndexDefinitionFactory.extractFieldName(fieldToIndex);

        if (!fieldName.equals("@rid") && !existingFieldNames.contains(fieldName.toLowerCase()))
          throw new OIndexException("Index with name : '" + name + "' cannot be created on class : '" + this.name
              + "' because field: '" + fieldName + "' is absent in class definition.");
      }

      final OIndexDefinition indexDefinition = OIndexDefinitionFactory.createIndexDefinition(this, Arrays.asList(fields),
          extractFieldTypes(fields), null);

      return getDatabase().getMetadata().getIndexManager()
          .createIndex(name, type, indexDefinition, polymorphicClusterIds, progressListener, metadata, algorithm);
    } finally {
      releaseSchemaReadLock();
    }
  }

  public boolean areIndexed(final String... fields) {
    return areIndexed(Arrays.asList(fields));
  }

  public boolean areIndexed(final Collection<String> fields) {
    final OIndexManager indexManager = getDatabase().getMetadata().getIndexManager();

    acquireSchemaReadLock();
    try {
      final boolean currentClassResult = indexManager.areIndexed(name, fields);

      if (superClass != null)
        return currentClassResult || superClass.areIndexed(fields);
      return currentClassResult;
    } finally {
      releaseSchemaReadLock();
    }
  }

  public Set<OIndex<?>> getInvolvedIndexes(final String... fields) {
    return getInvolvedIndexes(Arrays.asList(fields));
  }

  public Set<OIndex<?>> getInvolvedIndexes(final Collection<String> fields) {
    acquireSchemaReadLock();
    try {
      final Set<OIndex<?>> result = new HashSet<OIndex<?>>(getClassInvolvedIndexes(fields));

      if (superClass != null)
        result.addAll(superClass.getInvolvedIndexes(fields));

      return result;
    } finally {
      releaseSchemaReadLock();
    }
  }

  public Set<OIndex<?>> getClassInvolvedIndexes(final Collection<String> fields) {

    final OIndexManager indexManager = getDatabase().getMetadata().getIndexManager();

    acquireSchemaReadLock();
    try {
      return indexManager.getClassInvolvedIndexes(name, fields);
    } finally {
      releaseSchemaReadLock();
    }
  }

  public Set<OIndex<?>> getClassInvolvedIndexes(final String... fields) {
    return getClassInvolvedIndexes(Arrays.asList(fields));
  }

  public OIndex<?> getClassIndex(final String name) {
    acquireSchemaReadLock();
    try {
      return getDatabase().getMetadata().getIndexManager().getClassIndex(this.name, name);
    } finally {
      releaseSchemaReadLock();
    }
  }

  public Set<OIndex<?>> getClassIndexes() {
    acquireSchemaReadLock();
    try {
      return getDatabase().getMetadata().getIndexManager().getClassIndexes(name);
    } finally {
      releaseSchemaReadLock();
    }
  }

  @Override
  public void getClassIndexes(final Collection<OIndex<?>> indexes) {
    acquireSchemaReadLock();
    try {
      getDatabase().getMetadata().getIndexManager().getClassIndexes(name, indexes);
    } finally {
      releaseSchemaReadLock();
    }
  }

  public Set<OIndex<?>> getIndexes() {
    acquireSchemaReadLock();
    try {
      final Set<OIndex<?>> indexes = getClassIndexes();
      for (OClass s = superClass; s != null; s = s.getSuperClass()) {
        s.getClassIndexes(indexes);
      }
      return indexes;
    } finally {
      releaseSchemaReadLock();
    }
  }

  public void acquireSchemaReadLock() {
    owner.acquireSchemaReadLock();
  }

  public void releaseSchemaReadLock() {
    owner.releaseSchemaReadLock();
  }

  public void acquireSchemaWriteLock() {
    owner.acquireSchemaWriteLock();
  }

  public void releaseSchemaWriteLock() {
    owner.releaseSchemaWriteLock();
  }

  public void checkEmbedded() {
    if (!(getDatabase().getStorage().getUnderlying() instanceof OStorageEmbedded))
      throw new OSchemaException("'Internal' schema modification methods can be used only inside of embedded database");
  }

  public void setClusterSelectionInternal(final String clusterSelection) {
    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      this.clusterSelection = owner.getClusterSelectionFactory().newInstance(clusterSelection);
    } finally {
      releaseSchemaWriteLock();
    }
  }

  public void setClusterSelectionInternal(final OClusterSelectionStrategy clusterSelection) {
    checkEmbedded();
    this.clusterSelection = clusterSelection;
  }

  public void fireDatabaseMigration(final ODatabaseRecord database, final String propertyName, final OType type) {
    database.query(new OSQLAsynchQuery<Object>("select from " + name + " where " + propertyName + ".type() <> \"" + type.name()
        + "\"", new OCommandResultListener() {

      @Override
      public boolean result(Object iRecord) {
        final ODocument record = ((OIdentifiable) iRecord).getRecord();
        record.setFieldType(propertyName, type);
        record.field(propertyName, OType.convert(record.field(propertyName), type.getDefaultJavaType()), type);
        database.save(record);
        return true;
      }

      @Override
      public void end() {
      }
    }));

  }

  public void firePropertyNameMigration(final ODatabaseRecord database, final String propertyName, final String newPropertyName,
      final OType type) {
    database.query(new OSQLAsynchQuery<Object>("select from " + name + " where " + propertyName + " is not null ",
        new OCommandResultListener() {

          @Override
          public boolean result(Object iRecord) {
            final ODocument record = ((OIdentifiable) iRecord).getRecord();
            record.setFieldType(propertyName, type);
            record.field(newPropertyName, record.field(propertyName), type);
            database.save(record);
            return true;
          }

          @Override
          public void end() {
          }
        }));

  }

  public void checkPersistentPropertyType(ODatabaseComplex<ORecord> database, String propertyName, OType type) {

    StringBuilder builder = new StringBuilder(256);
    builder.append("select count(*) from ").append(name).append(" where ");
    builder.append(propertyName).append(".type() not in [");
    Iterator<OType> cur = type.getCastable().iterator();
    while (cur.hasNext()) {
      builder.append('"').append(cur.next().name()).append('"');
      if (cur.hasNext())
        builder.append(",");
    }
    builder.append("] and ").append(propertyName).append(" is not null ");
    if (type.isMultiValue())
      builder.append(" and ").append(propertyName).append(".size() <> 0 ");

    List<ODocument> res = database.command(new OCommandSQL(builder.toString())).execute();
    if (((Long) res.get(0).field("count")) > 0)
      throw new OSchemaException("The database contains some schemaless data in the property " + name + "." + propertyName
          + " that is not compatible with the type " + type);

  }

  public OSchemaShared getOwner() {
    return owner;
  }

  private void setCustomInternal(final String name, final String value) {
    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      if (customFields == null)
        customFields = new HashMap<String, String>();
      if (value == null || "null".equalsIgnoreCase(value))
        customFields.remove(name);
      else
        customFields.put(name, value);
    } finally {
      releaseSchemaWriteLock();
    }
  }

  private void clearCustomInternal() {
    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      customFields = null;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  private void setNameInternal(final String name) {
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_UPDATE);
    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      final String oldName = this.name;

      owner.changeClassName(this.name, name, this);
      this.name = name;

      ODatabaseRecordInternal database = getDatabase();
      final OStorage storage = database.getStorage();

      if (!database.getStorageVersions().classesAreDetectedByClusterId()) {
        for (int clusterId : clusterIds) {
          OClusterPosition[] range = storage.getClusterDataRange(clusterId);

          OPhysicalPosition[] positions = storage.ceilingPhysicalPositions(clusterId, new OPhysicalPosition(range[0]));
          do {
            for (OPhysicalPosition position : positions) {
              final ORecordId identity = new ORecordId(clusterId, position.clusterPosition);
              final ORawBuffer record = storage.readRecord(identity, null, true, null, false, OStorage.LOCKING_STRATEGY.DEFAULT)
                  .getResult();

              if (record.recordType == ODocument.RECORD_TYPE) {
                final ORecordSerializerSchemaAware2CSV serializer = (ORecordSerializerSchemaAware2CSV) ORecordSerializerFactory
                    .instance().getFormat(ORecordSerializerSchemaAware2CSV.NAME);

                if (serializer.getClassName(OBinaryProtocol.bytes2string(record.buffer)).equalsIgnoreCase(name)) {
                  final ODocument document = new ODocument();
                  document.setLazyLoad(false);
                  document.fromStream(record.buffer);
                  document.getRecordVersion().copyFrom(record.version);
                  ORecordInternal.setIdentity(document, identity);
                  document.setClassName(name);
                  document.setDirty();
                  document.save();
                }
              }

              if (positions.length > 0)
                positions = storage.higherPhysicalPositions(clusterId, positions[positions.length - 1]);
            }
          } while (positions.length > 0);
        }
      }

      renameCluster(oldName, this.name);
    } finally {
      releaseSchemaWriteLock();
    }
  }

  private void renameCluster(String oldName, String newName) {
    oldName = oldName.toLowerCase();
    newName = newName.toLowerCase();

    final ODatabaseRecordInternal database = getDatabase();
    final OStorage storage = database.getStorage();

    if (storage.getClusterIdByName(newName) != -1)
      return;

    final int clusterId = storage.getClusterIdByName(oldName);
    if (clusterId == -1)
      return;

    if (!hasClusterId(clusterId))
      return;

    database.command(new OCommandSQL("alter cluster " + oldName + " name " + newName)).execute();
  }

  private void setShortNameInternal(final String iShortName) {
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      String oldName = null;

      if (this.shortName != null)
        oldName = this.shortName.toLowerCase();

      this.shortName = iShortName;

      owner.changeClassName(oldName, shortName, this);
    } finally {
      releaseSchemaWriteLock();
    }
  }

  private void dropPropertyInternal(final String iPropertyName) {
    if (getDatabase().getTransaction().isActive())
      throw new IllegalStateException("Cannot drop a property inside a transaction");
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_DELETE);

    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      final OProperty prop = properties.remove(iPropertyName.toLowerCase());

      if (prop == null)
        throw new OSchemaException("Property '" + iPropertyName + "' not found in class " + name + "'");
    } finally {
      releaseSchemaWriteLock();
    }
  }

  private int createClusterIfNeeded(String nameOrId) {
    String[] parts = nameOrId.split(" ");
    int clId = getClusterId(parts[0]);

    if (clId == NOT_EXISTENT_CLUSTER_ID) {
      try {
        clId = Integer.parseInt(parts[0]);
        throw new IllegalArgumentException("Cluster id '" + clId + "' cannot be added");
      } catch (NumberFormatException e) {
        clId = getDatabase().addCluster(parts[0]);
      }
    }

    return clId;
  }

  private OClass addClusterIdInternal(final int clusterId) {
    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      owner.checkClusterCanBeAdded(clusterId, this);

      for (int currId : clusterIds)
        if (currId == clusterId)
          // ALREADY ADDED
          return this;

      clusterIds = OArrays.copyOf(clusterIds, clusterIds.length + 1);
      clusterIds[clusterIds.length - 1] = clusterId;
      Arrays.sort(clusterIds);

      addPolymorphicClusterId(clusterId);

      if (defaultClusterId == NOT_EXISTENT_CLUSTER_ID)
        defaultClusterId = clusterId;

      owner.addClusterForClass(clusterId, this);
      return this;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  private void addPolymorphicClusterId(int clusterId) {
    if (Arrays.binarySearch(polymorphicClusterIds, clusterId) >= 0)
      return;

    polymorphicClusterIds = OArrays.copyOf(polymorphicClusterIds, polymorphicClusterIds.length + 1);
    polymorphicClusterIds[polymorphicClusterIds.length - 1] = clusterId;
    Arrays.sort(polymorphicClusterIds);

    addClusterIdToIndexes(clusterId);

    if (superClass != null)
      superClass.addPolymorphicClusterId(clusterId);
  }

  private OClass removeClusterIdInternal(final int clusterToRemove) {

    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      boolean found = false;
      for (int clusterId : clusterIds) {
        if (clusterId == clusterToRemove) {
          found = true;
          break;
        }
      }

      if (found) {
        final int[] newClusterIds = new int[clusterIds.length - 1];
        for (int i = 0, k = 0; i < clusterIds.length; ++i) {
          if (clusterIds[i] == clusterToRemove)
            // JUMP IT
            continue;

          newClusterIds[k] = clusterIds[i];
          k++;
        }
        clusterIds = newClusterIds;

        removePolymorphicClusterId(clusterToRemove);
      }

      if (defaultClusterId == clusterToRemove)
        defaultClusterId = NOT_EXISTENT_CLUSTER_ID;

      owner.removeClusterForClass(clusterToRemove, this);
    } finally {
      releaseSchemaWriteLock();
    }

    return this;
  }

  private void setAbstractInternal(final boolean isAbstract) {
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      if (isAbstract) {
        // SWITCH TO ABSTRACT
        if (defaultClusterId != NOT_EXISTENT_CLUSTER_ID) {
          // CHECK
          if (count() > 0)
            throw new IllegalStateException("Cannot set the class as abstract because contains records.");

          tryDropCluster(defaultClusterId);
          for (int clusterId : getClusterIds()) {
            tryDropCluster(clusterId);
            removePolymorphicClusterId(clusterId);
            owner.removeClusterForClass(clusterId, this);
          }

          setClusterIds(new int[] { NOT_EXISTENT_CLUSTER_ID });

          defaultClusterId = NOT_EXISTENT_CLUSTER_ID;
        }
      } else {
        if (!abstractClass)
          return;

        int clusterId = getDatabase().getClusterIdByName(name);
        if (clusterId == -1)
          clusterId = getDatabase().addCluster(name);

        this.defaultClusterId = clusterId;
        this.clusterIds[0] = this.defaultClusterId;
      }

      this.abstractClass = isAbstract;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  private void setStrictModeInternal(final boolean iStrict) {
    getDatabase().checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_UPDATE);

    acquireSchemaWriteLock();
    try {
      checkEmbedded();

      this.strictMode = iStrict;
    } finally {
      releaseSchemaWriteLock();
    }
  }

  private OProperty addProperty(final String propertyName, final OType type, final OType linkedType, final OClass linkedClass) {
    if (type == null)
      throw new OSchemaException("Property type not defined.");

    if (propertyName == null || propertyName.length() == 0)
      throw new OSchemaException("Property name is null or empty");

    if (Character.isDigit(propertyName.charAt(0)))
      throw new OSchemaException("Found invalid property name. Cannot start with numbers");

    if (getDatabase().getTransaction().isActive())
      throw new OSchemaException("Cannot create a new property inside a transaction");

    final ODatabaseRecordInternal database = getDatabase();
    database.checkSecurity(ODatabaseSecurityResources.SCHEMA, ORole.PERMISSION_UPDATE);

    checkPersistentPropertyType(database, propertyName, type);
    OProperty property = null;
    acquireSchemaWriteLock();
    try {
      final StringBuilder cmd = new StringBuilder("create property ");
      // CLASS.PROPERTY NAME
      cmd.append(name);
      cmd.append('.');
      cmd.append(propertyName);

      // TYPE
      cmd.append(' ');
      cmd.append(type.name);

      if (linkedType != null) {
        // TYPE
        cmd.append(' ');
        cmd.append(linkedType.name);

      } else if (linkedClass != null) {
        // TYPE
        cmd.append(' ');
        cmd.append(linkedClass.getName());
      }

      final OStorage storage = database.getStorage();

      if (storage instanceof OStorageProxy) {
        database.command(new OCommandSQL(cmd.toString())).execute();
        reload();

        return getProperty(propertyName);
      } else if (isDistributedCommand()) {
        final OCommandSQL commandSQL = new OCommandSQL(cmd.toString());
        commandSQL.addExcludedNode(((OAutoshardedStorage) storage).getNodeId());

        database.command(commandSQL).execute();

        property = addPropertyInternal(propertyName, type, linkedType, linkedClass);
      } else
        property = addPropertyInternal(propertyName, type, linkedType, linkedClass);

    } finally {
      releaseSchemaWriteLock();
    }

    if (property != null)
      fireDatabaseMigration(database, propertyName, type);
    return property;
  }

  private int getClusterId(final String stringValue) {
    int clId;
    try {
      clId = Integer.parseInt(stringValue);
    } catch (NumberFormatException e) {
      clId = getDatabase().getClusterIdByName(stringValue);
    }
    return clId;
  }

  private void addClusterIdToIndexes(int iId) {
    if (getDatabase().getStorage().getUnderlying() instanceof OStorageEmbedded) {
      final String clusterName = getDatabase().getClusterNameById(iId);
      final List<String> indexesToAdd = new ArrayList<String>();

      for (OIndex<?> index : getIndexes())
        indexesToAdd.add(index.getName());

      final OIndexManager indexManager = getDatabase().getMetadata().getIndexManager();
      for (String indexName : indexesToAdd)
        indexManager.addClusterToIndex(clusterName, indexName);
    }
  }

  /**
   * Adds a base class to the current one. It adds also the base class cluster ids to the polymorphic cluster ids array.
   *
   * @param iBaseClass
   *          The base class to add.
   */
  private OClass addBaseClasses(final OClass iBaseClass) {
    if (baseClasses == null)
      baseClasses = new ArrayList<OClass>();

    if (baseClasses.contains(iBaseClass))
      return this;

    baseClasses.add(iBaseClass);

    // ADD CLUSTER IDS OF BASE CLASS TO THIS CLASS AND ALL SUPER-CLASSES
    OClassImpl currentClass = this;
    while (currentClass != null) {
      currentClass.addPolymorphicClusterIds((OClassImpl) iBaseClass);
      currentClass = (OClassImpl) currentClass.getSuperClass();
    }

    return this;
  }

  private void removePolymorphicClusterIds(final OClassImpl iBaseClass) {
    for (final int clusterId : iBaseClass.polymorphicClusterIds)
      removePolymorphicClusterId(clusterId);
  }

  private void removePolymorphicClusterId(int clusterId) {
    final int index = Arrays.binarySearch(polymorphicClusterIds, clusterId);
    if (index == -1)
      return;

    if (index < polymorphicClusterIds.length - 1)
      System.arraycopy(polymorphicClusterIds, index + 1, polymorphicClusterIds, index, polymorphicClusterIds.length - (index + 1));

    polymorphicClusterIds = Arrays.copyOf(polymorphicClusterIds, polymorphicClusterIds.length - 1);

    removeClusterFromIndexes(clusterId);

    if (superClass != null)
      superClass.removePolymorphicClusterId(clusterId);
  }

  private void removeClusterFromIndexes(int iId) {
    if (getDatabase().getStorage().getUnderlying() instanceof OStorageEmbedded) {
      final String clusterName = getDatabase().getClusterNameById(iId);
      final List<String> indexesToRemove = new ArrayList<String>();

      for (final OIndex<?> index : getIndexes())
        indexesToRemove.add(index.getName());

      final OIndexManager indexManager = getDatabase().getMetadata().getIndexManager();
      for (final String indexName : indexesToRemove)
        indexManager.removeClusterFromIndex(clusterName, indexName);
    }
  }

  private void tryDropCluster(int defaultClusterId) {
    if (name.toLowerCase().equals(getDatabase().getClusterNameById(defaultClusterId))) {
      // DROP THE DEFAULT CLUSTER CALLED WITH THE SAME NAME ONLY IF EMPTY
      if (getDatabase().getClusterRecordSizeById(defaultClusterId) == 0)
        getDatabase().dropCluster(defaultClusterId, true);
    }
  }

  private ODatabaseRecordInternal getDatabase() {
    return ODatabaseRecordThreadLocal.INSTANCE.get();
  }

  /**
   * Add different cluster id to the "polymorphic cluster ids" array.
   */
  private void addPolymorphicClusterIds(final OClassImpl iBaseClass) {
    boolean found;
    for (int i : iBaseClass.polymorphicClusterIds) {
      found = false;
      for (int k : polymorphicClusterIds) {
        if (i == k) {
          found = true;
          break;
        }
      }

      if (!found) {
        // ADD IT
        polymorphicClusterIds = OArrays.copyOf(polymorphicClusterIds, polymorphicClusterIds.length + 1);
        polymorphicClusterIds[polymorphicClusterIds.length - 1] = i;
        Arrays.sort(polymorphicClusterIds);
      }
    }
  }

  private List<OType> extractFieldTypes(String[] fieldNames) {
    final List<OType> types = new ArrayList<OType>(fieldNames.length);

    for (String fieldName : fieldNames) {
      if (!fieldName.equals("@rid"))
        types.add(getProperty(OIndexDefinitionFactory.extractFieldName(fieldName).toLowerCase()).getType());
      else
        types.add(OType.LINK);
    }
    return types;
  }

  private OClass setClusterIds(final int[] iClusterIds) {
    clusterIds = iClusterIds;
    Arrays.sort(clusterIds);

    return this;
  }

  private boolean isDistributedCommand() {
    return getDatabase().getStorage() instanceof OAutoshardedStorage
        && OScenarioThreadLocal.INSTANCE.get() != OScenarioThreadLocal.RUN_MODE.RUNNING_DISTRIBUTED;
  }

}
TOP

Related Classes of com.orientechnologies.orient.core.metadata.schema.OClassImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.