Package com.sun.sgs.impl.service.data.store

Source Code of com.sun.sgs.impl.service.data.store.DataStoreImpl$ThreadTxnInfoTable$Entry

/*
* Copyright 2007-2010 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
* --
*/

package com.sun.sgs.impl.service.data.store;

import com.sun.sgs.app.ObjectNotFoundException;
import com.sun.sgs.app.TransactionAbortedException;
import com.sun.sgs.app.TransactionNotActiveException;
import com.sun.sgs.impl.kernel.StandardProperties;
import static com.sun.sgs.impl.service.data.store.
    DataStoreHeader.ALLOCATION_BLOCK_SIZE;
import static com.sun.sgs.impl.service.data.store.
    DataStoreHeader.FIRST_PLACEHOLDER_ID_KEY;
import static com.sun.sgs.impl.service.data.store.
    DataStoreHeader.PLACEHOLDER_OBJ_VALUE;
import static com.sun.sgs.impl.service.data.store.
    DataStoreHeader.QUOTE_OBJ_VALUE;
import com.sun.sgs.impl.service.data.store.DbUtilities.Databases;
import com.sun.sgs.impl.sharedutil.LoggerWrapper;
import static com.sun.sgs.impl.sharedutil.Objects.checkNull;
import com.sun.sgs.impl.sharedutil.PropertiesWrapper;
import com.sun.sgs.kernel.ComponentRegistry;
import com.sun.sgs.service.Transaction;
import com.sun.sgs.service.TransactionParticipant;
import com.sun.sgs.service.TransactionProxy;
import com.sun.sgs.service.store.ClassInfoNotFoundException;
import com.sun.sgs.service.store.db.DbCursor;
import com.sun.sgs.service.store.db.DbDatabase;
import com.sun.sgs.service.store.db.DbDatabaseException;
import com.sun.sgs.service.store.db.DbEnvironment;
import com.sun.sgs.service.store.db.DbTransaction;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Properties;
import java.util.Queue;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;

/*
* XXX: Implement recovery for prepared transactions after a crash.
* -tjb@sun.com (11/07/2006)
*/

/**
* Provides an implementation of <code>DataStore</code> based on the database
* interface layer defined in the {@link
* com.sun.sgs.service.store.db} package. <p>
*
* Note that, although this class provides support for the {@link
* TransactionParticipant#prepare TransactionParticipant.prepare} method, it
* does not provide facilities for resolving prepared transactions after a
* crash.  Callers can work around this limitation by insuring that the
* transaction implementation calls {@link
* TransactionParticipant#prepareAndCommit
* TransactionParticipant.prepareAndCommit} to commit transactions on this
* class.  The current transaction implementation calls
* <code>prepareAndCommit</code> on durable participants, such as this class,
* so the inability to resolve prepared transactions should have no effect at
* present. <p>
*
* The {@link #DataStoreImpl constructor} supports these public <a
* href="../../../../impl/kernel/doc-files/config-properties.html#DataStore">
* properties</a>. <p>
*
* The constructor also passes the properties to the constructor of
* the {@link DbEnvironment} class chosen at runtime with the
* {@code com.sun.sgs.impl.service.data.store.db.environment.class} property.
* Each implementation of {@code DbEnvironment} may support additional
* properties. <p>
*
* This class uses the {@link Logger} named
* <code>com.sun.sgs.impl.service.data.store.DataStoreImpl</code> to log
* information at the following logging levels: <p>
*
* <ul>
* <li> {@link Level#SEVERE SEVERE} - Initialization failures
* <li> {@link Level#CONFIG CONFIG} - Constructor properties, data store
*  headers
* <li> {@link Level#FINE FINE} - Allocating blocks of object IDs
* <li> {@link Level#FINER FINER} - Transaction operations
* <li> {@link Level#FINEST FINEST} - Name and object operations
* </ul> <p>
*
* In addition, name and object operations that throw {@link
* TransactionAbortedException} will log the failure to the {@code Logger}
* named {@code com.sun.sgs.impl.service.data.store.DataStoreImpl.abort}, to
* make it easier to debug concurrency conflicts.
*/
public class DataStoreImpl extends AbstractDataStore {

    /** The name of this class. */
    private static final String CLASSNAME =
  "com.sun.sgs.impl.service.data.store.DataStoreImpl";

    /**
     * The property that specifies the directory in which to store database
     * files.
     */
    public static final String DIRECTORY_PROPERTY = CLASSNAME + ".directory";

    /** The default directory for database files from the app root. */
    private static final String DEFAULT_DIRECTORY = "dsdb";
   
    /** The property that specifies the environment class. */
    public static final String ENVIRONMENT_CLASS_PROPERTY =
  "com.sun.sgs.impl.service.data.store.db.environment.class";
   
    /** The default environment class. */
    public static final String DEFAULT_ENVIRONMENT_CLASS =
        "com.sun.sgs.impl.service.data.store.db.je.JeEnvironment";

    /** The object data for a placeholder. */
    private static final byte[] PLACEHOLDER_DATA = { PLACEHOLDER_OBJ_VALUE };

    /** The directory in which to store database files. */
    private final String directory;

    /** Stores information about transactions. */
    private final TxnInfoTable<TxnInfo> txnInfoTable;

    /** The database environment. */
    private final DbEnvironment env;

    /**
     * The database that holds version and next object ID information.  This
     * information is stored in a separate database to avoid concurrency
     * conflicts between the object ID and other data.
     */
    private final DbDatabase infoDb;

    /** The database that stores class information. */
    private final DbDatabase classesDb;

    /** The database that maps object IDs to object bytes. */
    final DbDatabase oidsDb;

    /** The database that maps name bindings to object IDs. */
    private final DbDatabase namesDb;

    /** The local node ID. */
    private final long nodeId;

    /**
     * Whether object allocations should create a placeholder at the end of
     * each allocation block.  These placeholders help to avoid allocation
     * concurrency conflicts when using BDB Java edition.
     */
    final boolean useAllocationBlockPlaceholders;

    /** Information about free object IDs. */
    final FreeObjectIds freeObjectIds;

