
/*!
  @header FTTransactionManagerImpl
  @abstract Module of FT
 
  @availability OS X, GNUstep
  @copyright 2004, 2005, 2006 Free Software Foundation, Inc.
  
  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library 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
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA

  <pre>
  -------------------------------------------------------------------------
  Modification history

  26.06.05 ola     initial version
  23.08.06 ola     license changed
  -------------------------------------------------------------------------
  </pre>
*/

#include <Encore/Encore.h>
#include <FT/FTTransactionManager.h>
#include <FT/FTSession.h>
#include <FT/FTSessionImpl.h>
#include <FT/FTTransactionManagerImpl.h>
#include <FT/FTTransactionImpl.h>
#include <FT/FTExceptions.h>
#include <FT/FTLogging.h>


@implementation FTTransactionManagerImpl

- init {
  self = [super init];
  
  self->globalLock = [[NSLock alloc] init];
  
  self->sessionIdToTransactionArray = [[NSMutableDictionary alloc] init];
  self->transactionOptimizers = [[NSMutableArray alloc] init];
  
  return self;
}


- (void) dealloc {
  [self->globalLock release];
  [self->sessionIdToTransactionArray dealloc];
  [self->transactionOptimizers release];
  
  [super dealloc];
}


- addTransactionOptimizer: (id <FTTransactionOptimizer>) optimizerToAdd
  withPriority: (unsigned) priority {
  
  if( 0 != priority ) {
    [[[ECIllegalArgumentException alloc] 
      initWithArgumentInfo: @"FTTransactionManagerImpl::addTransactionOptimizer"\
        ": priority must equal 0 at present" ] raise];
  }
  
  [self->transactionOptimizers addObject: optimizerToAdd];
  
  return self;
}


- (BOOL) commitTransaction: (id <FTTransaction>) transactionToCommit {
  BOOL success = YES;
  FTTransactionImpl *impl;
  NSEnumerator *steps;
  id currentStep;
  FTTransactionUndoStack *performedActions;
  NSException *stepException = nil;

  if( [[FTLogging coreLog] isDebugEnabled] ) {    
    [[FTLogging coreLog]
      debug: @"FTTransactionManagerImpl::commit: Starting commitment..." ];
  }
  
  if( ![transactionToCommit isKindOfClass: [FTTransactionImpl class]] ) {
    [[[ECIllegalArgumentException alloc] initWithArgumentInfo: 
      @"transactionToCommit is not derived from FTTransactionImpl"] raise];
  }
  
  [self->globalLock lock];
  impl = (FTTransactionImpl *) [self optimizeTransaction: transactionToCommit];
  steps = [[impl transactionSteps] objectEnumerator];
  performedActions = [[FTTransactionUndoStack alloc] init];

  while( (currentStep = [steps nextObject]) && success ) {
    if( [currentStep isKindOfClass: [FTTransactionStepAndContext class]] ) {
      id <FTTransactionStep> step;
      FTTransactionContext *context;
    
      step = [((FTTransactionStepAndContext *) currentStep) transactionStep];
      context = [((FTTransactionStepAndContext *) currentStep) 
        transactionContext];
      
      if( nil != step ) {
        NS_DURING
          success = [step performAction: context];
        NS_HANDLER
          success = NO;
          stepException = [localException retain];
          break;
        NS_ENDHANDLER
      }
      
        if( success ) {  
          [performedActions addPerformedStep: currentStep];
        }
    }
  }

  if( NO == success ) {
    // at least one step failed
    /**
     * TODO
     * At present we simply call "undoAction" of all actions
     * directly launched before.
     * TODO: Much more robust behaviour here
     */
    NS_DURING
      [performedActions undoAll];
    NS_HANDLER
      /**
       * Evil situation if an exception occurs here
       */
      [self->globalLock unlock];
      
      if( nil != stepException ) {
        [stepException release];
      }
      
      [performedActions release];
      
      [[[FTTransactionStepException alloc] 
       initWithTransactionStepException: localException] raise];
     NS_ENDHANDLER  
          
    if( nil != stepException ) {
      [stepException raise];
    }
  }
  
  [self->globalLock unlock];
  [performedActions release];
  
  return success;
}


- (id <FTTransaction>) createTransactionForSession: (id <FTSession>) session {
  FTTransactionImpl *transaction = [[[FTTransactionImpl alloc]
    initForTransactionManager: self ] autorelease];

  [self->globalLock lock];
  ECStack *transactionStack 
    = [sessionIdToTransactionArray objectForKey: [session sessionId]];
    
  if( nil == transactionStack ) {
    /** create stack: */
    transactionStack = [[ECStack alloc] init];
    [self->sessionIdToTransactionArray 
      setObject: transactionStack forKey: [session sessionId] ];
  }

  NSAssert( nil != transactionStack, @"Ups, transaction stack equals nil!" );
  
  [transactionStack pushObject: transaction];
  
  [self->globalLock unlock];  
  
  return transaction;
}


- (id <FTTransaction>) currentTransactionForSession: (id <FTSession>) session {
  ECStack *transactionStack;
  
  transactionStack = [self->sessionIdToTransactionArray objectForKey: 
    [session sessionId]];
  NSAssert( nil != transactionStack, @"Cannot find stack for storing "\
    "transactions!" );
    
  return [transactionStack topObject];
}


- (id <FTTransaction>) optimizeTransaction: (id <FTTransaction>) transaction {
  id <FTTransaction> toReturn = transaction;
  NSEnumerator *enumerator;
  id <FTTransactionOptimizer> transactionOptimizer;
  
  enumerator = [self->transactionOptimizers objectEnumerator];
  
  while( nil != (transactionOptimizer 
    = (id <FTTransactionOptimizer>) [enumerator nextObject] )) {
    toReturn = [transactionOptimizer optimizeTransaction: toReturn];
  }
  
  return toReturn;
}
@end


@implementation FTTransactionUndoStack 

- init {
  self = [super init];
  
  self->undoSteps = [[ECStack alloc] init];
  self->performedUndoSteps = [[ECStack alloc] init];
  self->currentUndoStep = nil;
  return self;
}


- (void) dealloc {
  [self->undoSteps release];
  [self->performedUndoSteps release];
  if( nil != self->currentUndoStep ) {
    [self->currentUndoStep release];
  }
  
  [super dealloc];
}


- addPerformedStep: (FTTransactionStepAndContext *) stepToAdd {
  if( nil != stepToAdd ) {
    [self->undoSteps pushObject: stepToAdd];
  }
  
  return self;
}


- undoAll {
  while( NO == [undoSteps isEmpty] ) {
    self->currentUndoStep 
      = (FTTransactionStepAndContext *) [self->undoSteps popObject];
    
    if( nil != self->currentUndoStep ) {
      [[self->currentUndoStep transactionStep]
        undoAction: [self->currentUndoStep transactionContext]];
        
      [self->performedUndoSteps pushObject: self->currentUndoStep];
      [self->currentUndoStep release];
      self->currentUndoStep = nil;
    }
  }
  
  return self;
}

@end
