Package com.orientechnologies.orient.core.storage.impl.local

Source Code of com.orientechnologies.orient.core.storage.impl.local.OStorageLocal

/*
* Copyright 1999-2010 Luca Garulli (l.garulli--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.
*/
package com.orientechnologies.orient.core.storage.impl.local;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.orientechnologies.common.concur.lock.OLockManager.LOCK;
import com.orientechnologies.common.exception.OException;
import com.orientechnologies.common.io.OFileUtils;
import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.common.parser.OSystemVariableResolver;
import com.orientechnologies.common.profiler.OProfiler;
import com.orientechnologies.common.profiler.OProfiler.OProfilerHookValue;
import com.orientechnologies.common.util.OArrays;
import com.orientechnologies.orient.core.OMemoryWatchDog;
import com.orientechnologies.orient.core.Orient;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.config.OStorageClusterConfiguration;
import com.orientechnologies.orient.core.config.OStorageConfiguration;
import com.orientechnologies.orient.core.config.OStorageDataConfiguration;
import com.orientechnologies.orient.core.config.OStorageLogicalClusterConfiguration;
import com.orientechnologies.orient.core.config.OStorageMemoryClusterConfiguration;
import com.orientechnologies.orient.core.config.OStoragePhysicalClusterConfiguration;
import com.orientechnologies.orient.core.config.OStorageSegmentConfiguration;
import com.orientechnologies.orient.core.db.record.ODatabaseRecord;
import com.orientechnologies.orient.core.exception.OConcurrentModificationException;
import com.orientechnologies.orient.core.exception.OConfigurationException;
import com.orientechnologies.orient.core.exception.ORecordNotFoundException;
import com.orientechnologies.orient.core.exception.OStorageException;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.storage.OCluster;
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.fs.OMMapManager;
import com.orientechnologies.orient.core.storage.impl.memory.OClusterMemory;
import com.orientechnologies.orient.core.tx.OTransaction;

public class OStorageLocal extends OStorageEmbedded {
  private final int                      DELETE_MAX_RETRIES;
  private final int                      DELETE_WAIT_TIME;
  public static final String[]          TYPES                = { OClusterLocal.TYPE, OClusterLogical.TYPE };

  private final Map<String, OCluster>    clusterMap          = new LinkedHashMap<String, OCluster>();
  private OCluster[]                    clusters            = new OCluster[0];
  private ODataLocal[]                  dataSegments        = new ODataLocal[0];

  private final OStorageLocalTxExecuter  txManager;
  private String                        storagePath;
  private final OStorageVariableParser  variableParser;
  private int                            defaultClusterId    = -1;

  private OStorageConfigurationSegment  configurationSegment;

  private static String[]                ALL_FILE_EXTENSIONS  = { "ocf", ".och", ".ocl", ".oda", ".odh", ".otx" };
  private final String                  PROFILER_CREATE_RECORD;
  private final String                  PROFILER_READ_RECORD;
  private final String                  PROFILER_UPDATE_RECORD;
  private final String                  PROFILER_DELETE_RECORD;

  public OStorageLocal(final String iName, final String iFilePath, final String iMode) throws IOException {
    super(iName, iFilePath, iMode);

    File f = new File(url);

    if (f.exists() || !exists(f.getParent())) {
      // ALREADY EXISTS OR NOT LEGACY
      storagePath = OSystemVariableResolver.resolveSystemVariables(OFileUtils.getPath(new File(url).getPath()));
    } else {
      // LEGACY DB
      storagePath = OSystemVariableResolver.resolveSystemVariables(OFileUtils.getPath(new File(url).getParent()));
    }

    variableParser = new OStorageVariableParser(storagePath);
    configuration = new OStorageConfigurationSegment(this, storagePath);
    txManager = new OStorageLocalTxExecuter(this, configuration.txSegment);

    PROFILER_CREATE_RECORD = "storage." + name + ".createRecord";
    PROFILER_READ_RECORD = "storage." + name + ".readRecord";
    PROFILER_UPDATE_RECORD = "storage." + name + ".updateRecord";
    PROFILER_DELETE_RECORD = "storage." + name + ".deleteRecord";

    DELETE_MAX_RETRIES = OGlobalConfiguration.FILE_MMAP_FORCE_RETRY.getValueAsInteger();
    DELETE_WAIT_TIME = OGlobalConfiguration.FILE_MMAP_FORCE_DELAY.getValueAsInteger();

    installProfilerHooks();
  }

