Package org.openrdf.sail.memory

Source Code of org.openrdf.sail.memory.MemoryStore

/*
* Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2008.
*
* Licensed under the Aduna BSD-style license.
*/
package org.openrdf.sail.memory;

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

import info.aduna.concurrent.locks.ExclusiveLockManager;
import info.aduna.concurrent.locks.Lock;
import info.aduna.concurrent.locks.ReadPrefReadWriteLockManager;
import info.aduna.concurrent.locks.ReadWriteLockManager;

import org.openrdf.OpenRDFUtil;
import org.openrdf.cursor.Cursor;
import org.openrdf.cursor.EmptyCursor;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.sail.SailMetaData;
import org.openrdf.sail.helpers.DefaultSailChangedEvent;
import org.openrdf.sail.helpers.DirectoryLockManager;
import org.openrdf.sail.helpers.SailUtil;
import org.openrdf.sail.inferencer.InferencerConnection;
import org.openrdf.sail.inferencer.helpers.AutoCommitInferencerConnection;
import org.openrdf.sail.inferencer.helpers.InferencerSailBase;
import org.openrdf.sail.inferencer.helpers.SynchronizedInferencerConnection;
import org.openrdf.sail.memory.model.MemBNodeFactory;
import org.openrdf.sail.memory.model.MemLiteralFactory;
import org.openrdf.sail.memory.model.MemResource;
import org.openrdf.sail.memory.model.MemStatement;
import org.openrdf.sail.memory.model.MemStatementCursor;
import org.openrdf.sail.memory.model.MemStatementList;
import org.openrdf.sail.memory.model.MemURI;
import org.openrdf.sail.memory.model.MemURIFactory;
import org.openrdf.sail.memory.model.MemValue;
import org.openrdf.sail.memory.model.MemValueFactory;
import org.openrdf.sail.memory.model.ReadMode;
import org.openrdf.sail.memory.model.TxnStatus;
import org.openrdf.store.StoreException;

/**
* An implementation of the Sail interface that stores its data in main memory
* and that can use a file for persistent storage. This Sail implementation
* supports single, isolated transactions. This means that changes to the data
* are not visible until a transaction is committed and that concurrent
* transactions are not possible. When another transaction is active, calls to
* <tt>startTransaction()</tt> will block until the active transaction is
* committed or rolled back.
*
* @author Arjohn Kampman
* @author jeen
*/
public class MemoryStore extends InferencerSailBase {

  /*-----------*
   * Constants *
   *-----------*/

  protected static final String DATA_FILE_NAME = "memorystore.data";

  protected static final String SYNC_FILE_NAME = "memorystore.sync";

  /*-----------*
   * Variables *
   *-----------*/

  /**
   * Factory/cache for MemBNode objects.
   */
  private final MemBNodeFactory bf = new MemBNodeFactory();

  /**
   * Factory/cache for MemURI objects.
   */
  private final MemURIFactory uf = new MemURIFactory();

  /**
   * Factory/cache for MemLiteral objects.
   */
  private final MemLiteralFactory lf = new MemLiteralFactory();

  /**
   * List containing all available statements.
   */
  private final MemStatementList statements = new MemStatementList(256);

  /**
   * Set of all statements that have been affected by a transaction.
   */
  private volatile IdentityHashMap<MemStatement, MemStatement> txnStatements;

  /**
   * Identifies the current snapshot.
   */
  private volatile int currentSnapshot;

  /**
   * Store for namespace prefix info.
   */
  private final MemNamespaceStore namespaceStore = new MemNamespaceStore();

  /**
   * Lock manager used to give the snapshot cleanup thread exclusive access to
   * the statement list.
   */
  private final ReadWriteLockManager statementListLockManager = new ReadPrefReadWriteLockManager(
      SailUtil.isDebugEnabled());

  /**
   * Lock manager used to prevent concurrent transactions.
   */
  private final ExclusiveLockManager txnLockManager = new ExclusiveLockManager(SailUtil.isDebugEnabled());

  /**
   * Flag indicating whether the Sail has been initialized.
   */
  private volatile boolean initialized = false;

  private volatile boolean persist = false;