    /**
     * Object to synchronize on when accessing txnCount, allOps and
     * shuttingDown.
     */
    private final Object txnCountLock = new Object();

    /** The number of currently active transactions. */
    private int txnCount = 0;

    /** Whether the data store is in the process of shutting down. */
    private boolean shuttingDown = false;

    /**
     * Records information about all active transactions.
     *
     * @param  <T> the type of information stored for each transaction
     */
    protected interface TxnInfoTable<T> {

  /**
   * Returns the information associated with the transaction, or null if
   * none is found.
   *
   * @param  txn the transaction
   * @return  the associated information, or null if none is found
   * @throws  TransactionNotActiveException if the implementation
   *              determines that the transaction is no longer active
   * @throws  IllegalStateException if the implementation determines
   *    that the specified transaction does not match the
   *    current context
   */
  T get(Transaction txn);

  /**
   * Removes the information associated with the transaction.
   *
   * @param  txn the transaction
   * @return  the previously associated information
   * @throws  IllegalStateException if the transaction is not active,
   *    or if the implementation determines that the specified
   *    transaction does not match the current context
   */
  T remove(Transaction txn);

  /**
   * Sets the information associated with the transaction, which should
   * not currently have associated information.
   *
   * @param  txn the transaction
   * @param  info the associated information
   */
  void set(Transaction txn, T info);
    }

    /**
     * An implementation of TxnInfoTable that uses a thread local to record
     * information about transactions, and requires that the same thread always
     * be used with a given transaction.
     */
    private static class ThreadTxnInfoTable<T> implements TxnInfoTable<T> {

  /**
   * Provides information about the transaction for the current thread.
   */
  private final ThreadLocal<Entry<T>> threadInfo =
      new ThreadLocal<Entry<T>>();

  /** Stores a transaction and the associated information. */
  private static class Entry<T> {
      final Transaction txn;
      final T info;
      Entry(Transaction txn, T info) {
    this.txn = txn;
    this.info = info;
      }
  }

  /** Creates an instance. */
  ThreadTxnInfoTable() { }

  /* -- Implement TxnInfoTable -- */

  public T get(Transaction txn) {
      Entry<T> entry = threadInfo.get();
      if (entry == null) {
    return null;
      } else if (!entry.txn.equals(txn)) {
    throw new IllegalStateException(
        "Wrong transaction: Got " + txn + ", expected " +
        entry.txn);
      } else {
    return entry.info;
      }
  }

  public T remove(Transaction txn) {
      Entry<T> entry = threadInfo.get();
      if (entry == null) {
    throw new IllegalStateException("Transaction not active");
      } else if (!entry.txn.equals(txn)) {
    throw new IllegalStateException("Wrong transaction");
      }
      threadInfo.set(null);
      return entry.info;
  }

  public void set(Transaction txn, T info) {
      threadInfo.set(new Entry<T>(txn, info));
  }
    }

    /** Stores transaction information. */
    private class TxnInfo {

  /** The associated database transaction. */
  final DbTransaction dbTxn;

  /** Whether preparation of the transaction has started. */
  boolean prepared;

  /** Whether any changes have been made in this transaction. */
  boolean modified;

  /** The currently open cursor over the names database, or null. */
  private DbCursor namesCursor;

  /** The last key returned by the namesCursor or null. */
  private String lastNamesCursorKey;

  /** The currently open cursor over the oids database, or null. */
  private DbCursor oidsCursor;

  /** The last key returned by the oidsCursor or -1. */
  private long lastOidsCursorKey = -1;

  /**
   * Information about object IDs available for allocation in this
   * transaction, or null if this transaction has not done allocation.
   */
  private ObjectIdInfo objectIdInfo = null;

  /**
   * Object ID blocks whose last IDs were used during this transaction,
   * or null if there were no such blocks.  The empty blocks will be
   * rolled back on abort, and will have their placeholders removed on
   * commit.
   */
  private List<ObjectIdInfo> emptyObjectIdInfo = null;

  TxnInfo(Transaction txn, DbEnvironment env) {
      dbTxn = env.beginTransaction(txn.getTimeout());
  }

  /**
   * Prepares the transaction, first updating object ID information and
   * closing cursors.
   */
  void prepare(byte[] gid) {
      prepareFreeObjectIds();
      maybeCloseCursors(false);
      dbTxn.prepare(gid);
  }

  /**
   * Prepares and commits the transaction, first updating object ID
   * information and closing cursors.
   */
  void prepareAndCommit() {
      prepareFreeObjectIds();
      maybeCloseCursors(false);
      dbTxn.commit();
  }

  /**
   * Updates object ID information for a transaction that is going to be
   * committed.  Returns object ID blocks that have more IDs to the free
   * list, and updates allocation block placeholders for blocks that are
   * empty.
   */
  private void prepareFreeObjectIds() {
      /* Move an empty objectIdInfo to emptyObjectIdInfo */
      if (objectIdInfo != null && !objectIdInfo.hasNext()) {
    if (emptyObjectIdInfo == null) {
        emptyObjectIdInfo = new LinkedList<ObjectIdInfo>();
    }
    emptyObjectIdInfo.add(objectIdInfo);
    objectIdInfo = null;
      }
      /*
       * Remove placeholders for empty blocks.  Note that this operation
       * may fail, because it operates on the database, so do it first
       * and without holding the lock on the free object IDs.
       */
      if (useAllocationBlockPlaceholders && emptyObjectIdInfo != null) {
    for (ObjectIdInfo empty : emptyObjectIdInfo) {
        long placeholder = empty.last();
        byte[] key = DataEncoding.encodeLong(placeholder);
        byte[] value = oidsDb.get(dbTxn, key, true);
        if (value != null && isPlaceholderValue(value)) {
      boolean success = oidsDb.delete(dbTxn, key);
      assert success;
        }
    }
      }
      freeObjectIds.prepare(objectIdInfo, emptyObjectIdInfo);
      objectIdInfo = null;
      emptyObjectIdInfo = null;
  }

  /**
   * Commits the transaction, which should already have been prepared.
   */
  void commit() {
      dbTxn.commit();
  }

