Transactioned.java

/*
 * $Source$
 * $Revision$
 *
 * Copyright (C) 2000 William Chesters
 *
 * Part of Melati (http://melati.org), a framework for the rapid
 * development of clean, maintainable web applications.
 *
 * Melati is free software; Permission is granted to copy, distribute
 * and/or modify this software under the terms either:
 *
 * a) the GNU General Public License as published by the Free Software
 *    Foundation; either version 2 of the License, or (at your option)
 *    any later version,
 *
 *    or
 *
 * b) any version of the Melati Software License, as published
 *    at http://melati.org
 *
 * You should have received a copy of the GNU General Public License and
 * the Melati Software License along with this program;
 * if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
 * GNU General Public License and visit http://melati.org to obtain the
 * Melati Software License.
 *
 * Feel free to contact the Developers of Melati (http://melati.org),
 * if you would like to work out a different arrangement than the options
 * outlined here.  It is our intention to allow Melati to be used by as
 * wide an audience as possible.
 *
 * This program 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.
 *
 * Contact details for copyright holder:
 *
 *     William Chesters <williamc At paneris.org>
 *     http://paneris.org/~williamc
 *     Obrechtstraat 114, 2517VX Den Haag, The Netherlands
 */

package org.melati.poem.transaction;

import org.melati.poem.PoemBugPoemException;

/**
 * An object which can have uncommitted state within a {@link Transaction}.
 */
public abstract class Transactioned {

  protected boolean valid = true;

  /** The transactions which have read us */
  private int seenMask = 0;

  /** The transaction which is writing to us */
  private Transaction touchedBy = null;
  private TransactionPool transactionPool = null;

  /**
   * Constructor.
   * @param transactionPool the TransactionPool
   */
  public Transactioned(TransactionPool transactionPool) {
    this.transactionPool = transactionPool;
  }

  /**
   * Constructor.
   */
  public Transactioned() {
    this(null);
  }

  /**
   * Load the transactioned object from its backing store.
   */
  protected abstract void load(Transaction transaction);

  /**
   * Whether this instance is up-to-date.
   * <p>
   * This is a hook to enable subtypes to define under what circumstances
   * an instance needs to be reloaded when it is marked as
   * invalid, however the two known subtypes just return 
   * the inherited valid flag. 
   */
  protected abstract boolean upToDate(Transaction transaction);

  protected abstract void writeDown(Transaction transaction);

  protected synchronized void reset() {
    valid = true;
    seenMask = 0;
    touchedBy = null;
  }

  protected final TransactionPool transactionPool() {
    return transactionPool;
  }

  protected synchronized void setTransactionPool(
      TransactionPool transactionPool) {
    if (transactionPool == null)
      throw new NullPointerException();
    if (this.transactionPool != null && 
        this.transactionPool != transactionPool)
      throw new IllegalArgumentException();

    this.transactionPool = transactionPool;
  }

  /**
   * We don't synchronize this; under the one-thread-per-transaction 
   * parity it can't happen, and at worst it means loading twice sometimes.
   * @param transaction the transaction to check
   */
  private void ensureValid(Transaction transaction) {
    if (!valid) {
      if (transaction == null)
        transaction = touchedBy;

      // NOTE This could be simplified to if(!valid) 
      // but that would remove a useful extension hook.
      if (!upToDate(transaction))
        load(transaction);

      valid = true;
    }
  }

  protected void readLock(Transaction transaction) {

    if (transaction != null) {
      // Block on writers until there aren't any

      for (;;) {
        Transaction blocker;
        synchronized (this) {
          if (touchedBy != null && touchedBy != transaction)
            blocker = touchedBy;
          else {
            if ((seenMask & transaction.mask) == 0) {
              seenMask |= transaction.mask;
              transaction.notifySeen(this);
            }
            break;
          }
        }

        blocker.block(transaction);
      }
    }

    ensureValid(transaction);
  }

  /**
   * Get a write lock on the given object if we do not already
   * have one.
   * <p>
   * This will block until no other transactions have
   * write locks on the object before claiming the next write
   * lock. Then it will block until none have read locks.
   * <p>
   * Finally it calls {@link #ensureValid(Transaction)}.
   */
  protected void writeLock(Transaction transaction) {

    if (transaction == null)
      throw new WriteCommittedException(this);

    // Block on other writers and readers until there aren't any

    for (;;) {
      Transaction blocker = null;
      synchronized (this) {
        if (touchedBy == transaction)
          // There's a writer, but it's us
          break;

        else if (touchedBy != null)
          // There's a writer, and it's not us
          blocker = touchedBy;

        else {
          int othersSeenMask = seenMask & transaction.negMask;
          if (othersSeenMask == 0) {
            // There are no readers besides us

            touchedBy = transaction;
            transaction.notifyTouched(this);
            break;
          }
          else {
            // There are other readers

            // We block not on the chronologically first reader but on the one
            // with the lowest index, i.e. essentially on an arbitrary
            // one---not perfect, but doing it any other way would be
            // expensive.

            int m = transactionPool().transactionsMax();
            int t, mask;
            for (t = 0, mask = 1;
                 t < m && (othersSeenMask & mask) == 0;
                 ++t, mask <<= 1)
              ;

            if (t == m)
              throw new PoemBugPoemException(
                  "Thought there was a blocking transaction, " +
                  "but didn't find it");

            blocker = transactionPool().transaction(t);
          }
        }
      }

      blocker.block(transaction);
    }

    ensureValid(transaction);
  }

  protected synchronized void commit(Transaction transaction) {
    if (touchedBy != transaction)
      throw new CrossTransactionCommitException(this);
    touchedBy = null;
  }

  protected synchronized void rollback(Transaction transaction) {
    if (touchedBy != transaction)
      throw new CrossTransactionCommitException(this);
    touchedBy = null;
    valid = false;
  }

  /**
   * Mark as invalid.
   */
  public synchronized void invalidate() {
    valid = false;
  }

  /**
   * Mark as valid.
   */
  public synchronized void markValid() {
    valid = true;
  }

  protected synchronized void unSee(Transaction transaction) {
    seenMask &= transaction.negMask;
  }
}