  /**
   * The file used for data persistence, null if this is a volatile RDF store.
   */
  private volatile File dataFile;

  /**
   * The file used for serialising data, null if this is a volatile RDF store.
   */
  private volatile File syncFile;

  /**
   * The directory lock, null if this is read-only or a volatile RDF store.
   */
  private volatile Lock dirLock;

  /**
   * Flag indicating whether the contents of this repository have changed.
   */
  private volatile boolean contentsChanged;

  /**
   * The sync delay.
   *
   * @see #setSyncDelay
   */
  private volatile long syncDelay = 0L;

  /**
   * Semaphore used to synchronize concurrent access to {@link #sync()}.
   */
  private final Object syncSemaphore = new Object();

  /**
   * The timer used to trigger file synchronization.
   */
  private volatile Timer syncTimer;

  /**
   * The currently scheduled timer task, if any.
   */
  private volatile TimerTask syncTimerTask;

  /**
   * Semaphore used to synchronize concurrent access to {@link #syncTimer} and
   * {@link #syncTimerTask}.
   */
  private final Object syncTimerSemaphore = new Object();

  /**
   * Cleanup thread that removes deprecated statements when no other threads
   * are accessing this list. Seee {@link #scheduleSnapshotCleanup()}.
   */
  private volatile Thread snapshotCleanupThread;

  /**
   * Semaphore used to synchronize concurrent access to
   * {@link #snapshotCleanupThread}.
   */
  private final Object snapshotCleanupThreadSemaphore = new Object();

  /*--------------*
   * Constructors *
   *--------------*/

  /**
   * Creates a new MemoryStore.
   */
  public MemoryStore() {
  }

  /**
   * Creates a new persistent MemoryStore. If the specified data directory
   * contains an existing store, its contents will be restored upon
   * initialization.
   *
   * @param dataDir
   *        the data directory to be used for persistence.
   */
  public MemoryStore(File dataDir) {
    setDataDir(dataDir);
    setPersist(true);
  }

  /*---------*
   * Methods *
   *---------*/

  @Override
  public SailMetaData getMetaData() {
    return new MemoryStoreMetaData(this);
  }

  @Override
  public void setDataDir(File dataDir) {
    if (isInitialized()) {
      throw new IllegalStateException("sail has already been initialized");
    }

    super.setDataDir(dataDir);
  }

  public void setPersist(boolean persist) {
    if (isInitialized()) {
      throw new IllegalStateException("sail has already been initialized");
    }

    this.persist = persist;
  }

  public boolean getPersist() {
    return persist;
  }

  /**
   * Sets the time (in milliseconds) to wait after a transaction was commited
   * before writing the changed data to file. Setting this variable to 0 will
   * force a file sync immediately after each commit. A negative value will
   * deactivate file synchronization until the Sail is shut down. A positive
   * value will postpone the synchronization for at least that amount of
   * milliseconds. If in the meantime a new transaction is started, the file
   * synchronization will be rescheduled to wait for another <tt>syncDelay</tt>
   * ms. This way, bursts of transaction events can be combined in one file
   * sync.
   * <p>
   * The default value for this parameter is <tt>0</tt> (immediate
   * synchronization).
   *
   * @param syncDelay
   *        The sync delay in milliseconds.
   */
  public void setSyncDelay(long syncDelay) {
    if (isInitialized()) {
      throw new IllegalStateException("sail has already been initialized");
    }

    this.syncDelay = syncDelay;
  }

  /**
   * Gets the currently configured sync delay.
   *
   * @return syncDelay The sync delay in milliseconds.
   * @see #setSyncDelay
   */
  public long getSyncDelay() {
    return syncDelay;
  }