  /**
   * Aborts the transaction, first updating object ID information and
   * closing cursors.
   */
  void abort() {
      freeObjectIds.abort(objectIdInfo, emptyObjectIdInfo);
      objectIdInfo = null;
      emptyObjectIdInfo = null;
      maybeCloseCursors(true);
      dbTxn.abort();
  }

  /** Returns the next name in the names database. */
  String nextName(String name, DbDatabase names) {
      if (namesCursor == null) {
    namesCursor = names.openCursor(dbTxn);
      }
      if (name == null) {
    lastNamesCursorKey = namesCursor.findFirst()
        ? DataEncoding.decodeString(namesCursor.getKey()) : null;
      } else {
    boolean matchesLast = name.equals(lastNamesCursorKey);
    if (!matchesLast) {
        /*
         * The name specified was not the last key returned, so
         * search for the specified name
         */
        lastNamesCursorKey =
      namesCursor.findNext(DataEncoding.encodeString(name))
      ? DataEncoding.decodeString(namesCursor.getKey())
      : null;
        /* Record if we found an exact match */
        matchesLast = name.equals(lastNamesCursorKey);
    }
    if (matchesLast) {
        /* The last key was an exact match, so find the next one */
        lastNamesCursorKey = namesCursor.findNext()
      ? DataEncoding.decodeString(namesCursor.getKey())
      : null;
    }
      }
      return lastNamesCursorKey;
  }

  /** Returns the next object ID in the oids database. */
  long nextObjectId(long oid, DbDatabase oids) {
      if (oidsCursor == null) {
    oidsCursor = oids.openCursor(dbTxn);
      }
      if (oid == -1) {
    lastOidsCursorKey = oidsCursor.findFirst()
        ? DataEncoding.decodeLong(oidsCursor.getKey()) : -1;
      } else {
    boolean matchesLast = (oid == lastOidsCursorKey);
    if (!matchesLast) {
        /*
         * The OID specified was not the last key returned, so
         * search for the specified OID
         */
        lastOidsCursorKey =
      oidsCursor.findNext(DataEncoding.encodeLong(oid))
      ? DataEncoding.decodeLong(oidsCursor.getKey()) : -1;
        /* Record if we found an exact match */
        matchesLast = (oid == lastOidsCursorKey);
    }
    if (matchesLast) {
        /* The last key was an exact match, so find the next one */
        lastOidsCursorKey = oidsCursor.findNext()
      ? DataEncoding.decodeLong(oidsCursor.getKey()) : -1;
    }
      }
      /* Skip placeholders */
      while (lastOidsCursorKey != -1 &&
       isPlaceholderValue(oidsCursor.getValue()))
      {
    lastOidsCursorKey = oidsCursor.findNext()
        ? DataEncoding.decodeLong(oidsCursor.getKey()) : -1;
      }
      return lastOidsCursorKey;
  }

  /**
   * Close the cursors if they are open.  Always null the cursor fields,
   * since the Berkeley DB API doesn't permit closing a cursor after an
   * attempt to close it.  If forAbort is true, then we are aborting the
   * transaction.  In that case, ignore abort exceptions when closing the
   * cursors, to make sure we complete the operations needed on abort.
   */
  private void maybeCloseCursors(boolean forAbort) {
      if (namesCursor != null) {
    try {
        namesCursor.close();
    } catch (TransactionAbortedException e) {
        if (forAbort) {
      logger.logThrow(
          Level.FINEST, e,
          "Exception closing names cursor during abort");
        } else {
      throw e;
        }
    } finally {
        namesCursor = null;
    }
      }
      if (oidsCursor != null) {
    try {
        oidsCursor.close();
    } catch (TransactionAbortedException e) {
        if (forAbort) {
      logger.logThrow(
          Level.FINEST, e,
          "Exception closing OIDs cursor during abort");
        } else {
      throw e;
        }
    } finally {
        oidsCursor = null;
    }
      }
  }

  /**
   * Returns information about free object IDs available for allocation
   * in this transaction, or null if no IDs are already available.
   */
  ObjectIdInfo getObjectIdInfo() {
      if (objectIdInfo == null) {
    objectIdInfo = freeObjectIds.get();
    if (objectIdInfo != null) {
        objectIdInfo.initTxn();
    }
      } else if (!objectIdInfo.hasNext()) {
    if (emptyObjectIdInfo == null) {
        emptyObjectIdInfo = new LinkedList<ObjectIdInfo>();
    }
    emptyObjectIdInfo.add(objectIdInfo);
    objectIdInfo = null;
      }
      return objectIdInfo;
  }

  /**
   * Creates and stores information about newly available free object
   * IDs.
   */
  ObjectIdInfo createObjectIdInfo(
      long firstObjectId, long lastObjectId)
  {
      assert objectIdInfo == null;
      objectIdInfo = freeObjectIds.create(firstObjectId, lastObjectId);
      return objectIdInfo;
  }
    }

    /** Stores information about free object IDs. */
    private static final class FreeObjectIds {

  /**
   * Available allocation blocks, with the lowest ID block first.
   * Synchronize on the FreeObjectIds instance when accessing this field.
   */
  private final Queue<ObjectIdInfo> freeObjectIdInfo =
      new PriorityQueue<ObjectIdInfo>();

  /**
   * The set of object IDs of placeholders for allocation blocks that are
   * still in use, or null if placeholders are not being used.
   * Synchronize on the FreeObjectIds instance when accessing the
   * contents of this field.
   */
  private final SortedSet<Long> placeholderOids;

  /** Creates an instance of this class. */
  FreeObjectIds(boolean usePlaceholders) {
      placeholderOids = usePlaceholders ? new TreeSet<Long>() : null;
  }

  /** Obtains a block of object IDs, or null if none are available. */
  synchronized ObjectIdInfo get() {
      return freeObjectIdInfo.poll();
  }