  public synchronized void open(final String iUserName, final String iUserPassword, final Map<String, Object> iProperties) {
    final long timer = OProfiler.getInstance().startChrono();

    lock.acquireExclusiveLock();
    try {

      if (status != STATUS.CLOSED)
        // ALREADY OPENED: THIS IS THE CASE WHEN A STORAGE INSTANCE IS
        // REUSED
        return;

      addUser();

      if (!exists())
        throw new OStorageException("Can't open the storage '" + name + "' because it not exists in path: " + url);

      status = STATUS.OPEN;

      // OPEN BASIC SEGMENTS
      int pos;
      pos = registerDataSegment(new OStorageDataConfiguration(configuration, OStorage.DATA_DEFAULT_NAME));
      dataSegments[pos].open();

      pos = createClusterFromConfig(new OStoragePhysicalClusterConfiguration(configuration, OStorage.CLUSTER_INTERNAL_NAME,
          clusters.length));
      clusters[pos].open();

      configuration.load();

      pos = createClusterFromConfig(new OStoragePhysicalClusterConfiguration(configuration, OStorage.CLUSTER_INDEX_NAME,
          clusters.length));
      clusters[pos].open();

      defaultClusterId = createClusterFromConfig(new OStoragePhysicalClusterConfiguration(configuration,
          OStorage.CLUSTER_DEFAULT_NAME, clusters.length));
      clusters[defaultClusterId].open();

      // REGISTER DATA SEGMENT
      for (int i = 0; i < configuration.dataSegments.size(); ++i) {
        final OStorageDataConfiguration dataConfig = configuration.dataSegments.get(i);

        pos = registerDataSegment(dataConfig);
        if (pos == -1) {
          // CLOSE AND REOPEN TO BE SURE ALL THE FILE SEGMENTS ARE
          // OPENED
          dataSegments[i].close();
          dataSegments[i] = new ODataLocal(this, dataConfig, pos);
          dataSegments[i].open();
        } else
          dataSegments[pos].open();
      }

      // REGISTER CLUSTER
      for (int i = 0; i < configuration.clusters.size(); ++i) {
        final OStorageClusterConfiguration clusterConfig = configuration.clusters.get(i);

        if (clusterConfig != null) {
          pos = createClusterFromConfig(clusterConfig);

          if (pos == -1) {
            // CLOSE AND REOPEN TO BE SURE ALL THE FILE SEGMENTS ARE
            // OPENED
            clusters[i].close();
            clusters[i] = new OClusterLocal(this, (OStoragePhysicalClusterConfiguration) clusterConfig);
            clusterMap.put(clusters[i].getName(), clusters[i]);
            clusters[i].open();
          } else {
            if (clusterConfig.getName().equals(OStorage.CLUSTER_DEFAULT_NAME))
              defaultClusterId = pos;

            clusters[pos].open();
          }
        } else {
          clusters = Arrays.copyOf(clusters, clusters.length + 1);
          clusters[i] = null;
        }
      }

      loadVersion();

      txManager.open();

    } catch (Exception e) {
      close(true);
      throw new OStorageException("Can't open local storage: " + url + ", with mode=" + mode, e);
    } finally {
      lock.releaseExclusiveLock();

      OProfiler.getInstance().stopChrono("storage." + name + ".open", timer);
    }
  }

  public void create(final Map<String, Object> iProperties) {
    final long timer = OProfiler.getInstance().startChrono();

    lock.acquireExclusiveLock();
    try {

      if (status != STATUS.CLOSED)
        throw new OStorageException("Can't create new storage " + name + " because it isn't closed");

      addUser();

      final File storageFolder = new File(storagePath);
      if (!storageFolder.exists())
        storageFolder.mkdir();

      if (exists())
        throw new OStorageException("Can't create new storage " + name + " because it already exists");

      status = STATUS.OPEN;

      addDataSegment(OStorage.DATA_DEFAULT_NAME);

      // ADD THE METADATA CLUSTER TO STORE INTERNAL STUFF
      addCluster(OStorage.CLUSTER_INTERNAL_NAME, OStorage.CLUSTER_TYPE.PHYSICAL);

      // ADD THE INDEX CLUSTER TO STORE, BY DEFAULT, ALL THE RECORDS OF
      // INDEXING
      addCluster(OStorage.CLUSTER_INDEX_NAME, OStorage.CLUSTER_TYPE.PHYSICAL);

      // ADD THE DEFAULT CLUSTER
      defaultClusterId = addCluster(OStorage.CLUSTER_DEFAULT_NAME, OStorage.CLUSTER_TYPE.PHYSICAL);

      configuration.create();

      txManager.create();
    } catch (OStorageException e) {
      close();
      throw e;
    } catch (IOException e) {
      close();
      throw new OStorageException("Error on creation of storage: " + name, e);

    } finally {
      lock.releaseExclusiveLock();

      OProfiler.getInstance().stopChrono("storage." + name + ".create", timer);
    }
  }

  public void reload() {
  }

  public boolean exists() {
    return exists(storagePath);
  }

  private boolean exists(String path) {
    return new File(path + "/" + OStorage.DATA_DEFAULT_NAME + ".0" + ODataLocal.DEF_EXTENSION).exists();
  }

  @Override
  public void close(final boolean iForce) {
    final long timer = OProfiler.getInstance().startChrono();

    lock.acquireExclusiveLock();
    try {

      if (!checkForClose(iForce))
        return;

      status = STATUS.CLOSING;

      saveVersion();

      for (OCluster cluster : clusters)
        if (cluster != null)
          cluster.close();
      clusters = new OCluster[0];
      clusterMap.clear();

      for (ODataLocal data : dataSegments)
        data.close();
      dataSegments = new ODataLocal[0];

      txManager.close();

      configuration.close();

      level2Cache.shutdown();

      OMMapManager.flush();

      super.close(iForce);

      Orient.instance().unregisterStorage(this);
      status = STATUS.CLOSED;
    } catch (IOException e) {
      OLogManager.instance().error(this, "Error on closing of the storage '" + name, e, OStorageException.class);

    } finally {
      lock.releaseExclusiveLock();

      OProfiler.getInstance().stopChrono("storage." + name + ".close", timer);
    }
  }