  /**
   * Initializes this repository. If a persistence file is defined for the
   * store, the contents will be restored.
   *
   * @throws StoreException
   *         when initialization of the store failed.
   */
  public void initialize()
    throws StoreException
  {
    if (isInitialized()) {
      throw new IllegalStateException("sail has already been intialized");
    }

    logger.debug("Initializing MemoryStore...");

    MemValueFactory valueFactory = createValueFactory();
    currentSnapshot = 1;

    if (persist) {
      File dataDir = getDataDir();
      DirectoryLockManager locker = new DirectoryLockManager(dataDir);
      dataFile = new File(dataDir, DATA_FILE_NAME);
      syncFile = new File(dataDir, SYNC_FILE_NAME);

      if (dataFile.exists()) {
        logger.debug("Reading data from {}...", dataFile);

        // Initialize persistent store from file
        if (!dataFile.canRead()) {
          logger.error("Data file is not readable: {}", dataFile);
          throw new StoreException("Can't read data file: " + dataFile);
        }
        // try to create a lock for later writing
        dirLock = locker.tryLock();
        if (dirLock == null) {
          logger.warn("Failed to lock directory: {}", dataDir);
        }
        // Don't try to read empty files: this will result in an
        // IOException, and the file doesn't contain any data anyway.
        if (dataFile.length() == 0L) {
          logger.warn("Ignoring empty data file: {}", dataFile);
        }
        else {
          try {
            new FileIO(this, valueFactory).read(dataFile);
            logger.debug("Data file read successfully");
          }
          catch (IOException e) {
            logger.error("Failed to read data file", e);
            throw new StoreException(e);
          }
        }
      }
      else {
        // file specified that does not exist yet, create it
        try {
          File dir = dataFile.getParentFile();
          if (dir != null && !dir.exists()) {
            logger.debug("Creating directory for data file...");
            if (!dir.mkdirs()) {
              logger.debug("Failed to create directory for data file: {}", dir);
              throw new StoreException("Failed to create directory for data file: " + dir);
            }
          }
          // try to lock directory or fail
          dirLock = locker.lockOrFail();

          logger.debug("Initializing data file...");
          new FileIO(this, valueFactory).write(syncFile, dataFile);
          logger.debug("Data file initialized");
        }
        catch (IOException e) {
          logger.debug("Failed to initialize data file", e);
          throw new StoreException("Failed to initialize data file " + dataFile, e);
        }
        catch (StoreException e) {
          logger.debug("Failed to initialize data file", e);
          throw new StoreException("Failed to initialize data file " + dataFile, e);
        }
      }
    }

    contentsChanged = false;
    initialized = true;

    logger.debug("MemoryStore initialized");
  }

  /**
   * Checks whether the Sail has been initialized.
   *
   * @return <tt>true</tt> if the Sail has been initialized, <tt>false</tt>
   *         otherwise.
   */
  protected final boolean isInitialized() {
    return initialized;
  }

  @Override
  protected void shutDownInternal()
    throws StoreException
  {
    if (isInitialized()) {
      Lock stLock = getStatementsReadLock();

      try {
        cancelSyncTimer();
        sync();

        statements.clear();
        bf.clear();
        uf.clear();
        lf.clear();
        dataFile = null;
        syncFile = null;
        initialized = false;
      }
      finally {
        stLock.release();
        if (dirLock != null) {
          dirLock.release();
        }
      }
    }
  }

  /**
   * Checks whether this Sail object is writable. A MemoryStore is not writable
   * if a read-only data file is used.
   */
  public boolean isWritable() {
    // Sail is not writable when it has a dataDir, but no directory lock
    return !persist || dirLock != null;
  }

  @Override
  protected InferencerConnection getConnectionInternal()
    throws StoreException
  {
    if (!isInitialized()) {
      throw new IllegalStateException("sail not initialized.");
    }

    InferencerConnection con = new MemoryStoreConnection(this);
    con = new SynchronizedInferencerConnection(con);
    con = new AutoCommitInferencerConnection(con);
    return con;
  }

  public MemURIFactory getURIFactory() {
    if (uf == null) {
      throw new IllegalStateException("sail not initialized.");
    }

    return uf;
  }

  public MemLiteralFactory getLiteralFactory() {
    if (lf == null) {
      throw new IllegalStateException("sail not initialized.");
    }

    return lf;
  }

  MemValueFactory createValueFactory() {
    return new MemValueFactory(bf, uf, lf);
  }

  protected MemNamespaceStore getNamespaceStore() {
    return namespaceStore;
  }

  protected MemStatementList getStatements() {
    return statements;
  }

