using System;
using System.Collections.Generic;
using System.IO;
using Pegasus.Diagnostics;
using Pegasus.Log4Net;
using Pegasus.Runtime.Serialization.Formatters.Xml;
namespace Pegasus.Workflow.Service.FileServices
{
/// <summary>
/// Uses the file system to persist the workflow context objects
/// </summary>
public class FilePersistenceService : WorkflowPersistenceService, IDisposable
{
// Local Instance Values
private ILog m_log = LogManager.GetLogger( typeof( FilePersistenceService ) );
private object m_lock = new object();
private string m_directory;
private Dictionary<int, WorkflowContext> m_contextTable = new Dictionary<int, WorkflowContext>();
// Local Const Values
private const string ContextFileLocked = "_FilePersistenceService.FileLocked";
/// <summary>
/// Initializes a new instance of the <see cref="T:FilePersistenceService"/> class.
/// </summary>
/// <param name="directory">The directory.</param>
public FilePersistenceService( string directory )
{
m_log.DebugFormat( "FilePersistenceService( directory = {0} )", directory );
// Check Parameters
ParamCode.AssertNotEmpty( directory, "directory" );
ParamCode.Assert( Directory.Exists( directory ), "directory", "The directory {0} does not exist or is inaccessable", directory );
m_directory = directory;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
m_log.Debug( "FilePersistenceService:Dispose()" );
m_contextTable.Clear();
}
/// <summary>
/// Starts this instance of the service.
/// </summary>
/// <param name="workflowService"></param>
public override void Start( WorkflowService workflowService )
{
m_log.DebugFormat( "FilePersistenceService:Start( workflowService = {0} )", workflowService );
lock( m_lock )
{
base.Start( workflowService );
m_contextTable.Clear();
}
}
/// <summary>
/// Stops this instance of the service.
/// </summary>
public override void Stop()
{
m_log.DebugFormat( "FilePersistenceService:Stop()" );
lock( m_lock )
{
base.Stop();
Dispose();
}
}
/// <summary>
/// Registers a new workflow context with the persistence service.
/// </summary>
/// <param name="context">The context.</param>
/// <returns></returns>
protected override int OnRegisterNewWorkflowContext( WorkflowContext context )
{
m_log.DebugFormat( "FilePersistenceService:OnRegisterNewWorkflowContext( context = {0} )", context );
// Check Parameters
ParamCode.AssertNotNull( context, "context" );
lock( m_lock )
{
// Find a new workflowId/filename for the workflow
int workflowId = GetNextWorkflowId();
// Open/Create the file so it exist on the disk
using( FileStream fileStream = OpenWorkflowFile( workflowId, FileMode.Create ) )
{
context[ ContextFileLocked ] = true;
}
// Set the new workflow context
InitializeNewContext( context, workflowId );
m_contextTable[ workflowId ] = context;
return workflowId;
}
}
/// <summary>
/// Saves the workflow context.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="unlock">if set to <c>true</c> [unlock].</param>
protected override void OnSaveWorkflowContext( WorkflowContext context, bool unlock )
{
m_log.DebugFormat( "FilePersistenceService:OnSaveWorkflowContext( context = {0}, unlock = {1} )", context, unlock );
// Check Parameters
ParamCode.AssertNotNull( context, "context" );
lock( m_lock )
{
// Is the context locked so that we can write the file out
bool fileLocked = false;
if( context.TryGetValue<bool>( ContextFileLocked, out fileLocked ) )
{
if( !fileLocked )
{
throw new WorkflowLockedException( context.WorkflowId, "Can not save the workflow because the context is not locked." );
}
if( context.IsReadOnly )
{
throw new WorkflowLockedException( context.WorkflowId, "Can not save the workflow because the context is read-only." );
}
// Open the file and write out the data
try
{
using( FileStream fileStream = OpenWorkflowFile( context.WorkflowId, FileMode.Create ) )
{
// Write out the context object
new XmlFormatter2().Serialize( fileStream, context );
}
}
catch( Exception e )
{
m_log.Error( "FilePersistenceService:OnSaveWorkflowContext: Exception", e );
throw;
}
finally
{
if( unlock )
{
OnReleaseWorkflowContext( context );
}
}
}
else
{
throw new WorkflowLockedException( context.WorkflowId, "Can not save the workflow because the context is not locked (missing lock value)." );
}
}
}
/// <summary>
/// Loads the workflow context.
/// </summary>
/// <param name="workflowId">The workflow id.</param>
/// <param name="readOnly">if set to <c>true</c> [read only].</param>
/// <returns></returns>
protected override WorkflowContext OnLoadWorkflowContext( int workflowId, bool readOnly )
{
m_log.DebugFormat( "FilePersistenceService:OnLoadWorkflowContext( workflowId = {0}, readOnly = {1} )", workflowId, readOnly );
// Check Parameters
ParamCode.AssertRange( workflowId, 1, int.MaxValue, "workflowId" );
lock( m_lock )
{
if( readOnly )
{
return LoadReadOnlyWorkflowContext( workflowId );
}
return LockAndLoadWorkflowContext( workflowId );
}
}
/// <summary>
/// Releases the workflow context.
/// </summary>
/// <param name="context">The context.</param>
protected override void OnReleaseWorkflowContext( WorkflowContext context )
{
m_log.DebugFormat( "FilePersistenceService:OnReleaseWorkflowContext( context = {0} )", context );
// Check Parameter
ParamCode.AssertNotNull( context, "context" );
lock( m_lock )
{
// Set the lock value to false
context[ ContextFileLocked ] = false;
// Release the context and remove it from our table.
SetContextAsReadOnly( context );
m_contextTable.Remove( context.WorkflowId );
}
}
/// <summary>
/// Locks the and load workflow context.
/// </summary>
/// <param name="workflowId">The workflow id.</param>
/// <returns></returns>
private WorkflowContext LockAndLoadWorkflowContext( int workflowId )
{
m_log.DebugFormat( "FilePersistenceService:LockAndLoadWorkflowContext( workflowId = {0} )", workflowId );
// Check Parameters
ParamCode.AssertRange( workflowId, 1, int.MaxValue, "workflowId" );
// If the file already loaded return it
if( m_contextTable.ContainsKey( workflowId ) )
{
return m_contextTable[ workflowId ];
}
WorkflowContext context = null;
using( FileStream fileStream = OpenWorkflowFile( workflowId, FileMode.Open ) )
{
context = ReadInWorkflowContext( fileStream );
context[ ContextFileLocked ] = true;
}
InitializeExistingContext( workflowId, context, false );
m_contextTable[ workflowId ] = context;
return context;
}
/// <summary>
/// Loads the read only workflow context.
/// </summary>
/// <param name="workflowId">The workflow id.</param>
/// <returns></returns>
private WorkflowContext LoadReadOnlyWorkflowContext( int workflowId )
{
m_log.DebugFormat( "FilePersistenceService:LoadReadOnlyWorkflowContext( workflowId = {0} )", workflowId );
// Check Parameters
ParamCode.AssertRange( workflowId, 1, int.MaxValue, "workflowId" );
using( FileStream fileStream = OpenWorkflowFile( workflowId, FileMode.Open ) )
{
WorkflowContext context = ReadInWorkflowContext( fileStream );
InitializeExistingContext( workflowId, context, true );
return context;
}
}
/// <summary>
/// Opens the filename.
/// </summary>
/// <param name="workflowId">The workflow id.</param>
/// <param name="mode">The open mode for the file.</param>
/// <returns></returns>
private FileStream OpenWorkflowFile( int workflowId, FileMode mode )
{
m_log.DebugFormat( "FilePersistenceService:OpenWorkflowFile( workflowId = {0}, mode = {1} )", workflowId, mode );
// Check Parameters
ParamCode.AssertRange( workflowId, 1, int.MaxValue, "workflowId" );
FileStream fileStream = null;
try
{
// Open the file
fileStream = File.Open( GetFilenameFromWorkflowId( workflowId ), mode, FileAccess.ReadWrite );
}
catch( FileNotFoundException e )
{
m_log.Error( "FilePersistenceService:OpenWorkflowFile: File not found exception.", e );
throw new WorkflowNotFoundException( workflowId, e );
}
catch( Exception e )
{
m_log.Error( "FilePersistenceService:OpenWorkflowFile: Exception.", e );
throw new WorkflowException( e, "Unable to open workflow {0}", workflowId );
}
return fileStream;
}
/// <summary>
/// Reads the in workflow context.
/// </summary>
/// <param name="fileStream">The file stream.</param>
/// <returns></returns>
private WorkflowContext ReadInWorkflowContext( FileStream fileStream )
{
m_log.DebugFormat( "FilePersistenceService:ReadInWorkflowContext( fileStream = {0} )", fileStream );
try
{
// Read in the context object.
return (WorkflowContext) new XmlFormatter2().Deserialize( fileStream );
}
catch( Exception e )
{
m_log.Error( "FilePersistenceService:ReadInWorkflowContext: Exception.", e );
throw new WorkflowException( e, "Unable to deserialize the workflow context" );
}
}
/// <summary>
/// Gets the filename from workflow id.
/// </summary>
/// <param name="workflowId">The workflow id.</param>
/// <returns></returns>
private string GetFilenameFromWorkflowId( int workflowId )
{
m_log.DebugFormat( "FilePersistenceService:GetFilenameFromWorkflowId( workflowId = {0} )", workflowId );
// Check Parameters
ParamCode.AssertRange( workflowId, 1, int.MaxValue, "workflowId" );
return Path.Combine( m_directory, string.Format( "Workflow_{0}.xml", workflowId ) );
}
/// <summary>
/// Gets the workflow next workflow id.
/// </summary>
/// <returns></returns>
private int GetNextWorkflowId()
{
m_log.Debug( "FilePersistenceService:GetNextWorkflowId()" );
int workflowId = 1;
string infoFilename = Path.Combine( m_directory, "FilePersistenceServiceInfo.xml" );
// If the files does not exist then create the default
if( File.Exists( infoFilename ) )
{
using( FileStream input = File.Open( infoFilename, FileMode.Open, FileAccess.Read, FileShare.None ) )
{
workflowId = (int) new XmlFormatter2().Deserialize( input );
}
workflowId++;
}
// Find a new workflowId/filename for the workflow
string filename = GetFilenameFromWorkflowId( workflowId );
while( File.Exists( filename ) )
{
workflowId++;
filename = GetFilenameFromWorkflowId( workflowId );
}
// Update/Create the file.
using( FileStream output = File.Open( infoFilename, FileMode.Create, FileAccess.Write, FileShare.None ) )
{
new XmlFormatter2().Serialize( output, workflowId );
}
return workflowId;
}
}
}