  /**
   * Deletes physically all the database files (that ends for ".och", ".ocl", ".oda", ".odh", ".otx"). Tries also to delete the
   * container folder if the directory is empty. If files are locked, retry up to 10 times before to raise an exception.
   */
  public void delete() {
    // CLOSE THE DATABASE BY REMOVING THE CURRENT USER
    if (status != STATUS.CLOSED) {
      if (getUsers() > 0) {
        while (removeUser() > 0)
          ;
      }
    }
    close(true);

    try {
      Orient.instance().unregisterStorage(this);
    } catch (Exception e) {
      OLogManager.instance().error(this, "Can't unregister storage", e);
    }

    final long timer = OProfiler.getInstance().startChrono();

    // GET REAL DIRECTORY
    File dbDir = new File(OSystemVariableResolver.resolveSystemVariables(url));
    if (!dbDir.exists() || !dbDir.isDirectory())
      dbDir = dbDir.getParentFile();

    lock.acquireExclusiveLock();
    try {

      // RETRIES
      for (int i = 0; i < DELETE_MAX_RETRIES; ++i) {
        if (dbDir.exists() && dbDir.isDirectory()) {
          int notDeletedFiles = 0;

          // TRY TO DELETE ALL THE FILES
          for (File f : dbDir.listFiles()) {
            // DELETE ONLY THE SUPPORTED FILES
            for (String ext : ALL_FILE_EXTENSIONS)
              if (f.getPath().endsWith(ext)) {
                if (!f.delete()) {
                  notDeletedFiles++;
                }
                break;
              }
          }

          if (notDeletedFiles == 0) {
            // TRY TO DELETE ALSO THE DIRECTORY IF IT'S EMPTY
            dbDir.delete();
            return;
          }
        } else
          return;

        OLogManager.instance().debug(this,
            "Can't delete database files because are locked by the OrientDB process yet: waiting a %d ms and retry %d/%d...",
            DELETE_WAIT_TIME, i, DELETE_MAX_RETRIES);

        // FORCE FINALIZATION TO COLLECT ALL THE PENDING BUFFERS
        OMemoryWatchDog.freeMemory(DELETE_WAIT_TIME);
      }

      throw new OStorageException("Can't delete database '" + name + "' located in: " + dbDir + ". Database files seems locked");

    } finally {
      lock.releaseExclusiveLock();

      OProfiler.getInstance().stopChrono("storage." + name + ".delete", timer);
    }
  }

  public ODataLocal getDataSegment(final int iDataSegmentId) {
    checkOpeness();

    lock.acquireSharedLock();
    try {

      if (iDataSegmentId >= dataSegments.length)
        throw new IllegalArgumentException("Data segment #" + iDataSegmentId + " doesn't exist in current storage");

      return dataSegments[iDataSegmentId];

    } finally {
      lock.releaseSharedLock();
    }
  }

  /**
   * Add a new data segment in the default segment directory and with filename equals to the cluster name.
   */
  public int addDataSegment(final String iDataSegmentName) {
    String segmentFileName = storagePath + "/" + iDataSegmentName;
    return addDataSegment(iDataSegmentName, segmentFileName);
  }

  public int addDataSegment(String iSegmentName, final String iSegmentFileName) {
    checkOpeness();

    iSegmentName = iSegmentName.toLowerCase();

    lock.acquireExclusiveLock();
    try {

      final OStorageDataConfiguration conf = new OStorageDataConfiguration(configuration, iSegmentName);
      configuration.dataSegments.add(conf);

      final int pos = registerDataSegment(conf);

      if (pos == -1)
        throw new OConfigurationException("Can't add segment " + conf.name + " because it's already part of current storage");

      dataSegments[pos].create(-1);
      configuration.update();

      return pos;
    } catch (Throwable e) {
      OLogManager.instance().error(this, "Error on creation of new data segment '" + iSegmentName + "' in: " + iSegmentFileName, e,
          OStorageException.class);
      return -1;

    } finally {
      lock.releaseExclusiveLock();
    }
  }

  /**
   * Add a new cluster into the storage. Type can be: "physical" or "logical".
   */
  public int addCluster(String iClusterName, final OStorage.CLUSTER_TYPE iClusterType, final Object... iParameters) {
    checkOpeness();

    try {
      iClusterName = iClusterName != null ? iClusterName.toLowerCase() : null;

      switch (iClusterType) {
      case PHYSICAL: {
        // GET PARAMETERS
        final String clusterFileName = (String) (iParameters.length < 1 ? storagePath + "/" + iClusterName : iParameters[0]);
        final int startSize = (iParameters.length < 2 ? -1 : (Integer) iParameters[1]);

        return addPhysicalCluster(iClusterName, clusterFileName, startSize);
      }
      case LOGICAL: {
        // GET PARAMETERS
        final int physicalClusterId = (iParameters.length < 1 ? getClusterIdByName(OStorage.CLUSTER_INTERNAL_NAME)
            : (Integer) iParameters[0]);

        return addLogicalCluster(iClusterName, physicalClusterId);
      }

      case MEMORY:
        return addMemoryCluster(iClusterName);

      default:
        OLogManager.instance().exception(
            "Cluster type '" + iClusterType + "' is not supported. Supported types are: " + Arrays.toString(TYPES), null,
            OStorageException.class);
      }
    } catch (Exception e) {
      OLogManager.instance().exception("Error in creation of new cluster '" + iClusterName + "' of type: " + iClusterType, e,
          OStorageException.class);
    }

    return -1;
  }

