NAME
   Object::Transaction - Virtual base class for transactions on files
   containing serialized hash objects

SYNOPSIS
           package Pkg;

           @ISA = qw(Object::Transaction);

           use Object::Transaction;

           $obj = sub new { ... }
           sub file($ref,$id) { ... }

           $obj = load Pkg $id;
           $obj->savelater();
           $obj->save();
           $obj->removelater();
           $obj->remove();
           $obj->commit();
           $obj->uncache();
           $obj->abandon();

           $id = sub id { ... }
           @passby = sub presave($old) { ... }
           sub postsave($old,@passby) { ... }
           $newid = sub preload($id) { .... }
           sub postload() { ... }
           sub preremove() { ... }
           sub postremove() { ... }

DESCRIPTION
   Object::Transaction provides transaction support for hash-based objects
   that are stored one-per-file using Storable. Multiuser access is supported.
   In the future, serializing methods other than Storable will be supported.

   Object::Transaction is a virtual base class. In order to use it, you must
   inherit from it and override the `new' method and the `file' method.

   Optomistic locking is used: it is possible that a transaction will fail
   because the data that is is based upon has changed out from under it.

EXAMPLE
           package User;

           @ISA = qw(Object::Transaction);

           use Object::Transaction;

           my $top = "/some/path";

           sub new {
                   my ($package, $login) = @_;
                   die unless getpwnam($login);
                   return bless { 'UID' => getpwnam($login) };
           }

           sub file {
                   my ($ref, $id) = @_;
                   $id = $ref->id() unless $id;
                   return "$top/users/$id/data.storable";
           }

           sub id {
                   my ($this) = @_;
                   return $this->{'UID'};
           }

           sub preload
           {
                   my ($id) = @_;
                   return if getpwuid($id);
                   return getpwnam($id) if getpwnam($id);
                   die;
           }

           sub postload
           {
                   my ($this) = @_;
                   my ($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,
                           $shell,$expire) = getpwuid($this->{'UID'});
                   $this->{'SHELL'} = $shell;
           }

           sub presave
           {
                   my ($this, $old) = @_;
                   my $id = $this->{'UID'};
                   mkdir("$top/users/$id", 0700);
                   delete $this->{'SHELL'};
           }

           sub postsave
           {
                   goto &postload;
           }

           sub postremove
           {
                   delete from pw file...
           }

           my $joe = new User "joe";
           $joe->savelater();

           my $fred = new User "fred";
           $fred->savelater();

           $joe->commit();

METHODS PROVIDED
   Object::Transaction provides the following methods.

   `load($id)'    `load' is the way to bring an object into memory. It is usually
                  invoked as `my $obj = load MyObject $id'.

                  There are two opporunities to customize the behavior of
                  `load': `preload' for things that should happen before
                  loading and `postload' for things that should happen after
                  loading.

                  Object::Transaction caches objects that are loaded. This is
                  done both for performance reasons and to make sure that only
                  one copy of an object is in memory at a time. If caching is
                  not desired, the `uncache' method must be invoked after
                  loading.


   `savelater()'  `savelater' is the usual method of saving an object. The object
                  is not saved at the time that `savelater' is invoked. It is
                  actually saved when `commit' is invoked.

                  There are two opporunities to customize the behavior of
                  `savelater': `presave' for things that should happen before
                  saving and `postsave' for things that should happen after
                  saving. These are invoked when the object is actually being
                  saved.

   `save()'       Simply `savelater' combined with a `commit'.


   `removelater()'
                  `removelater' is the usual method of removing an object. The
                  object is not removed at the time that `removelater' is
                  invoked. It is actually removed when `commit' is invoked.

                  There are two opporunities to customize the behavior of
                  `removelater': `preremove' for things that should happen
                  before removing and `postremove' for things that should
                  happen after removing. These are invoked when the object is
                  actually being removed.

   `remove()'     Simply `removelater' combined with a `commit'


   `commit()'     `commit' writes all pending changes to disk. Either all changes
                  will be saved or none of them will. Deadlocks are avoided by
                  locking files in order.

                  Object::Transaction uses opportunistic locking. Commit can
                  fail. If it fails, it will `die' with a message that begins
                  `DATACHANGE: file'. It is advisable to wrap your entire
                  transaction inside an eval so that it can be re-tried in the
                  event that the data on disk changed between the time is was
                  loaded and commited.

                  In the event of a commit failure, the object cache will be
                  reset. Do not keep any old references to objects after such
                  a failure.

   `transaction($funcref,@args)'
                  `transaction()' is a wrapper for a complete transaction.
                  Transactions that fail due to opportunistic locking problems
                  will be re-run automatically. Beware side-effects!

                  The first parameter is a reference to a function. Any
                  additional parameters will be passed as parameters to that
                  function. The return value of `transaction()' is the return
                  value of `&$funcref()'.

                  It is not necessary to use the `transaction()' method. Just
                  beware that `commit()', `save()', and `remove()' can fail.
                  `transaction()' will keep trying until it suceeds or it
                  failes for a reason other than an opportunistic locking
                  problem.


   `abandon()'    As an alternative to `commit', all changes may be abandoned.
                  Calling `abandon()' does not undo changes made to the in-
                  memory copies of objects.


   `uncache()'    Object::Transaction caches all objects. To flush an object from
                  Object::Transaction's cache, invoke the `uncache' method on
                  the object.

                  Be careful when doing this -- it makes it possible to have
                  more than one copy of the same object in memory.

                  `uncache()' can be invoked as a class method rather than an
                  object method (`Object::Transaction-'uncache()>). When
                  invoked as a class method, the entire cache is flushed.

   `readlock()'   By default Object::Transaction does not lock objects unless they
                  are being modified.

                  The `readlock()' method insures that objects are properly
                  locked and unchanged during a transaction even if they are
                  not being modified. `savelater()' takes precedence over
                  `readlock()' so they can be combined freely.

                  Paranoid programmers should use `readlock()' on most
                  objects.

REQUIRED METHODS TO OVERRIDE
   The following methods must be overriden.

   `new'          Object::Transaction does not provide a contructor. You must
                  provide one yourself.

   `file($ref,$id)'
                  You must provide a function that returns the filename that
                  an object is stored in. The `file' method can be invoked in
                  two ways: as an object method call without an `$id'
                  parameter; or as a class method call with an `$id'
                  parameter.