  /**
   * Updates object ID information for a transaction that is going to be
   * committed.  Returns the object ID block, if not null, to the free
   * list, and updates placeholders for the empty blocks.
   */
  synchronized void prepare(ObjectIdInfo info,
          List<ObjectIdInfo> emptyObjectIdInfo)
  {
      if (info != null) {
    assert info.hasNext();
    freeObjectIdInfo.add(info);
      }
      if (placeholderOids != null && emptyObjectIdInfo != null) {
    for (ObjectIdInfo empty : emptyObjectIdInfo) {
        placeholderOids.remove(empty.last());
    }
      }
  }

  /**
   * Updates object ID information for a transaction that is being
   * aborted.  Rolls back the allocations in all blocks.
   */
  synchronized void abort(ObjectIdInfo info,
        List<ObjectIdInfo> emptyObjectIdInfo)
  {
      if (info != null) {
    info.abort();
    freeObjectIdInfo.add(info);
      }
      if (emptyObjectIdInfo != null) {
    for (ObjectIdInfo empty : emptyObjectIdInfo) {
        empty.abort();
        freeObjectIdInfo.add(empty);
    }
      }
  }

  /** Creates and returns a new block of object IDs. */
  ObjectIdInfo create(long firstObjectId, long lastObjectId) {
      assert firstObjectId >= 0;
      assert lastObjectId > firstObjectId;
      ObjectIdInfo info = new ObjectIdInfo(firstObjectId, lastObjectId);
      if (placeholderOids != null) {
    synchronized (this) {
        placeholderOids.add(lastObjectId);
    }
      }
      return info;
  }

  /**
   * Returns the object ID of the lowest-numbered object allocation block
   * placeholder currently in use, or -1 if none.  This method should
   * only be called if placeholders are in use.
   */
  synchronized long getFirstPlaceholder() {
      return placeholderOids.isEmpty() ? -1 : placeholderOids.first();
  }
    }

    /** Stores information about object IDs available for allocation. */
    private static final class ObjectIdInfo
  implements Comparable<ObjectIdInfo>
    {
  /** The first object ID in this block. */
  private final long firstObjectId;

  /**
   * The next object ID to use for creating an object.  Valid if not
   * greater than lastObjectId.
   */
  private long nextObjectId;

  /**
   * The last object ID that is free for allocating an object before
   * needing to obtain more IDs from the database.
   */
  private final long lastObjectId;

  /**
   * The value of nextObjectId at the start of the transaction, and which
   * it should be set to if the transaction aborts.
   */
  private long abortNextObjectId;

  /**
   * Creates an instance with the specified first and last object IDs.
   */
  ObjectIdInfo(long firstObjectId, long lastObjectId) {
      this.firstObjectId = firstObjectId;
      nextObjectId = firstObjectId;
      this.lastObjectId = lastObjectId;
      abortNextObjectId = nextObjectId;
  }

  /** Implement Comparable<ObjectIdInfo>, ordered by object ID. */
  public int compareTo(ObjectIdInfo other) {
      return Long.signum(lastObjectId - other.lastObjectId);
  }

  /** Use object ID for comparison. */
  public boolean equals(Object object) {
      return object instanceof ObjectIdInfo &&
    lastObjectId == ((ObjectIdInfo) object).lastObjectId;
  }

  /** Use object ID for hash code. */
  public int hashCode() {
      return (int) (lastObjectId >> 32) & (int) lastObjectId;
  }

  /**
   * Records the initial value of nextObjectId, so that it can be rolled
   * back if the transaction aborts.
   */
  void initTxn() {
      abortNextObjectId = nextObjectId;
  }

  /** Returns whether there are more object IDs available. */
  boolean hasNext() {
      return nextObjectId <= lastObjectId;
  }

  /** Returns the next object ID. */
  long next() {
      assert hasNext();
      return nextObjectId++;
  }

  /**
   * Sets nextObjectId back to the value from the start of the
   * transaction, for when the transaction aborts.
   */
  void abort() {
      nextObjectId = abortNextObjectId;
  }

  /** Returns the first object ID in this block. */
  long first() {
      return firstObjectId;
  }

  /** Returns the last object ID in this block. */
  long last() {
      return lastObjectId;
  }
    }

    /**
     * Creates an instance of this class.  See the {@linkplain DataStoreImpl
     * class documentation} for a list of supported properties.
     *
     * @param  properties the properties for configuring this instance
     * @param  systemRegistry the registry of available system components
     * @param  txnProxy the transaction proxy
     * @throws  DataStoreException if there is a problem with the database
     * @throws  IllegalArgumentException if any of the properties are invalid,
     *    as specified in the class documentation
     */
    public DataStoreImpl(Properties properties,
       ComponentRegistry systemRegistry,
       TransactionProxy txnProxy)
    {
  super(systemRegistry,
        new LoggerWrapper(Logger.getLogger(CLASSNAME)),
        new LoggerWrapper(Logger.getLogger(CLASSNAME + ".abort")));
        logger.log(Level.CONFIG, "Creating DataStoreImpl");

  PropertiesWrapper wrappedProps = new PropertiesWrapper(properties);
  String specifiedDirectory =
      wrappedProps.getProperty(DIRECTORY_PROPERTY);
  if (specifiedDirectory == null) {
      String rootDir =
    properties.getProperty(StandardProperties.APP_ROOT);
      if (rootDir == null) {
    throw new IllegalArgumentException(
        "A value for the property " + StandardProperties.APP_ROOT +
        " must be specified");
      }
      specifiedDirectory = rootDir + File.separator + DEFAULT_DIRECTORY;
  }
  /*
   * Use an absolute path to avoid problems on Windows.
   * -tjb@sun.com (02/16/2007)
   */
  directory = new File(specifiedDirectory).getAbsolutePath();
  txnInfoTable = getTxnInfoTable(TxnInfo.class);
  DbTransaction dbTxn = null;
  boolean done = false;
  try {
      File directoryFile = new File(specifiedDirectory).getAbsoluteFile();
            if (!directoryFile.exists()) {
                logger.log(Level.INFO, "Creating database directory : " +
                           directoryFile.getAbsolutePath());
                if (!directoryFile.mkdirs()) {
                    throw new DataStoreException("Unable to create database " +
                                                 "directory : " +
                                                 directoryFile.getName());
                }
      }
            env = wrappedProps.getClassInstanceProperty(
                    ENVIRONMENT_CLASS_PROPERTY,
                    DEFAULT_ENVIRONMENT_CLASS,
                    DbEnvironment.class,
                    new Class<?>[]{
                        String.class, Properties.class,
      ComponentRegistry.class, TransactionProxy.class
                    },
                    directory, properties, systemRegistry, txnProxy);
      dbTxn = env.beginTransaction(Long.MAX_VALUE);
      Databases dbs = DbUtilities.getDatabases(env, dbTxn, logger);
      infoDb = dbs.info();
      classesDb = dbs.classes();
      oidsDb = dbs.oids();
      namesDb = dbs.names();
      nodeId = DataStoreHeader.getNextId(
    DataStoreHeader.NEXT_NODE_ID_KEY, infoDb, dbTxn, 1);
      useAllocationBlockPlaceholders =
    env.useAllocationBlockPlaceholders();
      freeObjectIds = new FreeObjectIds(useAllocationBlockPlaceholders);
      removeUnusedAllocationPlaceholders(dbTxn);
      done = true;
      dbTxn.commit();

            logger.log(Level.CONFIG,
                       "Created DataStoreImpl with properties:" +
                       "\n  " + DIRECTORY_PROPERTY + "=" + specifiedDirectory +
                       "\n  " + ENVIRONMENT_CLASS_PROPERTY + "=" +
                       env.getClass().getName());
           
  } catch (RuntimeException e) {
      throw handleException(
    null, Level.SEVERE, e, "DataStore initialization");
  } catch (Error e) {
      logger.logThrow(
    Level.SEVERE, e, "DataStore initialization failed");
      throw e;
  } finally {
      if (dbTxn != null && !done) {
    try {
        dbTxn.abort();
    } catch (RuntimeException e) {
        logger.logThrow(Level.FINE, e, "Exception during abort");
    }
      }
  }
    }
   