  public ODataLocal[] getDataSegments() {
    return dataSegments;
  }

  public OStorageLocalTxExecuter getTxManager() {
    return txManager;
  }

  public boolean dropCluster(final int iClusterId) {
    lock.acquireExclusiveLock();
    try {

      if (iClusterId < 0 || iClusterId >= clusters.length)
        throw new IllegalArgumentException("Cluster id '" + iClusterId + "' is out of range of configured clusters (0-"
            + (clusters.length - 1) + ")");

      final OCluster cluster = clusters[iClusterId];
      if (cluster == null)
        return false;

      getLevel2Cache().freeCluster(iClusterId);

      cluster.delete();

      clusterMap.remove(cluster.getName());
      clusters[iClusterId] = null;

      // UPDATE CONFIGURATION
      configuration.clusters.set(iClusterId, null);
      configuration.update();

      return true;
    } catch (Exception e) {
      OLogManager.instance().exception("Error while removing cluster '" + iClusterId + "'", e, OStorageException.class);

    } finally {
      lock.releaseExclusiveLock();
    }

    return false;
  }

  public long count(final int[] iClusterIds) {
    checkOpeness();

    lock.acquireSharedLock();
    try {

      long tot = 0;

      for (int i = 0; i < iClusterIds.length; ++i) {
        if (iClusterIds[i] >= clusters.length)
          throw new OConfigurationException("Cluster id " + iClusterIds[i] + "was not found");

        final OCluster c = clusters[iClusterIds[i]];
        if (c != null)
          tot += c.getEntries();
      }

      return tot;

    } finally {
      lock.releaseSharedLock();
    }
  }

  public long[] getClusterDataRange(final int iClusterId) {
    if (iClusterId == -1)
      throw new OStorageException("Cluster Id is invalid: " + iClusterId);

    checkOpeness();

    lock.acquireSharedLock();
    try {

      return clusters[iClusterId] != null ? new long[] { clusters[iClusterId].getFirstEntryPosition(),
          clusters[iClusterId].getLastEntryPosition() } : new long[0];

    } catch (IOException e) {

      OLogManager.instance().error(this, "Error on getting last entry position", e);
      return null;

    } finally {
      lock.releaseSharedLock();
    }
  }

  public long count(final int iClusterId) {
    if (iClusterId == -1)
      throw new OStorageException("Cluster Id is invalid: " + iClusterId);

    // COUNT PHYSICAL CLUSTER IF ANY
    checkOpeness();

    lock.acquireSharedLock();
    try {

      return clusters[iClusterId] != null ? clusters[iClusterId].getEntries() : 0l;

    } finally {
      lock.releaseSharedLock();
    }
  }

  public long createRecord(final ORecordId iRid, final byte[] iContent, final byte iRecordType) {
    checkOpeness();

    iRid.clusterPosition = createRecord(getClusterById(iRid.clusterId), iContent, iRecordType);
    return iRid.clusterPosition;
  }

  public ORawBuffer readRecord(final ODatabaseRecord iDatabase, final ORecordId iRid, final String iFetchPlan) {
    checkOpeness();
    return readRecord(getClusterById(iRid.clusterId), iRid, true);
  }

  public int updateRecord(final ORecordId iRid, final byte[] iContent, final int iVersion, final byte iRecordType) {
    checkOpeness();
    return updateRecord(getClusterById(iRid.clusterId), iRid, iContent, iVersion, iRecordType);
  }

  public boolean deleteRecord(final ORecordId iRid, final int iVersion) {
    checkOpeness();
    return deleteRecord(getClusterById(iRid.clusterId), iRid, iVersion);
  }

  public Set<String> getClusterNames() {
    checkOpeness();

    lock.acquireSharedLock();
    try {

      return clusterMap.keySet();

    } finally {
      lock.releaseSharedLock();
    }
  }

  public int getClusterIdByName(final String iClusterName) {
    checkOpeness();

    if (iClusterName == null)
      throw new IllegalArgumentException("Cluster name is null");

    if (iClusterName.length() == 0)
      throw new IllegalArgumentException("Cluster name is empty");

    if (Character.isDigit(iClusterName.charAt(0)))
      return Integer.parseInt(iClusterName);

    // SEARCH IT BETWEEN PHYSICAL CLUSTERS
    lock.acquireSharedLock();
    try {

      final OCluster segment = clusterMap.get(iClusterName.toLowerCase());
      if (segment != null)
        return segment.getId();

    } finally {
      lock.releaseSharedLock();
    }

    return -1;
  }