OPTIONAL METHODS TO OVERRIDE
   The following methods may be overridden.


   `preload($id)' `preload()' is invoked as nearly the first step of `load'. It is
                  generally used to make sure that the `$id' is valid.
                  `preload()' is a class method rather than an object method.

                  The return value of `preload' is a replacement `$id'. For
                  example, it might be called as `preload("Joe")' to load the
                  user named Joe, but if users are numbered rather than named
                  it could return the number for Joe. A return value of undef
                  is ignored.

                  No lock on the underlying file is present at the time
                  `preload' is called.


   `postload($id)'
                  `postload' is invoked after the object has been loaded into
                  memory but before transaction completeness is checked.

                  The underlying file is locked at the time that `postload' is
                  invoked.

                  If a transaction rollback is required, `postload' will be
                  invoked again after the object has been reverted to its pre-
                  transaction state.


   `presave($old)'
                  `presave()' is invoked just before an object is written to
                  disk.

                  Objects are stored on disk in the file specified by the
                  `file' method. The directory in which that file resides must
                  exist by the time `presave()' finishes. `presave' should
                  make the directory if it isn't already made.

                  The underlying file may or may not be locked at the time
                  `presave' is invoked.

                  `presave' can be invoked as a side-effect of `load' if the
                  object must roll back to a previous version.

                  The parameter `$old' is a copy of the object as of the time
                  it was first loaded into memory.

                  Any return values from `presave' will be remembered and
                  passed to `postsave'.


   `postsave($old,@psv)'
                  `postsave' is invoked after an object has been written to
                  disk.

                  The underlying file is always locked at the time `postsave'
                  is invoked.

                  Invocations of `presave' and `postsave' are always paired.

                  The parameter `$old' is a copy of the object as of the time
                  it was first loaded into memory.

                  The parameter `@psv' is the return value from `presave'.


   `id()'         Object::Transaction expect to be able to find the unique
                  identifier (id) for each object as `$obj-'{'ID'}>. If that
                  isn't the case, you can override the `id' function to
                  provide an alternative.

PUBLIC MEMBER DATA
   The following data members are used by Object::Transaction.

   `ID'           Object::Transaction expect to find the id for an object in
                  `$obj-'{'ID'}>. This can be overridden by defining your own
                  `id' function.

   `OLD'          When an object is loaded into memory a copy is made. The copy
                  can be found at `$obj-'{'OLD'}>. The copy should not be
                  modified. The copy is explicitly passed in `presave' and
                  `postsave'.

PRIVATE MEMBER DATA
   Object::Transaction ads a few data members to each object for its own
   internal use.

   These are:

           __frozen
           __transfollowers
           __transleader
           __rollback
           __removenow
           __toremove
           __transdata
           __readonly

   None of these should be touched.

BUGS
   A program or computer crash at just the wrong moment can allow an object
   that should be deleted to escape deletion. Any future attempt to access
   such an object will cause it to self-destruct.

   In some situations objects will be saved even if niether `save()' nor
   `savelater()' is invoked. This happens if `readlock()' is used and the
   transaction leader object (one per transaction) choosen turns out to be an
   object for which only `readlock()' was called.

AUTHOR
   David Muir Sharnoff <[email protected]>

COPYRIGHT
   Copyright (C) 1999, Internet Journals Corporation <www.bepress.com>. All
   rights reserved. License hearby granted for anyone to use this module at
   their own risk. Please feed useful changes back to David Muir Sharnoff
   <[email protected]>.