  protected int getCurrentSnapshot() {
    return currentSnapshot;
  }

  protected Lock getStatementsReadLock()
    throws StoreException
  {
    try {
      return statementListLockManager.getReadLock();
    }
    catch (InterruptedException e) {
      throw new StoreException(e);
    }
  }

  protected Lock getTransactionLock()
    throws StoreException
  {
    try {
      return txnLockManager.getExclusiveLock();
    }
    catch (InterruptedException e) {
      throw new StoreException(e);
    }
  }

  protected int size() {
    return statements.size();
  }

  protected boolean hasStatement(Resource subj, URI pred, Value obj, boolean explicitOnly, int snapshot,
      ReadMode readMode, MemValueFactory vf, Resource... contexts) throws StoreException
  {
    Cursor<MemStatement> cursor = createStatementIterator(subj, pred, obj, explicitOnly, snapshot, readMode, vf, contexts);
    try {
      return cursor.next() != null;
    }
    finally {
      cursor.close();
    }
  }

  /**
   * Creates a StatementIterator that contains the statements matching the
   * specified pattern of subject, predicate, object, context. Inferred
   * statements are excluded when <tt>explicitOnly</tt> is set to <tt>true</tt>
   * . Statements from the null context are excluded when
   * <tt>namedContextsOnly</tt> is set to <tt>true</tt>. The returned
   * StatementIterator will assume the specified read mode.
   */
  protected Cursor<MemStatement> createStatementIterator(Resource subj, URI pred, Value obj,
      boolean explicitOnly, int snapshot, ReadMode readMode, MemValueFactory vf, Resource... contexts)
  {
    // Perform look-ups for value-equivalents of the specified values
    MemResource memSubj = vf.getMemResource(subj);
    if (subj != null && memSubj == null) {
      // non-existent subject
      return new EmptyCursor<MemStatement>();
    }

    MemURI memPred = vf.getMemURI(pred);
    if (pred != null && memPred == null) {
      // non-existent predicate
      return new EmptyCursor<MemStatement>();
    }

    MemValue memObj = vf.getMemValue(obj);
    if (obj != null && memObj == null) {
      // non-existent object
      return new EmptyCursor<MemStatement>();
    }

    MemResource[] memContexts;
    MemStatementList smallestList;

    contexts = OpenRDFUtil.notNull(contexts);
    if (contexts.length == 0) {
      memContexts = new MemResource[0];
      smallestList = statements;
    }
    else if (contexts.length == 1 && contexts[0] != null) {
      MemResource memContext = vf.getMemResource(contexts[0]);
      if (memContext == null) {
        // non-existent context
        return new EmptyCursor<MemStatement>();
      }

      memContexts = new MemResource[] { memContext };
      smallestList = memContext.getContextStatementList();
    }
    else {
      Set<MemResource> contextSet = new LinkedHashSet<MemResource>(2 * contexts.length);

      for (Resource context : contexts) {
        MemResource memContext = vf.getMemResource(context);
        if (context == null || memContext != null) {
          contextSet.add(memContext);
        }
      }

      if (contextSet.isEmpty()) {
        // no known contexts specified
        return new EmptyCursor<MemStatement>();
      }

      memContexts = contextSet.toArray(new MemResource[contextSet.size()]);
      smallestList = statements;
    }

    if (memSubj != null) {
      MemStatementList l = memSubj.getSubjectStatementList();
      if (l.size() < smallestList.size()) {
        smallestList = l;
      }
    }

    if (memPred != null) {
      MemStatementList l = memPred.getPredicateStatementList();
      if (l.size() < smallestList.size()) {
        smallestList = l;
      }
    }

    if (memObj != null) {
      MemStatementList l = memObj.getObjectStatementList();
      if (l.size() < smallestList.size()) {
        smallestList = l;
      }
    }

    return new MemStatementCursor(smallestList, memSubj, memPred, memObj, explicitOnly, snapshot, readMode,
        memContexts);
  }