  public String getClusterTypeByName(final String iClusterName) {
    checkOpeness();

    if (iClusterName == null)
      throw new IllegalArgumentException("Cluster name is null");

    // SEARCH IT BETWEEN PHYSICAL CLUSTERS
    lock.acquireSharedLock();
    try {

      final OCluster segment = clusterMap.get(iClusterName.toLowerCase());
      if (segment != null)
        return segment.getType();

    } finally {
      lock.releaseSharedLock();
    }

    return null;
  }

  public void commit(final OTransaction iTx) {
    lock.acquireExclusiveLock();
    try {

      try {
        txManager.commitAllPendingRecords(iTx);

        incrementVersion();
        if (OGlobalConfiguration.TX_COMMIT_SYNCH.getValueAsBoolean())
          synch();

      } catch (RuntimeException e) {
        // WE NEED TO CALL ROLLBACK HERE, IN THE LOCK
        rollback(iTx);
        throw e;
      } catch (IOException e) {
        // WE NEED TO CALL ROLLBACK HERE, IN THE LOCK
        rollback(iTx);
        throw new OException(e);
      }

      try {
        txManager.clearLogEntries(iTx);
      } catch (Exception e) {
        // XXX WHAT CAN WE DO HERE ? ROLLBACK IS NOT POSSIBLE
        // IF WE THROW EXCEPTION, A ROLLBACK WILL BE DONE AT DB LEVEL BUT NOT AT STORAGE LEVEL
        OLogManager.instance().error(this, "Clear tx log entries failed", e);
      }
    } finally {
      lock.releaseExclusiveLock();
    }
  }

  public void rollback(final OTransaction iTx) {
    try {
      txManager.getTxSegment().rollback(iTx);
      if (OGlobalConfiguration.TX_COMMIT_SYNCH.getValueAsBoolean())
        synch();
    } catch (IOException ioe) {
      OLogManager.instance().error(this,
          "Error executing rollback for transaction with id '" + iTx.getId() + "' cause: " + ioe.getMessage(), ioe);
    }
  }

  public void synch() {
    checkOpeness();

    final long timer = OProfiler.getInstance().startChrono();

    lock.acquireExclusiveLock();
    try {
      saveVersion();

      for (OCluster cluster : clusters)
        if (cluster != null)
          cluster.synch();

      for (ODataLocal data : dataSegments)
        if (data != null)
          data.synch();

    } catch (IOException e) {
      throw new OStorageException("Error on synch", e);

    } finally {
      lock.releaseExclusiveLock();

      OProfiler.getInstance().stopChrono("storage." + name + ".synch", timer);
    }
  }

  /**
   * Returns the list of holes as pair of position & ODataHoleInfo
   *
   * @throws IOException
   */
  public List<ODataHoleInfo> getHolesList() {
    final List<ODataHoleInfo> holes = new ArrayList<ODataHoleInfo>();

    lock.acquireSharedLock();
    try {

      for (ODataLocal d : dataSegments)
        holes.addAll(d.getHolesList());

      return holes;

    } finally {
      lock.releaseSharedLock();
    }
  }

  /**
   * Returns the total number of holes.
   *
   * @throws IOException
   */
  public long getHoles() {
    lock.acquireSharedLock();
    try {

      long holes = 0;
      for (ODataLocal d : dataSegments)
        holes += d.getHoles();
      return holes;

    } finally {
      lock.releaseSharedLock();
    }
  }

  /**
   * Returns the total size used by holes
   *
   * @throws IOException
   */
  public long getHoleSize() {
    lock.acquireSharedLock();
    try {

      final List<ODataHoleInfo> holes = getHolesList();
      long size = 0;
      for (ODataHoleInfo h : holes)
        if (h.dataOffset > -1 && h.size > 0)
          size += h.size;

      return size;

    } finally {
      lock.releaseSharedLock();
    }
  }

  public String getPhysicalClusterNameById(final int iClusterId) {
    checkOpeness();

    lock.acquireSharedLock();
    try {

      if (iClusterId >= clusters.length)
        return null;

      return clusters[iClusterId] != null ? clusters[iClusterId].getName() : null;

    } finally {
      lock.releaseSharedLock();
    }
  }

  @Override
  public OStorageConfiguration getConfiguration() {
    return configuration;
  }

  public int getDefaultClusterId() {
    return defaultClusterId;
  }

  public OCluster getClusterById(int iClusterId) {
    lock.acquireSharedLock();
    try {

      if (iClusterId == ORID.CLUSTER_ID_INVALID)
        // GET THE DEFAULT CLUSTER
        iClusterId = defaultClusterId;

      checkClusterSegmentIndexRange(iClusterId);

      return clusters[iClusterId];

    } finally {
      lock.releaseSharedLock();
    }
  }

  @Override
  public OCluster getClusterByName(final String iClusterName) {
    lock.acquireSharedLock();
    try {

      final OCluster cluster = clusterMap.get(iClusterName.toLowerCase());

      if (cluster == null)
        throw new IllegalArgumentException("Cluster " + iClusterName + " not exists");
      return cluster;

    } finally {
      lock.releaseSharedLock();
    }
  }