    /**
     * Removes any unused allocation block placeholders and updates the ID of
     * the first placeholder.
     */
    private void removeUnusedAllocationPlaceholders(DbTransaction dbTxn) {
  byte[] firstPlaceholderKey =
      DataEncoding.encodeLong(FIRST_PLACEHOLDER_ID_KEY);
  long placeholderOid = DataEncoding.decodeLong(
      infoDb.get(dbTxn, firstPlaceholderKey, true));
  if (placeholderOid < 0) {
      logger.log(Level.FINEST, "No allocation placeholders");
      return;
  }
  DbCursor cursor = oidsDb.openCursor(dbTxn);
  try {
      while (cursor.findNext(DataEncoding.encodeLong(placeholderOid))) {
    byte[] key = cursor.getKey();
    if (DataEncoding.decodeLong(key) != placeholderOid) {
        if (logger.isLoggable(Level.FINEST)) {
      logger.log(Level.FINEST,
           "Placeholder oid:{0,number,#} not found",
           placeholderOid);
        }
    } else if (isPlaceholderValue(cursor.getValue())) {
        boolean success = oidsDb.delete(dbTxn, key);
        assert success;
        if (logger.isLoggable(Level.FINEST)) {
      logger.log(Level.FINEST,
           "Removed placeholder at oid:{0,number,#}",
           placeholderOid);
        }
    } else {
        if (logger.isLoggable(Level.FINEST)) {
      logger.log(Level.FINEST,
           "Ignoring oid:{0,number,#} that does not" +
           " refer to a placeholder",
           placeholderOid);
        }
    }
    placeholderOid += ALLOCATION_BLOCK_SIZE;
      }
      infoDb.put(
    dbTxn, firstPlaceholderKey, DataEncoding.encodeLong(-1));
  } finally {
      cursor.close();
  }
    }

    /* -- Implement AbstractDataStore's DataStore methods -- */

    /** {@inheritDoc} */
    protected long getLocalNodeIdInternal() {
  return nodeId;
    }

    /** {@inheritDoc} */
    protected long createObjectInternal(Transaction txn) {
  TxnInfo txnInfo = checkTxn(txn);
  ObjectIdInfo objectIdInfo = txnInfo.getObjectIdInfo();
  if (objectIdInfo == null) {
      logger.log(Level.FINE, "Allocate more object IDs");
      long newNextObjectId;
      long newLastObjectId;
      DbTransaction dbTxn = env.beginTransaction(txn.getTimeout());
      boolean done = false;
      try {
    newNextObjectId = DbUtilities.getNextObjectId(
        infoDb, dbTxn, ALLOCATION_BLOCK_SIZE);
    newLastObjectId =
        newNextObjectId + ALLOCATION_BLOCK_SIZE - 1;
    maybeUpdateAllocationBlockPlaceholders(
        dbTxn, newLastObjectId);
    done = true;
    dbTxn.commit();
      } finally {
    if (!done) {
        dbTxn.abort();
    }
      }
      objectIdInfo = txnInfo.createObjectIdInfo(
    newNextObjectId, newLastObjectId);
  }
  long result = objectIdInfo.next();
  if (useAllocationBlockPlaceholders && result == objectIdInfo.first()) {
      /*
       * Create the placeholder when using the first ID in the block.
       * Don't do this when storing the allocation and placeholder
       * information in the info database because this transaction may
       * already be holding a lock on the location in the OIDs database.
       */
      oidsDb.put(txnInfo.dbTxn,
           DataEncoding.encodeLong(objectIdInfo.last()),
           PLACEHOLDER_DATA);
  }
  return result;
    }

    /** {@inheritDoc} */
    protected void markForUpdateInternal(Transaction txn, long oid) {
  TxnInfo txnInfo = checkTxn(txn);
  oidsDb.markForUpdate(txnInfo.dbTxn, DataEncoding.encodeLong(oid));
    }

