# $Id: transaction.rb 257 2003-10-28 16:29:04Z bolzer $
# Author:: Oliver M. Bolzer (mailto:oliver@fakeroot.net)
# Copyright:: (c) Oliver M. Bolzer, 2002
# Licence:: Ruby licence.

require 'vapor/exceptions'
require 'vapor/utils'

module Vapor

  # The Transaction object controls initiation and completion or abortion
  # of transactons. Will raise a StaleTransactionError on all methods except
  # begin(), if no transaction is currently active.
  class Transaction
    include Vapor::Exceptions

    # Create a Transaction object associated with a specific
    # PersistenceManager instance and Datastore. 
    def initialize( pmgr, datastore ) # :nodoc:
      @persistence_manager = pmgr
      @datastore = datastore
      @active = false
      @block_mode = false
      @log = TransactionLog.new 
      @committer = ""
    end # initialize()

    # Returns PersistenceManager instance associated with this Transaction
    attr_reader :persistence_manager

    # Returns <em>true</em> if a transaction if currently active.
    def active?
      @active
    end # active?()

    # Start a transaction. Raises a NestedTransactionError if an unfinished
    # transaction is currently active.
    def begin
      unless @active 
        @datastore.begin_transaction
        unless @persistence_manager.autocommit_in_progress?
          @persistence_manager.autocommit = false
        end
        @log = TransactionLog.new
        @log.committer = @committer
        @active = true
      else
        raise NestedTransactionError
      end
    end # begin()

    # Commit the currently active transaction to the Repository. Raises a
    # StaleTransactionError if there is no active transaction or
    # a CommitError (or one of it's childclasses) if an error occurs.
    def commit
      raise StaleTransactionError unless  @active

      begin
        @log.update_commit_time
        @log.make_persistent
        @log.cleanup_modofied_objects()
        @datastore.transaction_log = @log.oid
        @persistence_manager.flush_all
        @datastore.commit_transaction
        @datastore.transaction_log = 0
      rescue Exception => e
        self.rollback
        raise e
      end

      @active = false
    end # commit()

    # Abort the currently active transaction and restore all values of
    # persistent objects to the values in the datastore. Persistent
    # objects in the NEW state will become TRANSIENT again, losing their
    # persistent identity. Raises an TransactionAbortedError when the
    # transaction is occuring inside a block.
    def rollback
      @datastore.rollback_transaction
      @persistence_manager.rollback_all
      @active = false

      if @block_mode then
        raise TransactionAbortedError
      end

    end # rollback()
    alias :abort :rollback
    
    # Begin a transaction and #commit it when the block terminates. If
    # any exception is raised by the block, the transaction is automatically
    # rolledback by calling #abort and the exception re-raised. Raises a
    # NestedTransactionError if the ransaction is already active.
    def do
      raise ArgumentError, "block required" unless block_given?

      self.begin

      begin
        @block_mode = true
        yield self
      rescue TransactionAbortedError  # aborted by user
      rescue                          # all other errors
        @block_mode = false
        self.abort
        raise
      ensure
        @block_mode = false
        self.commit if self.active?
      end

    end # do()

    # TransactionLog holding information about the current transaction.
    def log_entry
      @log
    end # log_entry()

    # Set committer for all transactions committed hereafter.
    def committer=( committer )
      @committer = committer
      if  @active and !@log.nil? then
        @log.committer = committer
      end
    end # committer=()

    # Set log message for currently active transaction. Will be
    # cleared when a new transaction is started. Raises a
    # StaleTransactionError unless a transaction is currenlty
    # active.
    def message=( message )
      raise StaleTransactionError unless  @active
      @log.message = message
    end # message=() 
    
  end # class Transaction
end # module Vapor