  public long getSize() {
    lock.acquireSharedLock();
    try {

      long size = 0;

      for (ODataLocal d : dataSegments)
        if (d != null)
          size += d.getFilledUpTo();

      for (OCluster c : clusters)
        if (c != null)
          size += c.getSize();

      return size;

    } finally {
      lock.releaseSharedLock();
    }
  }

  public OStorageConfigurationSegment getConfigurationSegment() {
    return configurationSegment;
  }

  public String getStoragePath() {
    return storagePath;
  }

  public String getMode() {
    return mode;
  }

  public OStorageVariableParser getVariableParser() {
    return variableParser;
  }

  public int getClusters() {
    lock.acquireSharedLock();
    try {

      return clusters.length;

    } finally {
      lock.releaseSharedLock();
    }
  }

  public Set<OCluster> getClusterInstances() {
    final Set<OCluster> result = new HashSet<OCluster>();

    lock.acquireSharedLock();
    try {

      // ADD ALL THE CLUSTERS
      for (OCluster c : clusters)
        result.add(c);

    } finally {
      lock.releaseSharedLock();
    }

    return result;
  }

  /**
   * Method that completes the cluster rename operation. <strong>IT WILL NOT RENAME A CLUSTER, IT JUST CHANGES THE NAME IN THE
   * INTERNAL MAPPING</strong>
   */
  public void renameCluster(final String iOldName, final String iNewName) {
    clusterMap.put(iNewName, clusterMap.remove(iOldName));
  }

  protected int registerDataSegment(final OStorageDataConfiguration iConfig) throws IOException {
    checkOpeness();

    int pos = 0;

    // CHECK FOR DUPLICATION OF NAMES
    for (ODataLocal data : dataSegments)
      if (data.getName().equals(iConfig.name)) {
        // OVERWRITE CONFIG
        data.config = iConfig;
        return -1;
      }
    pos = dataSegments.length;

    // CREATE AND ADD THE NEW REF SEGMENT
    final ODataLocal segment = new ODataLocal(this, iConfig, pos);

    dataSegments = OArrays.copyOf(dataSegments, dataSegments.length + 1);
    dataSegments[pos] = segment;

    return pos;
  }

  /**
   * Create the cluster by reading the configuration received as argument and register it assigning it the higher serial id.
   *
   * @param iConfig
   *          A OStorageClusterConfiguration implementation, namely physical or logical
   * @return The id (physical position into the array) of the new cluster just created. First is 0.
   * @throws IOException
   * @throws IOException
   */
  private int createClusterFromConfig(final OStorageClusterConfiguration iConfig) throws IOException {
    OCluster cluster = clusterMap.get(iConfig.getName());

    if (cluster != null) {
      if (cluster instanceof OClusterLocal)
        // ALREADY CONFIGURED, JUST OVERWRITE CONFIG
        ((OClusterLocal) cluster).config = (OStorageSegmentConfiguration) iConfig;
      return -1;
    }

    if (iConfig instanceof OStoragePhysicalClusterConfiguration)
      cluster = new OClusterLocal(this, (OStoragePhysicalClusterConfiguration) iConfig);
    else
      cluster = new OClusterLogical(this, (OStorageLogicalClusterConfiguration) iConfig);

    return registerCluster(cluster);
  }

  /**
   * Register the cluster internally.
   *
   * @param iCluster
   *          OCluster implementation
   * @return The id (physical position into the array) of the new cluster just created. First is 0.
   * @throws IOException
   */
  private int registerCluster(final OCluster iCluster) throws IOException {
    final int id;

    if (iCluster != null) {
      // CHECK FOR DUPLICATION OF NAMES
      if (clusterMap.containsKey(iCluster.getName()))
        throw new OConfigurationException("Can't add segment '" + iCluster.getName() + "' because it was already registered");
      // CREATE AND ADD THE NEW REF SEGMENT
      clusterMap.put(iCluster.getName(), iCluster);
      id = iCluster.getId();
    } else
      id = clusters.length;

    clusters = OArrays.copyOf(clusters, clusters.length + 1);
    clusters[id] = iCluster;

    return id;
  }

  private void checkClusterSegmentIndexRange(final int iClusterId) {
    if (iClusterId > clusters.length - 1)
      throw new IllegalArgumentException("Cluster segment #" + iClusterId + " not exists");
  }

  protected int getDataSegmentForRecord(final OCluster iCluster, final byte[] iContent) {
    // TODO: CREATE POLICY & STRATEGY TO ASSIGN THE BEST-MULTIPLE DATA
    // SEGMENT
    return 0;
  }