    /** {@inheritDoc} */
    protected byte[] getObjectInternal(
  Transaction txn, long oid, boolean forUpdate)
    {
  TxnInfo txnInfo = checkTxn(txn);
  byte[] result = oidsDb.get(
      txnInfo.dbTxn, DataEncoding.encodeLong(oid), forUpdate);
  if (result == null || isPlaceholderValue(result)) {
      throw new ObjectNotFoundException("Object not found: " + oid);
  }
  return decodeValue(result);
    }

    /** {@inheritDoc} */
    protected void setObjectInternal(Transaction txn, long oid, byte[] data) {
  TxnInfo txnInfo = checkTxn(txn);
  oidsDb.put(
      txnInfo.dbTxn, DataEncoding.encodeLong(oid), encodeValue(data));
  txnInfo.modified = true;
    }

    /** {@inheritDoc} */
    protected void setObjectsInternal(
  Transaction txn, long[] oids, byte[][] dataArray)
    {
  TxnInfo txnInfo = checkTxn(txn);
  for (int i = 0; i < oids.length; i++) {
      oidsDb.put(txnInfo.dbTxn, DataEncoding.encodeLong(oids[i]),
           encodeValue(dataArray[i]));
  }
  txnInfo.modified = true;
    }

    /** {@inheritDoc} */
    protected void removeObjectInternal(Transaction txn, long oid) {
  TxnInfo txnInfo = checkTxn(txn);
  byte[] key = DataEncoding.encodeLong(oid);
  boolean found = oidsDb.delete(txnInfo.dbTxn, key);
  if (!found) {
      throw new ObjectNotFoundException("Object not found: " + oid);
  }
  txnInfo.modified = true;
    }

    /** {@inheritDoc} */
    protected BindingValue getBindingInternal(Transaction txn, String name) {
  TxnInfo txnInfo = checkTxn(txn);
  byte[] value = namesDb.get(
      txnInfo.dbTxn, DataEncoding.encodeString(name), false);
  if (value == null) {
      return new BindingValue(-1, txnInfo.nextName(name, namesDb));
  } else {
      return new BindingValue(DataEncoding.decodeLong(value), null);
  }
    }

    /** {@inheritDoc} */
    protected BindingValue setBindingInternal(
  Transaction txn, String name, long oid)
    {
  TxnInfo txnInfo = checkTxn(txn);
  byte[] key = DataEncoding.encodeString(name);
  byte[] oldValue = namesDb.get(txnInfo.dbTxn, key, true);
  namesDb.put(txnInfo.dbTxn, key, DataEncoding.encodeLong(oid));
  txnInfo.modified = true;
  if (oldValue != null) {
      return new BindingValue(1, null);
  } else {
      return new BindingValue(-1, txnInfo.nextName(name, namesDb));
  }
    }

    /** {@inheritDoc} */
    protected BindingValue removeBindingInternal(
  Transaction txn, String name)
    {
  TxnInfo txnInfo = checkTxn(txn);
  boolean found = namesDb.delete(
      txnInfo.dbTxn, DataEncoding.encodeString(name));
  if (found) {
      txnInfo.modified = true;
      return new BindingValue(1, txnInfo.nextName(name, namesDb));
  } else {
      return new BindingValue(-1, txnInfo.nextName(name, namesDb));
  }
    }
   
    /**
     * {@inheritDoc} <p>
     *
     * This implementation uses a single cursor, so it provides better
     * performance when used to iterate over names in order.
     */
    protected String nextBoundNameInternal(Transaction txn, String name) {
  TxnInfo txnInfo = checkTxn(txn);
  return txnInfo.nextName(name, namesDb);
    }

    /** {@inheritDoc} */
    protected void shutdownInternal() {
  synchronized (txnCountLock) {
      shuttingDown = true;
      while (txnCount > 0) {
    try {
        logger.log(Level.FINEST,
             "shutdown waiting for {0} transactions",
             txnCount);
        txnCountLock.wait();
    } catch (InterruptedException e) {
        // loop until shutdown is complete
        logger.log(Level.FINEST, "DataStore shutdown " +
             "interrupt ignored");
    }
      }
      if (txnCount < 0) {
    return; // return silently
      }
     
      infoDb.close();
      classesDb.close();
      oidsDb.close();
      namesDb.close();
      env.close();
      txnCount = -1;
  }
    }

    /** {@inheritDoc} */
    protected int getClassIdInternal(Transaction txn, byte[] classInfo) {
  checkTxn(txn);
  return DbUtilities.getClassId(
      env, classesDb, classInfo, txn.getTimeout());
    }

    /** {@inheritDoc} */
    protected byte[] getClassInfoInternal(Transaction txn, int classId)
  throws ClassInfoNotFoundException
    {
  checkTxn(txn);
  byte[] result = DbUtilities.getClassInfo(
      env, classesDb, classId, txn.getTimeout());
  if (result != null) {
      return result;
  } else {
      throw new ClassInfoNotFoundException(
    "No information found for class ID " + classId);
  }
    }

    /** {@inheritDoc} */
    protected long nextObjectIdInternal(Transaction txn, long oid) {
  TxnInfo txnInfo = checkTxn(txn);
  return txnInfo.nextObjectId(oid, oidsDb);
    }

    /* -- Implement AbstractDataStore's TransactionParticipant methods -- */

    /** {@inheritDoc} */
    protected boolean prepareInternal(Transaction txn) {
  TxnInfo txnInfo = checkTxnNoJoin(txn);
  txn.checkTimeout();
  if (txnInfo.prepared) {
      throw new IllegalStateException(
    "Transaction has already been prepared");
  }
  if (txnInfo.modified) {
      byte[] tid = txn.getId();
      /*
       * Berkeley DB requires transaction IDs to be at least 128 bytes
       * long.  -tjb@sun.com (11/07/2006)
       */
      byte[] gid = new byte[128];
      /*
       * The current transaction implementation uses 8 byte transaction
       * IDs.  -tjb@sun.com (03/22/2007)
       */
      assert tid.length < 128 : "Transaction ID is too long";
      System.arraycopy(tid, 0, gid, 128 - tid.length, tid.length);
      txnInfo.prepare(gid);
      txnInfo.prepared = true;
  } else {
      /*
       * Make sure to clear the transaction information, regardless of
       * whether the Berkeley DB commit operation succeeds, since
       * Berkeley DB doesn't permit operating on its transaction object
       * after commit is called.
       */
      try {
    txnInfoTable.remove(txn);
    txnInfo.prepareAndCommit();
      } finally {
    decrementTxnCount();
      }
  }
  return !txnInfo.modified;
    }