  protected Statement addStatement(Resource subj, URI pred, Value obj, Resource context, boolean explicit,
      MemValueFactory vf)
    throws StoreException
  {
    assert txnStatements != null;
    boolean newValueCreated = false;

    // Get or create MemValues for the operands
    MemResource memSubj = vf.getMemResource(subj);
    if (memSubj == null) {
      memSubj = vf.createMemResource(subj);
      newValueCreated = true;
    }
    MemURI memPred = vf.getMemURI(pred);
    if (memPred == null) {
      memPred = vf.createMemURI(pred);
      newValueCreated = true;
    }
    MemValue memObj = vf.getMemValue(obj);
    if (memObj == null) {
      memObj = vf.createMemValue(obj);
      newValueCreated = true;
    }
    MemResource memContext = vf.getMemResource(context);
    if (context != null && memContext == null) {
      memContext = vf.createMemResource(context);
      newValueCreated = true;
    }

    if (!newValueCreated) {
      // All values were already present in the graph. Possibly, the
      // statement is already present. Check this.
      Cursor<MemStatement> stIter = createStatementIterator(memSubj, memPred, memObj, false,
          currentSnapshot + 1, ReadMode.RAW, vf, memContext);

      try {
        MemStatement st = stIter.next();
        if (st != null) {
          // statement is already present, update its transaction
          // status if appropriate

          txnStatements.put(st, st);

          TxnStatus txnStatus = st.getTxnStatus();

          if (txnStatus == TxnStatus.NEUTRAL && !st.isExplicit() && explicit) {
            // Implicit statement is now added explicitly
            st.setTxnStatus(TxnStatus.EXPLICIT);
          }
          else if (txnStatus == TxnStatus.NEW && !st.isExplicit() && explicit) {
            // Statement was first added implicitly and now
            // explicitly
            st.setExplicit(true);
          }
          else if (txnStatus == TxnStatus.DEPRECATED) {
            if (st.isExplicit() == explicit) {
              // Statement was removed but is now re-added
              st.setTxnStatus(TxnStatus.NEUTRAL);
            }
            else if (explicit) {
              // Implicit statement was removed but is now added
              // explicitly
              st.setTxnStatus(TxnStatus.EXPLICIT);
            }
            else {
              // Explicit statement was removed but can still be
              // inferred
              st.setTxnStatus(TxnStatus.INFERRED);
            }

            return st;
          }
          else if (txnStatus == TxnStatus.INFERRED && st.isExplicit() && explicit) {
            // Explicit statement was removed but is now re-added
            st.setTxnStatus(TxnStatus.NEUTRAL);
          }
          else if (txnStatus == TxnStatus.ZOMBIE) {
            // Restore zombie statement
            st.setTxnStatus(TxnStatus.NEW);
            st.setExplicit(explicit);

            return st;
          }

          return null;
        }
      }
      finally {
        stIter.close();
      }
    }

    // completely new statement
    MemStatement st = new MemStatement(memSubj, memPred, memObj, memContext, explicit, currentSnapshot + 1,
        TxnStatus.NEW);
    statements.add(st);
    st.addToComponentLists();

    txnStatements.put(st, st);
   
    assert hasStatement(memSubj, memPred, memObj, explicit, currentSnapshot + 1, ReadMode.TRANSACTION, vf, memContext);

    return st;
  }

  protected boolean removeStatement(MemStatement st, boolean explicit)
    throws StoreException
  {
    boolean statementsRemoved = false;
    TxnStatus txnStatus = st.getTxnStatus();

    if (txnStatus == TxnStatus.NEUTRAL && st.isExplicit() == explicit) {
      // Remove explicit statement
      st.setTxnStatus(TxnStatus.DEPRECATED);
      statementsRemoved = true;
    }
    else if (txnStatus == TxnStatus.NEW && st.isExplicit() == explicit) {
      // Statement was added and now removed in the same transaction
      st.setTxnStatus(TxnStatus.ZOMBIE);
      statementsRemoved = true;
    }
    else if (txnStatus == TxnStatus.INFERRED && st.isExplicit() && !explicit) {
      // Explicit statement was replaced by inferred statement and this
      // inferred statement is now removed
      st.setTxnStatus(TxnStatus.DEPRECATED);
      statementsRemoved = true;
    }
    else if (txnStatus == TxnStatus.EXPLICIT && !st.isExplicit() && explicit) {
      // Inferred statement was replaced by explicit statement, but this is
      // now undone
      st.setTxnStatus(TxnStatus.NEUTRAL);
    }

    txnStatements.put(st, st);

    return statementsRemoved;
  }