  protected long createRecord(final OCluster iClusterSegment, final byte[] iContent, final byte iRecordType) {
    checkOpeness();

    if (iContent == null)
      throw new IllegalArgumentException("Record is null");

    final long timer = OProfiler.getInstance().startChrono();

    lock.acquireSharedLock();
    try {

      lockManager.acquireLock(Thread.currentThread(), ORecordId.EMPTY_RECORD_ID, LOCK.EXCLUSIVE);
      try {

        final int dataSegment = getDataSegmentForRecord(iClusterSegment, iContent);
        final ODataLocal data = getDataSegment(dataSegment);

        final ORecordId rid = new ORecordId(iClusterSegment.getId());
        rid.clusterPosition = iClusterSegment.addPhysicalPosition(-1, -1, iRecordType);

        final long dataOffset = data.addRecord(rid, iContent);

        // UPDATE THE POSITION IN CLUSTER WITH THE POSITION OF RECORD IN
        // DATA
        iClusterSegment.setPhysicalPosition(rid.clusterPosition, dataSegment, dataOffset, iRecordType);

        incrementVersion();

        return rid.clusterPosition;

      } finally {
        lockManager.releaseLock(Thread.currentThread(), ORecordId.EMPTY_RECORD_ID, LOCK.EXCLUSIVE);
      }
    } catch (IOException e) {

      OLogManager.instance().error(this, "Error on creating record in cluster: " + iClusterSegment, e);
      return -1;
    } finally {
      lock.releaseSharedLock();

      OProfiler.getInstance().stopChrono(PROFILER_CREATE_RECORD, timer);
    }
  }

  @Override
  protected ORawBuffer readRecord(final OCluster iClusterSegment, final ORecordId iRid, boolean iAtomicLock) {
    if (iRid.clusterPosition < 0)
      throw new IllegalArgumentException("Can't read the record " + iRid + " since the position is invalid");

    // NOT FOUND: SEARCH IT IN THE STORAGE
    final long timer = OProfiler.getInstance().startChrono();

    // GET LOCK ONLY IF IT'S IN ATOMIC-MODE (SEE THE PARAMETER iAtomicLock)
    // USUALLY BROWSING OPERATIONS (QUERY) AVOID ATOMIC LOCKING
    // TO IMPROVE PERFORMANCES BY LOCKING THE ENTIRE CLUSTER FROM THE
    // OUTSIDE.
    if (iAtomicLock)
      lock.acquireSharedLock();

    try {

      lockManager.acquireLock(Thread.currentThread(), iRid, LOCK.SHARED);
      try {

        final long lastPos = iClusterSegment.getLastEntryPosition();

        if (iRid.clusterPosition > lastPos)
          throw new ORecordNotFoundException("Record " + iRid + " is out cluster size. Valid range for cluster '"
              + iClusterSegment.getName() + "' is 0-" + lastPos);

        final OPhysicalPosition ppos = iClusterSegment.getPhysicalPosition(iRid.clusterPosition, new OPhysicalPosition());
        if (ppos == null || !checkForRecordValidity(ppos))
          // DELETED
          return null;

        final ODataLocal data = getDataSegment(ppos.dataSegment);
        return new ORawBuffer(data.getRecord(ppos.dataPosition), ppos.version, ppos.type);

      } finally {
        lockManager.releaseLock(Thread.currentThread(), iRid, LOCK.SHARED);
      }

    } catch (IOException e) {

      OLogManager.instance().error(this, "Error on reading record " + iRid + " (cluster: " + iClusterSegment + ")", e);
      return null;

    } finally {
      if (iAtomicLock)
        lock.releaseSharedLock();

      OProfiler.getInstance().stopChrono(PROFILER_READ_RECORD, timer);
    }
  }

  protected int updateRecord(final OCluster iClusterSegment, final ORecordId iRid, final byte[] iContent, final int iVersion,
      final byte iRecordType) {
    final long timer = OProfiler.getInstance().startChrono();

    lock.acquireExclusiveLock();

    try {
      lockManager.acquireLock(Thread.currentThread(), iRid, LOCK.EXCLUSIVE);
      try {
        final OPhysicalPosition ppos = iClusterSegment.getPhysicalPosition(iRid.clusterPosition, new OPhysicalPosition());
        if (!checkForRecordValidity(ppos))
          // DELETED
          return -1;

        // MVCC TRANSACTION: CHECK IF VERSION IS THE SAME
        if (iVersion > -1 && iVersion != ppos.version)
          throw new OConcurrentModificationException(
              "Can't update record "
                  + iRid
                  + " because the version is not the latest one. Probably you are updating an old record or it has been modified by another user (db=v"
                  + ppos.version + " your=v" + iVersion + ")");

        if (ppos.type != iRecordType)
          iClusterSegment.updateRecordType(iRid.clusterPosition, iRecordType);

        iClusterSegment.updateVersion(iRid.clusterPosition, ++ppos.version);

        final long newDataSegmentOffset;
        if (ppos.dataPosition == -1)
          // WAS EMPTY FIRST TIME, CREATE IT NOW
          newDataSegmentOffset = getDataSegment(ppos.dataSegment).addRecord(iRid, iContent);
        else
          // UPDATE IT
          newDataSegmentOffset = getDataSegment(ppos.dataSegment).setRecord(ppos.dataPosition, iRid, iContent);

        if (newDataSegmentOffset != ppos.dataPosition)
          // UPDATE DATA SEGMENT OFFSET WITH THE NEW PHYSICAL POSITION
          iClusterSegment.setPhysicalPosition(iRid.clusterPosition, ppos.dataSegment, newDataSegmentOffset, iRecordType);

        incrementVersion();

        return ppos.version;

      } finally {
        lockManager.releaseLock(Thread.currentThread(), iRid, LOCK.EXCLUSIVE);
      }
    } catch (IOException e) {

      OLogManager.instance().error(this, "Error on updating record " + iRid + " (cluster: " + iClusterSegment + ")", e);

    } finally {
      lock.releaseExclusiveLock();

      OProfiler.getInstance().stopChrono(PROFILER_UPDATE_RECORD, timer);
    }

    return -1;
  }