    /** {@inheritDoc} */
    protected void commitInternal(Transaction txn) {
  TxnInfo txnInfo = checkTxnNoJoin(txn);
  if (!txnInfo.prepared) {
      throw new IllegalStateException(
    "Transaction has not been prepared");
  }
  /*
   * Make sure to clear the transaction information, regardless of
   * whether the Berkeley DB commit operation succeeds, since Berkeley DB
   * doesn't permit operating on its transaction object after commit is
   * called.
   */
  txnInfoTable.remove(txn);
  try {
      txnInfo.commit();
  } finally {
      decrementTxnCount();
  }
    }

    /** {@inheritDoc} */
    protected void prepareAndCommitInternal(Transaction txn) {
  TxnInfo txnInfo = checkTxnNoJoin(txn);
  txn.checkTimeout();
  if (txnInfo.prepared) {
      throw new IllegalStateException(
    "Transaction has already been prepared");
  }
  /*
   * Make sure to clear the transaction information, regardless of
   * whether the Berkeley DB commit operation succeeds, since Berkeley DB
   * doesn't permit operating on its transaction object after commit is
   * called.
   */
  txnInfoTable.remove(txn);
  try {
      txnInfo.prepareAndCommit();
  } finally {
      decrementTxnCount();
  }
    }

    /** {@inheritDoc} */
    protected void abortInternal(Transaction txn) {
  checkNull("txn", txn);
  TxnInfo txnInfo = txnInfoTable.remove(txn);
  if (txnInfo == null) {
      throw new IllegalStateException("Transaction is not active");
  }
  try {
      txnInfo.abort();
  } finally {
      decrementTxnCount();
  }
    }
   
    /* -- Other AbstractDataStore methods -- */

    /**
     * {@inheritDoc} <p>
     *
     * This implementation converts {@link DbDatabaseException} to {@link
     * DataStoreException}.
     */
    @Override
    protected RuntimeException handleException(
  Transaction txn, Level level, RuntimeException e, String operation)
    {
  if (e instanceof DbDatabaseException) {
      e = new DataStoreException(
    operation + " failed: " + e.getMessage(), e);
  }
  return super.handleException(txn, level, e, operation);
    }

    /* -- Other public methods -- */

    /**
     * Returns a string representation of this object.
     *
     * @return  a string representation of this object
     */
    public String toString() {
  return "DataStoreImpl[directory=\"" + directory + "\"]";
    }

    /* -- Protected methods -- */

    /**
     * Returns the table that will be used to store transaction information.
     * Note that this method will be called during instance construction.
     *
     * @param  <T> the type of the information to be stored
     * @param  txnInfoType a class representing the type of the information to
     *    be stored
     * @return  the table
     */
    protected <T> TxnInfoTable<T> getTxnInfoTable(Class<T> txnInfoType) {
  return new ThreadTxnInfoTable<T>();
    }

    /**
     * Returns the next available transaction ID, and reserves the specified
     * number of IDs.
     *
     * @param  count the number of IDs to reserve
     * @param  timeout the transaction timeout in milliseconds
     * @return  the next available transaction ID
     */
    protected long getNextTxnId(int count, long timeout) {
  try {
      return getNextId(DataStoreHeader.NEXT_TXN_ID_KEY, count, timeout);
  } catch (RuntimeException e) {
      throw handleException(
    null, Level.FINE, e, "getNextTxnId count:" + count);
  }
    }

    /**
     * Explicitly joins a new transaction.
     *
     * @param  txn the transaction to join
     */
    protected void joinNewTransaction(Transaction txn) {
  try {
      joinTransaction(txn);
  } catch (RuntimeException e) {
      throw handleException(
    txn, Level.FINER, e, "joinNewTransaction txn:" + txn);
  }
    }

    /**
     * Returns a new node ID, for use with a newly started node.
     *
     * @return  the new node ID
     */
    protected long newNodeId() {
  return getNextId(DataStoreHeader.NEXT_NODE_ID_KEY, 1, Long.MAX_VALUE);
    }

    /* -- Private methods -- */

    /**
     * Checks that the correct transaction is in progress, and join if none is
     * in progress.  The op argument, if non-null, specifies the operation
     * being performed under the specified transaction.
     */
    private TxnInfo checkTxn(Transaction txn) {
  if (txn == null) {
      throw new NullPointerException("Transaction must not be null");
  }
  TxnInfo txnInfo = txnInfoTable.get(txn);
  if (txnInfo == null) {
      txnInfo = joinTransaction(txn);
  } else if (txnInfo.prepared) {
      throw new IllegalStateException(
    "Transaction has been prepared");
  }
  return txnInfo;
    }

    /**
     * Joins the specified transaction, checking first to see if the data store
     * is currently shutting down, and returning the new TxnInfo.
     */
    private TxnInfo joinTransaction(Transaction txn) {
  synchronized (txnCountLock) {
      if (txnCount < 0) {
    throw new IllegalStateException("Service is shut down");
      } else if (shuttingDown) {
    throw new IllegalStateException("Service is shutting down");
      }
      txnCount++;
  }
  boolean joined = false;
  try {
      txn.join(this);
      joined = true;
      if (logger.isLoggable(Level.FINER)) {
    logger.log(Level.FINER, "join txn:{0}, thread:{1}",
         txn, Thread.currentThread().getName());
      }
  } finally {
      if (!joined) {
    decrementTxnCount();
      }
  }
  TxnInfo txnInfo = new TxnInfo(txn, env);
  txnInfoTable.set(txn, txnInfo);
  return txnInfo;
    }