  protected void startTransaction()
    throws StoreException
  {
    cancelSyncTask();

    assert txnStatements == null;
    txnStatements = new IdentityHashMap<MemStatement, MemStatement>();
  }

  protected void commit()
    throws StoreException
  {
    assert txnStatements != null;

    boolean statementsAdded = false;
    boolean statementsRemoved = false;
    boolean statementsDeprecated = false;

    int txnSnapshot = currentSnapshot + 1;

    for (MemStatement st : txnStatements.keySet()) {
      TxnStatus txnStatus = st.getTxnStatus();

      if (txnStatus == TxnStatus.NEUTRAL) {
        continue;
      }
      else if (txnStatus == TxnStatus.NEW) {
        statementsAdded = true;
      }
      else if (txnStatus == TxnStatus.DEPRECATED) {
        st.setTillSnapshot(txnSnapshot);
        statementsRemoved = true;
      }
      else if (txnStatus == TxnStatus.ZOMBIE) {
        st.setTillSnapshot(txnSnapshot);
        statementsDeprecated = true;
      }
      else if (txnStatus == TxnStatus.EXPLICIT || txnStatus == TxnStatus.INFERRED) {
        // Deprecate the existing statement...
        st.setTillSnapshot(txnSnapshot);
        statementsDeprecated = true;

        // ...and add a clone with modified explicit/implicit flag
        MemStatement explSt = new MemStatement(st.getSubject(), st.getPredicate(), st.getObject(),
            st.getContext(), txnStatus == TxnStatus.EXPLICIT, txnSnapshot);
        statements.add(explSt);
        explSt.addToComponentLists();
      }

      st.setTxnStatus(TxnStatus.NEUTRAL);
    }

    txnStatements = null;

    if (statementsAdded || statementsRemoved || statementsDeprecated) {
      currentSnapshot = txnSnapshot;
    }

    if (statementsAdded || statementsRemoved) {
      contentsChanged = true;
      scheduleSyncTask();

      DefaultSailChangedEvent event = new DefaultSailChangedEvent(this);
      event.setStatementsAdded(statementsAdded);
      event.setStatementsRemoved(statementsRemoved);
      notifySailChanged(event);
    }

    if (statementsRemoved || statementsDeprecated) {
      scheduleSnapshotCleanup();
    }
  }

  protected void rollback()
    throws StoreException
  {
    assert txnStatements != null;
    logger.debug("rolling back transaction");

    boolean statementsDeprecated = false;

    for (MemStatement st : txnStatements.keySet()) {
      TxnStatus txnStatus = st.getTxnStatus();
      if (txnStatus == TxnStatus.NEW || txnStatus == TxnStatus.ZOMBIE) {
        // Statement has been added during this transaction and deprecates
        // immediately
        st.setTillSnapshot(currentSnapshot);
        statementsDeprecated = true;
      }
      else if (txnStatus != TxnStatus.NEUTRAL) {
        // Return statement to neutral status
        st.setTxnStatus(TxnStatus.NEUTRAL);
      }
    }

    txnStatements = null;

    if (statementsDeprecated) {
      scheduleSnapshotCleanup();
    }
  }

  protected void scheduleSyncTask()
    throws StoreException
  {
    if (!persist) {
      return;
    }

    if (syncDelay == 0L) {
      // Sync immediately
      sync();
    }
    else if (syncDelay > 0L) {
      synchronized (syncTimerSemaphore) {
        // Sync in syncDelay milliseconds
        if (syncTimer == null) {
          // Create the syncTimer on a deamon thread
          syncTimer = new Timer("MemoryStore synchronization", true);
        }

        if (syncTimerTask != null) {
          logger.error("syncTimerTask is not null");
        }

        syncTimerTask = new TimerTask() {

          @Override
          public void run() {
            try {
              // Acquire read lock to guarantee that the statement list
              // doesn't change while writing
              Lock stLock = getStatementsReadLock();
              try {
                sync();
              }
              finally {
                stLock.release();
              }
            }
            catch (StoreException e) {
              logger.warn("Unable to sync on timer", e);
            }
          }
        };

        syncTimer.schedule(syncTimerTask, syncDelay);
      }
    }
  }