  protected boolean deleteRecord(final OCluster iClusterSegment, final ORecordId iRid, final int iVersion) {
    final long timer = OProfiler.getInstance().startChrono();

    lock.acquireExclusiveLock();
    try {

      lockManager.acquireLock(Thread.currentThread(), iRid, LOCK.EXCLUSIVE);
      try {

        final OPhysicalPosition ppos = iClusterSegment.getPhysicalPosition(iRid.clusterPosition, new OPhysicalPosition());

        if (!checkForRecordValidity(ppos))
          // ALREADY DELETED
          return false;

        // MVCC TRANSACTION: CHECK IF VERSION IS THE SAME
        if (iVersion > -1 && ppos.version != iVersion)
          throw new OConcurrentModificationException(
              "Can't delete the record "
                  + iRid
                  + " because the version is not the latest one. Probably you are deleting an old record or it has been modified by another user (db=v"
                  + ppos.version + " your=v" + iVersion + ")");

        iClusterSegment.removePhysicalPosition(iRid.clusterPosition, ppos);

        if (ppos.dataPosition > -1)
          getDataSegment(ppos.dataSegment).deleteRecord(ppos.dataPosition);

        incrementVersion();

        return true;

      } finally {
        lockManager.releaseLock(Thread.currentThread(), iRid, LOCK.EXCLUSIVE);
      }
    } catch (IOException e) {

      OLogManager.instance().error(this, "Error on deleting record " + iRid + "( cluster: " + iClusterSegment + ")", e);

    } finally {
      lock.releaseExclusiveLock();

      OProfiler.getInstance().stopChrono(PROFILER_DELETE_RECORD, timer);
    }

    return false;
  }

  /***
   * Save the version number to disk
   *
   * @throws IOException
   */
  private void saveVersion() throws IOException {
    lock.acquireExclusiveLock();
    try {

      dataSegments[0].saveVersion(version.get());

    } finally {
      lock.releaseExclusiveLock();
    }
  }

  /**
   * Read the storage version from disk;
   *
   * @return Long as serial version number
   * @throws IOException
   */
  private long loadVersion() throws IOException {
    lock.acquireExclusiveLock();
    try {

      final long v = dataSegments[0].loadVersion();
      version.set(v);
      return v;

    } finally {
      lock.releaseExclusiveLock();
    }
  }

  /**
   * Add a new physical cluster into the storage.
   *
   * @throws IOException
   */
  private int addPhysicalCluster(final String iClusterName, String iClusterFileName, final int iStartSize) throws IOException {
    lock.acquireExclusiveLock();
    try {

      final OClusterLocal cluster;

      if (iClusterName != null) {
        final OStoragePhysicalClusterConfiguration config = new OStoragePhysicalClusterConfiguration(configuration, iClusterName,
            clusters.length);
        configuration.clusters.add(config);

        cluster = new OClusterLocal(this, config);
      } else
        cluster = null;

      final int id = registerCluster(cluster);

      if (iClusterName != null) {
        clusters[id].create(iStartSize);
        configuration.update();
      }

      return id;

    } finally {
      lock.releaseExclusiveLock();
    }
  }

  @Deprecated
  private int addLogicalCluster(final String iClusterName, final int iPhysicalCluster) throws IOException {
    lock.acquireExclusiveLock();
    try {

      final OClusterLogical cluster;

      if (iClusterName != null) {
        final OStorageLogicalClusterConfiguration config = new OStorageLogicalClusterConfiguration(iClusterName, clusters.length,
            iPhysicalCluster, null);

        configuration.clusters.add(config);

        cluster = new OClusterLogical(this, clusters.length, iClusterName, iPhysicalCluster);
        config.map = cluster.getRID();
      } else
        cluster = null;

      final int id = registerCluster(cluster);

      if (iClusterName != null)
        configuration.update();

      return id;

    } finally {
      lock.releaseExclusiveLock();
    }
  }

  private int addMemoryCluster(final String iClusterName) throws IOException {
    lock.acquireExclusiveLock();
    try {
      final OClusterMemory cluster;

      if (iClusterName != null) {
        final OStorageMemoryClusterConfiguration config = new OStorageMemoryClusterConfiguration(iClusterName, clusters.length);

        configuration.clusters.add(config);

        cluster = new OClusterMemory(clusters.length, iClusterName);
      } else
        cluster = null;

      final int id = registerCluster(cluster);

      if (iClusterName != null)
        configuration.update();

      return id;

    } finally {
      lock.releaseExclusiveLock();
    }
  }

  private void installProfilerHooks() {
    OProfiler.getInstance().registerHookValue("storage." + name + ".data.holes", new OProfilerHookValue() {
      public Object getValue() {
        return getHoles();
      }
    });
    OProfiler.getInstance().registerHookValue("storage." + name + ".data.holeSize", new OProfilerHookValue() {
      public Object getValue() {
        return getHoleSize();
      }
    });
  }
}
TOP

Related Classes of com.orientechnologies.orient.core.storage.impl.local.OStorageLocal

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.