    /**
     * Checks that the correct transaction is in progress, throwing an
     * exception if the transaction has not been joined.  Checks if the store
     * is shutting down, but does not check the prepared state of the
     * transaction.
     */
    private TxnInfo checkTxnNoJoin(Transaction txn) {
  if (txn == null) {
      throw new NullPointerException("Transaction must not be null");
  }
  TxnInfo txnInfo = txnInfoTable.get(txn);
  if (txnInfo == null) {
      throw new IllegalStateException("Transaction is not active");
  } else if (getTxnCount() < 0) {
      throw new IllegalStateException("DataStore is shutting down");
  }
  return txnInfo;
    }

    /** Returns the current transaction count. */
    private int getTxnCount() {
  synchronized (txnCountLock) {
      return txnCount;
  }
    }

    /**
     * Decrements the current transaction count.  If the argument is not null,
     * tallies the operations that were recorded for the transaction.
     */
    private void decrementTxnCount() {
  synchronized (txnCountLock) {
      txnCount--;
      if (txnCount <= 0) {
    txnCountLock.notifyAll();
      }
  }
    }

    /**
     * Returns the next available ID stored under the specified key, and
     * increments the stored value by the specified amount.  Uses the specified
     * timeout when creating a DB transaction.
     */
    private long getNextId(long key, int blockSize, long timeout) {
  DbTransaction dbTxn = env.beginTransaction(timeout);
  boolean done = false;
  try {
      long id = DataStoreHeader.getNextId(
    key, infoDb, dbTxn, blockSize);
      done = true;
      dbTxn.commit();
      return id;
  } finally {
      if (!done) {
    dbTxn.abort();
      }
  }
    }

    /**
     * Checks if an object value read from the database is from a placeholder,
     * meaning there is no object present.
     */
    static boolean isPlaceholderValue(byte[] value) {
  return value.length > 0 && value[0] == PLACEHOLDER_OBJ_VALUE;
    }

    /**
     * Encodes an object value for writing to the database to quote it's first
     * byte if it would conflict with PLACEHOLDER_OBJ_VALUE or QUOTE_OBJ_VALUE.
     */
    private static byte[] encodeValue(byte[] value) {
  if (value.length > 0) {
      if (value[0] == PLACEHOLDER_OBJ_VALUE ||
    value[0] == QUOTE_OBJ_VALUE)
      {
    byte[] result = new byte[value.length + 1];
    result[0] = QUOTE_OBJ_VALUE;
    System.arraycopy(value, 0, result, 1, value.length);
    return result;
      }
  }
  return value;
    }

    /**
     * Decodes an object value read from the database to account for quoting of
     * its first byte.  Throws an exception if the value represents a
     * placeholder.
     */
    private static byte[] decodeValue(byte[] value) {
  if (value.length > 0) {
      if (value[0] == PLACEHOLDER_OBJ_VALUE) {
    throw new IllegalArgumentException(
        "Attempt to decode a placeholder as an object value");
      } else if (value[0] == QUOTE_OBJ_VALUE) {
    byte[] result = new byte[value.length - 1];
    System.arraycopy(value, 1, result, 0, value.length - 1);
    return result;
      }
  }
  return value;
    }

    /**
     * Notes the first placeholder when starting to use a new allocation block
     * with the specified object ID at its end, if using allocation block
     * placeholders.
     */
    private void maybeUpdateAllocationBlockPlaceholders(
  DbTransaction dbTxn, long placeholderOid)
    {
  if (useAllocationBlockPlaceholders) {
      long firstPlaceholderOid = freeObjectIds.getFirstPlaceholder();
      if (firstPlaceholderOid == -1) {
    firstPlaceholderOid = placeholderOid;
      }
      infoDb.put(dbTxn,
           DataEncoding.encodeLong(FIRST_PLACEHOLDER_ID_KEY),
           DataEncoding.encodeLong(firstPlaceholderOid));
      if (logger.isLoggable(Level.FINEST)) {
    logger.log(Level.FINEST,
         "Note first placeholder oid:{0,number,#}",
         firstPlaceholderOid);
      }
  }
    }

    /**
     * Store raw data for the specified object ID.  The value is used as the
     * literal data, without checking for placeholders or quoted values.  This
     * method is intended for testing.
     *
     * @param  txn the transaction under which the operation should take place
     * @param  oid the object ID
     * @param  data the data
     */
    void setObjectRaw(Transaction txn, long oid, byte[] data) {
  TxnInfo txnInfo = checkTxn(txn);
  oidsDb.put(txnInfo.dbTxn, DataEncoding.encodeLong(oid), data);
    }

    /**
     * Get raw data for the specified object ID.  The value returned is the
     * literal data, without checking for placeholders or quoted values.  This
     * method is intended for testing.
     *
     * @param  txn the transaction under which the operation should take place
     * @param  oid the object ID
     * @return  the data or null if the object ID is not found
     */
    byte[] getObjectRaw(Transaction txn, long oid) {
  TxnInfo txnInfo = checkTxn(txn);
  return oidsDb.get(txnInfo.dbTxn, DataEncoding.encodeLong(oid), false);
    }

    /**
     * Gets the next object ID after the one specified, or -1 if there are no
     * more objects.  If oid is -1, then returns the first object ID.  The
     * values for the object IDs will not be checked, so this method can be
     * used to obtain object IDs for placeholders.  This method is intended for
     * testing.
     *
     * @param  txn the transaction under which the operation should take place
     * @param  oid the object ID or -1
     * @return  the next object ID or -1
     */
    long nextObjectIdRaw(Transaction txn, long oid) {
  TxnInfo txnInfo = checkTxn(txn);
  DbCursor cursor = oidsDb.openCursor(txnInfo.dbTxn);
  try {
      boolean found =  (oid < 0)
    ? cursor.findFirst()
    : cursor.findNext(DataEncoding.encodeLong(oid + 1));
      if (found) {
    return DataEncoding.decodeLong(cursor.getKey());
      } else {
    return -1;
      }
  } finally {
      cursor.close();
  }
    }
}
TOP

Related Classes of com.sun.sgs.impl.service.data.store.DataStoreImpl$ThreadTxnInfoTable$Entry

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.
a'); ga('create', 'UA-20639858-1', 'auto'); ga('send', 'pageview');