#region License
/**
* Ingenious MVC : An MVC framework for .NET 2.0
* Copyright (C) 2006, JDP Group
*
* 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
*
* Authors: - Kent Boogaart (kentcb@internode.on.net)
*/
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Threading;
using Ingenious.Mvc.Configuration;
using Ingenious.Mvc.Factories;
using Ingenious.Mvc.Util;
namespace Ingenious.Mvc{
/// <include file='TaskManager.doc.xml' path='/doc/member[@name="T:TaskManager"]/*'/>
[Serializable]
public sealed class TaskManager
{
private ConfigurationInfo _configuration;
private Task _activeTask;
private IDictionary<Id<Task>, TaskInfo> _taskCache;
private IDictionary<Id<IViewManager>, IViewManager> _viewManagerCache;
[NonSerialized]
private ReaderWriterLock _taskCacheLock;
[NonSerialized]
private ReaderWriterLock _viewManagerCacheLock;
private static readonly TaskManager _instance = new TaskManager();
private static readonly Log _log = Log.CreateForType(typeof(TaskManager));
/// <include file='TaskManager.doc.xml' path='/doc/member[@name="P:ConfigurationInfo"]/*'/>
public ConfigurationInfo ConfigurationInfo
{
get
{
return _configuration;
}
set
{
_configuration = value;
}
}
/// <include file='TaskManager.doc.xml' path='/doc/member[@name="P:Tasks"]/*'/>
public IList<Task> Tasks
{
get
{
IList<Task> retVal = new List<Task>();
_taskCacheLock.AcquireReaderLock(Timeout.Infinite);
try
{
foreach (TaskInfo taskInfo in _taskCache.Values)
{
retVal.Add(taskInfo.Task);
}
}
finally
{
_taskCacheLock.ReleaseReaderLock();
}
return retVal;
}
}
/// <include file='TaskManager.doc.xml' path='/doc/member[@name="P:ActiveTask"]/*'/>
public Task ActiveTask
{
get
{
return _activeTask;
}
}
/// <include file='TaskManager.doc.xml' path='/doc/member[@name="P:Instance"]/*'/>
public static TaskManager Instance
{
get
{
return _instance;
}
}
/// <include file='TaskManager.doc.xml' path='/doc/member[@name="E:TaskStarting"]/*'/>
[field: NonSerialized]
public event EventHandler<TaskEventArgs> TaskStarting;
/// <include file='TaskManager.doc.xml' path='/doc/member[@name="E:TaskStarted"]/*'/>
[field: NonSerialized]
public event EventHandler<TaskEventArgs> TaskStarted;
/// <include file='TaskManager.doc.xml' path='/doc/member[@name="E:TaskEnding"]/*'/>
[field: NonSerialized]
public event EventHandler<TaskEndingEventArgs> TaskEnding;
/// <include file='TaskManager.doc.xml' path='/doc/member[@name="E:TaskEnded"]/*'/>
[field: NonSerialized]
public event EventHandler<TaskEventArgs> TaskEnded;
/// <include file='TaskManager.doc.xml' path='/doc/member[@name="M:.ctor()"]/*'/>
public TaskManager()
{
_viewManagerCache = new Dictionary<Id<IViewManager>, IViewManager>();
_viewManagerCacheLock = new ReaderWriterLock();
_taskCache = new Dictionary<Id<Task>, TaskInfo>();
_taskCacheLock = new ReaderWriterLock();
}
/// <include file='TaskManager.doc.xml' path='/doc/member[@name="M:StartTask(Ingenious.Mvc.Id`1)"]/*'/>
public Task StartTask(Id<INavigator> navigatorId)
{
return StartTask(navigatorId, null, null);
}
/// <include file='TaskManager.doc.xml' path='/doc/member[@name="M:StartTask(Ingenious.Mvc.Id`1,System.Object)"]/*'/>
public Task StartTask(Id<INavigator> navigatorId, object data)
{
return StartTask(navigatorId, data, null);
}
/// <include file='TaskManager.doc.xml' path='/doc/member[@name="M:StartTask(Ingenious.Mvc.Id`1,System.Object,System.Object)"]/*'/>
public Task StartTask(Id<INavigator> navigatorId, object data, object viewData)
{
ArgumentHelper.AssertNotEmpty(navigatorId, "navigatorId");
_log.Information("Starting task for navigator with ID '{0}'.", navigatorId);
ConfigurationInfo configurationInfo = ConfigurationInfo;
//make sure we have some configuration to work with
ExceptionHelper.ThrowIf(configurationInfo == null, "StartTask.noConfiguration");
//try and get an Navigator instance from the configuration
Debug.Assert(configurationInfo.NavigatorInfos != null);
NavigatorInfo navigatorInfo = configurationInfo.NavigatorInfos[navigatorId];
//make sure we have the navigator info and the view manager info
ExceptionHelper.ThrowIf(navigatorInfo == null, "StartTask.navigatorNotFound", navigatorId);
ExceptionHelper.ThrowIf(navigatorInfo.ViewManagerInfo == null, "StartTask.viewManagerNotSpecified", navigatorId);
Debug.Assert(configurationInfo.ViewManagerFactory != null);
//get the view manager from the cache
IViewManager viewManager = GetViewManager(navigatorInfo.ViewManagerInfo, configurationInfo, navigatorInfo.Id);
//try and get an INavigator instance from the configured navigator factory
Debug.Assert(configurationInfo.NavigatorFactory != null);
//create the task details
Task task = _configuration.TaskFactory.Create(ActiveTask, GetNewTaskId(), this, data);
//create the INavigator instance
INavigator navigator = configurationInfo.NavigatorFactory.Create(navigatorInfo, task, viewManager);
//make sure the navigator was created and it specifies a starting view
ExceptionHelper.ThrowIf(navigator == null, "StartTask.navigatorFactoryFailed", navigatorId);
ExceptionHelper.ThrowIf(navigator.StartingViewId.IsEmpty, "StartTask.startingViewNotSpecified", navigatorId);
AddTaskToCache(task, navigator, viewManager);
_log.Verbose("Navigating to starting view with ID '{0}'.", navigator.StartingViewId);
//navigate to the first view in the navigator
navigator.NavigateTo(navigator.StartingViewId, viewData);
//attach to the task to bubble up events
AttachToTask(task);
//ask the task to start
task.Start();
return task;
}
/// <include file='TaskManager.doc.xml' path='/doc/member[@name="M:GetViewManager(Ingenious.Mvc.Id`1)"]/*'/>
public IViewManager GetViewManager(Id<IViewManager> viewManagerId)
{
ArgumentHelper.AssertNotEmpty(viewManagerId, "viewManagerId");
_viewManagerCacheLock.AcquireReaderLock(Timeout.Infinite);
try
{
if (_viewManagerCache.ContainsKey(viewManagerId))
{
return _viewManagerCache[viewManagerId];
}
}
finally
{
_viewManagerCacheLock.ReleaseReaderLock();
}
return null;
}
/// <include file='TaskManager.doc.xml' path='/doc/member[@name="M:GetViewManagerForTask(Ingenious.Mvc.Task)"]/*'/>
public IViewManager GetViewManagerForTask(Task task)
{
ArgumentHelper.AssertNotNull(task, "task");
_taskCacheLock.AcquireReaderLock(Timeout.Infinite);
try
{
ExceptionHelper.ThrowIf(!_taskCache.ContainsKey(task.Id), "GetViewManagerForTask.taskNotFound", task.Id);
return _taskCache[task.Id].ViewManager;
}
finally
{
_taskCacheLock.ReleaseReaderLock();
}
}
/// <include file='TaskManager.doc.xml' path='/doc/member[@name="M:GetControllerForTask(Ingenious.Mvc.Task,Ingenious.Mvc.Configuration.ControllerInfo,Ingenious.Mvc.IControllerFactory)"]/*'/>
public IController GetControllerForTask(Task task, ControllerInfo controllerInfo, IControllerFactory controllerFactory)
{
ArgumentHelper.AssertNotNull(task, "task");
ArgumentHelper.AssertNotNull(controllerFactory, "controllerFactory");
_taskCacheLock.AcquireReaderLock(Timeout.Infinite);
try
{
ExceptionHelper.ThrowIf(!_taskCache.ContainsKey(task.Id), "GetControllerForTask.taskNotFound", task.Id);
TaskInfo taskInfo = _taskCache[task.Id];
//found the task information - now look for controller with required ID
IController retVal = null;
if (controllerInfo != null)
{
taskInfo.Controllers.TryGetValue(controllerInfo.Id, out retVal);
}
if ((retVal == null) && (controllerInfo != null))
{
//need to write to cache (probably) so get writer lock
LockCookie lockCookie = _taskCacheLock.UpgradeToWriterLock(Timeout.Infinite);
try
{
//other threads may have written to the cache before we obtained the writer lock so re-check the cache
taskInfo.Controllers.TryGetValue(controllerInfo.Id, out retVal);
if (retVal == null)
{
retVal = controllerFactory.Create(controllerInfo, task, taskInfo.Navigator);
//add the created controller to the cache
taskInfo.Controllers[controllerInfo.Id] = retVal;
}
}
finally
{
//finished writing so release
_taskCacheLock.DowngradeFromWriterLock(ref lockCookie);
}
}
return retVal;
}
finally
{
_taskCacheLock.ReleaseReaderLock();
}
}
/// <include file='TaskManager.doc.xml' path='/doc/member[@name="M:GetNavigatorForTask(Ingenious.Mvc.Task)"]/*'/>
public INavigator GetNavigatorForTask(Task task)
{
ArgumentHelper.AssertNotNull(task, "task");
_taskCacheLock.AcquireReaderLock(Timeout.Infinite);
try
{
ExceptionHelper.ThrowIf(!_taskCache.ContainsKey(task.Id), "GetNavigatorForTask.taskNotFound", task.Id);
return _taskCache[task.Id].Navigator;
}
finally
{
_taskCacheLock.ReleaseReaderLock();
}
}
private IViewManager GetViewManager(ViewManagerInfo viewManagerInfo, ConfigurationInfo configurationInfo, Id<INavigator> navigatorId)
{
_viewManagerCacheLock.AcquireReaderLock(Timeout.Infinite);
try
{
IViewManager retVal;
_viewManagerCache.TryGetValue(viewManagerInfo.Id, out retVal);
if (retVal == null)
{
//need to write to cache (probably) so get writer lock
LockCookie lockCookie = _viewManagerCacheLock.UpgradeToWriterLock(Timeout.Infinite);
try
{
//other threads may have written to the cache before we obtained the writer lock so re-check the cache
_viewManagerCache.TryGetValue(viewManagerInfo.Id, out retVal);
if (retVal == null)
{
retVal = configurationInfo.ViewManagerFactory.Create(viewManagerInfo, this);
ExceptionHelper.ThrowIf(retVal == null, "GetViewManager.viewManagerFactoryFailed", navigatorId);
retVal.ViewActivated += new EventHandler<ViewEventArgs>(retVal_ViewActivated);
//store in cache for next time
_viewManagerCache[viewManagerInfo.Id] = retVal;
}
}
finally
{
//finished writing so release
_viewManagerCacheLock.DowngradeFromWriterLock(ref lockCookie);
}
}
Debug.Assert(retVal != null);
return retVal;
}
finally
{
_viewManagerCacheLock.ReleaseReaderLock();
}
}
private static string GetNewTaskId()
{
return Guid.NewGuid().ToString();
}
private void AddTaskToCache(Task task, INavigator navigator, IViewManager viewManager)
{
Debug.Assert(task != null);
Debug.Assert(navigator != null);
Debug.Assert(navigator.Task.Id == task.Id);
Debug.Assert(viewManager != null);
_taskCacheLock.AcquireWriterLock(Timeout.Infinite);
try
{
ExceptionHelper.ThrowIf(_taskCache.ContainsKey(task.Id), "AddTaskToCache.taskAlreadyAdded", navigator.Task.Id, navigator.Id);
//ensure we bubble up events
AttachToTask(task);
_taskCache[task.Id] = new TaskInfo(task, navigator, viewManager);
_log.Verbose("Added task with ID '{0}' to task cache.", task.Id);
}
finally
{
_taskCacheLock.ReleaseWriterLock();
}
}
private void RemoveTask(Task task)
{
Debug.Assert(task != null);
Debug.Assert(task.State == TaskState.Ended);
_taskCacheLock.AcquireWriterLock(Timeout.Infinite);
try
{
//ensure we no longer bubble up events for this task
DetachFromTask(task);
//remove the task from the cache
_taskCache.Remove(task.Id);
_log.Verbose("Removed task with ID '{0}' from task cache.", task.Id);
}
finally
{
_taskCacheLock.ReleaseWriterLock();
}
}
private void AttachToTask(Task task)
{
task.Starting += task_Starting;
task.Started += task_Started;
task.Ending += task_Ending;
task.Ended += task_Ended;
}
private void DetachFromTask(Task task)
{
task.Starting -= task_Starting;
task.Started -= task_Started;
task.Ending -= task_Ending;
task.Ended -= task_Ended;
}
private void OnTaskStarting(TaskEventArgs e)
{
EventHelper.Raise(TaskStarting, this, e);
}
private void OnTaskStarted(TaskEventArgs e)
{
EventHelper.Raise(TaskStarted, this, e);
}
private void OnTaskEnding(TaskEndingEventArgs e)
{
EventHelper.Raise(TaskEnding, this, e);
}
private void OnTaskEnded(TaskEventArgs e)
{
EventHelper.Raise(TaskEnded, this, e);
}
private void task_Starting(object sender, TaskEventArgs e)
{
OnTaskStarting(e);
}
private void task_Started(object sender, TaskEventArgs e)
{
OnTaskStarted(e);
}
private void task_Ending(object sender, TaskEndingEventArgs e)
{
OnTaskEnding(e);
}
private void task_Ended(object sender, TaskEventArgs e)
{
RemoveTask(e.Task);
OnTaskEnded(e);
}
private void retVal_ViewActivated(object sender, ViewEventArgs e)
{
_activeTask = e.View.Task;
}
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
//recreate locks
_viewManagerCacheLock = new ReaderWriterLock();
_taskCacheLock = new ReaderWriterLock();
}
[Serializable]
private sealed class TaskInfo
{
private Task _task;
private INavigator _navigator;
private IViewManager _viewManager;
private IDictionary<Id<IController>, IController> _controllers;
public Task Task
{
get
{
return _task;
}
}
public INavigator Navigator
{
get
{
return _navigator;
}
}
public IViewManager ViewManager
{
get
{
return _viewManager;
}
}
public IDictionary<Id<IController>, IController> Controllers
{
get
{
return _controllers;
}
}
public TaskInfo(Task task, INavigator navigator, IViewManager viewManager)
{
_task = task;
_navigator = navigator;
_viewManager = viewManager;
_controllers = new Dictionary<Id<IController>, IController>();
}
}
}
}
|