  protected void cancelSyncTask() {
    synchronized (syncTimerSemaphore) {
      if (syncTimerTask != null) {
        syncTimerTask.cancel();
        syncTimerTask = null;
      }
    }
  }

  protected void cancelSyncTimer() {
    synchronized (syncTimerSemaphore) {
      if (syncTimer != null) {
        syncTimer.cancel();
        syncTimer = null;
      }
    }
  }

  /**
   * Synchronizes the contents of this repository with the data that is stored
   * on disk. Data will only be written when the contents of the repository and
   * data in the file are out of sync.
   */
  public void sync()
    throws StoreException
  {
    synchronized (syncSemaphore) {
      if (persist && contentsChanged) {
        logger.debug("syncing data to file...");
        try {
          new FileIO(this, createValueFactory()).write(syncFile, dataFile);
          contentsChanged = false;
          logger.debug("Data synced to file");
        }
        catch (IOException e) {
          logger.error("Failed to sync to file", e);
          throw new StoreException(e);
        }
      }
    }
  }

  /**
   * Removes statements from old snapshots from the main statement list and
   * resets the snapshot to 1 for the rest of the statements.
   *
   * @throws InterruptedException
   */
  protected void cleanSnapshots()
    throws InterruptedException
  {
    // System.out.println("cleanSnapshots() starting...");
    // long startTime = System.currentTimeMillis();
    MemStatementList statements = this.statements;

    if (statements == null) {
      // Store has been shut down
      return;
    }

    // Sets used to keep track of which lists have already been processed
    HashSet<MemValue> processedSubjects = new HashSet<MemValue>();
    HashSet<MemValue> processedPredicates = new HashSet<MemValue>();
    HashSet<MemValue> processedObjects = new HashSet<MemValue>();
    HashSet<MemValue> processedContexts = new HashSet<MemValue>();

    Lock stLock = statementListLockManager.getWriteLock();
    try {
      for (int i = statements.size() - 1; i >= 0; i--) {
        MemStatement st = statements.get(i);

        if (st.getTillSnapshot() <= currentSnapshot) {
          MemResource subj = st.getSubject();
          if (processedSubjects.add(subj)) {
            subj.cleanSnapshotsFromSubjectStatements(currentSnapshot);
          }

          MemURI pred = st.getPredicate();
          if (processedPredicates.add(pred)) {
            pred.cleanSnapshotsFromPredicateStatements(currentSnapshot);
          }

          MemValue obj = st.getObject();
          if (processedObjects.add(obj)) {
            obj.cleanSnapshotsFromObjectStatements(currentSnapshot);
          }

          MemResource context = st.getContext();
          if (context != null && processedContexts.add(context)) {
            context.cleanSnapshotsFromContextStatements(currentSnapshot);
          }

          // stale statement
          statements.remove(i);
        }
        else {
          // Reset snapshot
          st.setSinceSnapshot(1);
        }
      }

      currentSnapshot = 1;
    }
    finally {
      stLock.release();
    }

    // long endTime = System.currentTimeMillis();
    // System.out.println("cleanSnapshots() took " + (endTime - startTime) +
    // " ms");
  }

  protected void scheduleSnapshotCleanup() {
    synchronized (snapshotCleanupThreadSemaphore) {
      if (snapshotCleanupThread == null || !snapshotCleanupThread.isAlive()) {
        Runnable runnable = new Runnable() {

          public void run() {
            try {
              cleanSnapshots();
            }
            catch (InterruptedException e) {
              logger.warn("snapshot cleanup interrupted");
            }
          }
        };

        snapshotCleanupThread = new Thread(runnable, "MemoryStore snapshot cleanup");
        snapshotCleanupThread.setDaemon(true);
        snapshotCleanupThread.start();
      }
    }
  }
}
TOP

Related Classes of org.openrdf.sail.memory.MemoryStore

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.