#region License Information
/* HeuristicLab
* Copyright (C) 2002-2012 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
*
* This file is part of HeuristicLab.
*
* HeuristicLab is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* HeuristicLab 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with HeuristicLab. If not, see .
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using HeuristicLab.Services.Hive.DataTransfer;
using HeuristicLab.Services.Hive.ServiceContracts;
using DA = HeuristicLab.Services.Hive.DataAccess;
using DT = HeuristicLab.Services.Hive.DataTransfer;
namespace HeuristicLab.Services.Hive {
///
/// Implementation of the Hive service (interface ).
/// We need 'IgnoreExtensionDataObject' Attribute for the slave to work.
///
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, IgnoreExtensionDataObject = true)]
public class HiveService : IHiveService {
private IHiveDao dao {
get { return ServiceLocator.Instance.HiveDao; }
}
private IAuthenticationManager authen {
get { return ServiceLocator.Instance.AuthenticationManager; }
}
private IAuthorizationManager author {
get { return ServiceLocator.Instance.AuthorizationManager; }
}
private DataAccess.ITransactionManager trans {
get { return ServiceLocator.Instance.TransactionManager; }
}
private IEventManager eventManager {
get { return ServiceLocator.Instance.EventManager; }
}
private IUserManager userManager {
get { return ServiceLocator.Instance.UserManager; }
}
private HeartbeatManager heartbeatManager {
get { return ServiceLocator.Instance.HeartbeatManager; }
}
#region Task Methods
public Guid AddTask(Task task, TaskData taskData, IEnumerable resourceIds) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
return trans.UseTransaction(() => {
task.Id = dao.AddTask(task);
taskData.TaskId = task.Id;
taskData.LastUpdate = DateTime.Now;
foreach (Guid slaveGroupId in resourceIds) {
dao.AssignJobToResource(task.Id, slaveGroupId);
}
dao.AddTaskData(taskData);
dao.UpdateTaskState(task.Id, DA.TaskState.Waiting, null, userManager.CurrentUserId, null);
return taskData.TaskId;
}, false, true);
}
public Guid AddChildTask(Guid parentTaskId, Task task, TaskData taskData) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
return trans.UseTransaction(() => {
task.ParentTaskId = parentTaskId;
return AddTask(task, taskData, dao.GetAssignedResources(parentTaskId).Select(x => x.Id));
}, false, true);
}
public Task GetTask(Guid taskId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
author.AuthorizeForTask(taskId, Permission.Read);
return trans.UseTransaction(() => {
return dao.GetTask(taskId);
}, false, false);
}
public IEnumerable GetTasks() {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
var tasks = dao.GetTasks(x => true);
foreach (var task in tasks)
author.AuthorizeForTask(task.Id, Permission.Read);
return tasks;
}
public IEnumerable GetLightweightTasks(IEnumerable taskIds) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
return trans.UseTransaction(() => {
var tasks = dao.GetTasks(x => taskIds.Contains(x.TaskId)).Select(x => new LightweightTask(x)).ToArray();
foreach (var task in tasks)
author.AuthorizeForTask(task.Id, Permission.Read);
return tasks;
}, false, false);
}
public IEnumerable GetLightweightChildTasks(Guid? parentTaskId, bool recursive, bool includeParent) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
return trans.UseTransaction(() => {
var tasks = GetChildTasks(parentTaskId, recursive, includeParent).Select(x => new LightweightTask(x)).ToArray();
foreach (var task in tasks)
author.AuthorizeForTask(task.Id, Permission.Read);
return tasks;
}, false, false);
}
public IEnumerable GetLightweightJobTasks(Guid jobId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
author.AuthorizeForJob(jobId, Permission.Read);
return trans.UseTransaction(() => {
return dao.GetTasks(x => x.JobId == jobId).Select(x => new LightweightTask(x)).ToArray();
}, false, false);
}
public TaskData GetTaskData(Guid taskId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
author.AuthorizeForTask(taskId, Permission.Read);
return dao.GetTaskData(taskId);
}
public void UpdateTask(Task taskDto) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
author.AuthorizeForTask(taskDto.Id, Permission.Full);
trans.UseTransaction(() => {
dao.UpdateTask(taskDto);
});
}
public void UpdateTaskData(Task task, TaskData taskData) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
author.AuthorizeForTask(task.Id, Permission.Full);
author.AuthorizeForTask(taskData.TaskId, Permission.Full);
//trans.UseTransaction(() => { // cneumuel: try without transaction
taskData.LastUpdate = DateTime.Now;
dao.UpdateTask(task);
dao.UpdateTaskData(taskData);
//}, false, true);
}
public void DeleteTask(Guid taskId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
author.AuthorizeForTask(taskId, Permission.Full);
trans.UseTransaction(() => {
dao.DeleteTask(taskId);
});
}
public void DeleteChildTasks(Guid parentTaskId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
author.AuthorizeForTask(parentTaskId, Permission.Full);
trans.UseTransaction(() => {
var tasks = GetChildTasks(parentTaskId, true, false);
foreach (var task in tasks) {
dao.DeleteTask(task.Id);
dao.DeleteTaskData(task.Id);
};
});
}
public Task UpdateTaskState(Guid taskId, TaskState taskState, Guid? slaveId, Guid? userId, string exception) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
author.AuthorizeForTask(taskId, Permission.Full);
return trans.UseTransaction(() => {
Task task = dao.UpdateTaskState(taskId, DataTransfer.Convert.ToEntity(taskState), slaveId, userId, exception);
if (task.Command.HasValue && task.Command.Value == Command.Pause && task.State == TaskState.Paused) {
task.Command = null;
} else if (task.Command.HasValue && task.Command.Value == Command.Abort && task.State == TaskState.Aborted) {
task.Command = null;
} else if (task.Command.HasValue && task.Command.Value == Command.Stop && task.State == TaskState.Aborted) {
task.Command = null;
} else if (taskState == TaskState.Paused && !task.Command.HasValue) {
// slave paused and uploaded the task (no user-command) -> set waiting.
task = dao.UpdateTaskState(taskId, DataTransfer.Convert.ToEntity(TaskState.Waiting), slaveId, userId, exception);
}
dao.UpdateTask(task);
return task;
});
}
public IEnumerable GetTasksByResourceId(Guid resourceId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator);
var tasks = trans.UseTransaction(() => dao.GetJobsByResourceId(resourceId));
foreach (var task in tasks)
author.AuthorizeForTask(task.Id, Permission.Read);
return tasks;
}
#endregion
#region Task Control Methods
public void StopTask(Guid taskId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
author.AuthorizeForTask(taskId, Permission.Full);
trans.UseTransaction(() => {
var task = dao.GetTask(taskId);
if (task.State == TaskState.Calculating || task.State == TaskState.Transferring) {
task.Command = Command.Stop;
dao.UpdateTask(task);
} else {
if (task.State != TaskState.Aborted && task.State != TaskState.Finished && task.State != TaskState.Failed) {
task = UpdateTaskState(taskId, TaskState.Aborted, null, null, string.Empty);
}
}
});
}
public void PauseTask(Guid taskId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
author.AuthorizeForTask(taskId, Permission.Full);
trans.UseTransaction(() => {
var job = dao.GetTask(taskId);
if (job.State == TaskState.Calculating || job.State == TaskState.Transferring) {
job.Command = Command.Pause;
dao.UpdateTask(job);
} else {
job = UpdateTaskState(taskId, TaskState.Paused, null, null, string.Empty);
}
});
}
public void RestartTask(Guid taskId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
author.AuthorizeForTask(taskId, Permission.Full);
trans.UseTransaction(() => {
Task task = dao.UpdateTaskState(taskId, DA.TaskState.Waiting, null, userManager.CurrentUserId, string.Empty);
task.Command = null;
dao.UpdateTask(task);
});
}
#endregion
#region Job Methods
public Job GetJob(Guid id) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
author.AuthorizeForJob(id, Permission.Read);
var job = dao.GetJobs(x =>
x.JobId == id
&& (x.OwnerUserId == userManager.CurrentUserId || x.JobPermissions.Count(hep => hep.Permission != DA.Permission.NotAllowed && hep.GrantedUserId == userManager.CurrentUserId) > 0)
).FirstOrDefault();
if (job != null) {
job.Permission = DT.Convert.ToDto(dao.GetPermissionForJob(job.Id, userManager.CurrentUserId));
job.OwnerUsername = userManager.GetUserById(job.OwnerUserId).UserName;
}
return job;
}
public IEnumerable GetJobs() {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
var jobs = dao.GetJobs(x => x.OwnerUserId == userManager.CurrentUserId || x.JobPermissions.Count(hep => hep.Permission != DA.Permission.NotAllowed && hep.GrantedUserId == userManager.CurrentUserId) > 0);
foreach (var job in jobs) {
author.AuthorizeForJob(job.Id, Permission.Read);
job.Permission = DT.Convert.ToDto(dao.GetPermissionForJob(job.Id, userManager.CurrentUserId));
job.OwnerUsername = userManager.GetUserById(job.OwnerUserId).UserName;
}
return jobs;
}
public IEnumerable GetAllJobs() {
authen.AuthenticateForAnyRole(HiveRoles.Administrator);
var jobs = dao.GetJobs(x => true);
foreach (var job in jobs) { // no authorization here, since this method is admin-only! (admin is allowed to read all task)
job.Permission = DT.Convert.ToDto(dao.GetPermissionForJob(job.Id, userManager.CurrentUserId));
job.OwnerUsername = userManager.GetUserById(job.OwnerUserId).UserName;
}
return jobs;
}
public Guid AddJob(Job jobDto) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
return trans.UseTransaction(() => {
jobDto.OwnerUserId = userManager.CurrentUserId;
jobDto.DateCreated = DateTime.Now;
return dao.AddJob(jobDto);
});
}
public void UpdateJob(Job jobDto) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
author.AuthorizeForJob(jobDto.Id, Permission.Full);
trans.UseTransaction(() => {
dao.UpdateJob(jobDto);
});
}
public void DeleteJob(Guid jobId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
author.AuthorizeForJob(jobId, Permission.Full);
trans.UseTransaction(() => {
dao.DeleteJob(jobId); // child task will be deleted by db-trigger
});
}
#endregion
#region JobPermission Methods
public void GrantPermission(Guid jobId, Guid grantedUserId, Permission permission) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
trans.UseTransaction(() => {
Job job = dao.GetJob(jobId);
if (job == null) throw new FaultException(new FaultReason("Could not find task with id " + jobId));
Permission perm = DT.Convert.ToDto(dao.GetPermissionForJob(job.Id, userManager.CurrentUserId));
if (perm != Permission.Full) throw new FaultException(new FaultReason("Not allowed to grant permissions for this experiment"));
dao.SetJobPermission(jobId, userManager.CurrentUserId, grantedUserId, DT.Convert.ToEntity(permission));
});
}
public void RevokePermission(Guid jobId, Guid grantedUserId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
trans.UseTransaction(() => {
Job job = dao.GetJob(jobId);
if (job == null) throw new FaultException(new FaultReason("Could not find task with id " + jobId));
DA.Permission perm = dao.GetPermissionForJob(job.Id, userManager.CurrentUserId);
if (perm != DA.Permission.Full) throw new FaultException(new FaultReason("Not allowed to grant permissions for this experiment"));
dao.SetJobPermission(jobId, userManager.CurrentUserId, grantedUserId, DA.Permission.NotAllowed);
});
}
public IEnumerable GetJobPermissions(Guid jobId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
return trans.UseTransaction(() => {
DA.Permission currentUserPermission = dao.GetPermissionForJob(jobId, userManager.CurrentUserId);
if (currentUserPermission != DA.Permission.Full) throw new FaultException(new FaultReason("Not allowed to list permissions for this experiment"));
return dao.GetJobPermissions(x => x.JobId == jobId);
});
}
public bool IsAllowedPrivileged() {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
return authen.IsInRole(HiveRoles.IsAllowedPrivileged);
}
#endregion
#region Login Methods
public void Hello(Slave slaveInfo) {
authen.AuthenticateForAnyRole(HiveRoles.Slave);
trans.UseTransaction(() => {
var slave = dao.GetSlave(slaveInfo.Id);
if (slave == null) {
dao.AddSlave(slaveInfo);
} else {
var dbSlave = dao.GetSlave(slaveInfo.Id);
dbSlave.Name = slaveInfo.Name;
dbSlave.Description = slaveInfo.Description;
dbSlave.Cores = slaveInfo.Cores;
dbSlave.CpuArchitecture = slaveInfo.CpuArchitecture;
dbSlave.CpuSpeed = slaveInfo.CpuSpeed;
dbSlave.FreeCores = slaveInfo.FreeCores;
dbSlave.FreeMemory = slaveInfo.FreeMemory;
dbSlave.Memory = slaveInfo.Memory;
dbSlave.OperatingSystem = slaveInfo.OperatingSystem;
dbSlave.LastHeartbeat = DateTime.Now;
dbSlave.SlaveState = SlaveState.Idle;
// don't update those properties: dbSlave.IsAllowedToCalculate, dbSlave.ParentResourceId
dao.UpdateSlave(dbSlave);
}
});
}
public void GoodBye(Guid slaveId) {
authen.AuthenticateForAnyRole(HiveRoles.Slave);
trans.UseTransaction(() => {
var slave = dao.GetSlave(slaveId);
if (slave != null) {
slave.SlaveState = SlaveState.Offline;
dao.UpdateSlave(slave);
}
});
}
#endregion
#region Heartbeat Methods
public List Heartbeat(Heartbeat heartbeat) {
authen.AuthenticateForAnyRole(HiveRoles.Slave);
List result = trans.UseTransaction(() => heartbeatManager.ProcessHeartbeat(heartbeat));
if (HeuristicLab.Services.Hive.Properties.Settings.Default.TriggerEventManagerInHeartbeat) {
TriggerEventManager(false);
}
return result;
}
#endregion
#region Plugin Methods
public Guid AddPlugin(Plugin plugin, List pluginDatas) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
return trans.UseTransaction(() => {
plugin.UserId = userManager.CurrentUserId;
plugin.DateCreated = DateTime.Now;
var existing = dao.GetPlugins(x => x.Hash != null).Where(x => x.Hash.SequenceEqual(plugin.Hash));
if (existing.Count() > 0) {
// a plugin already exists.
throw new FaultException(new PluginAlreadyExistsFault(existing.Single().Id));
}
Guid pluginId = dao.AddPlugin(plugin);
foreach (PluginData pluginData in pluginDatas) {
pluginData.PluginId = pluginId;
dao.AddPluginData(pluginData);
}
return pluginId;
});
}
public Plugin GetPlugin(Guid pluginId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
return dao.GetPlugin(pluginId);
}
public Plugin GetPluginByHash(byte[] hash) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
return dao.GetPlugins(x => x.Hash == hash).FirstOrDefault();
}
// note: this is a possible security problem, since a client is able to download all plugins, which may contain proprietary code (which can be disassembled)
// change so that only with GetPluginByHash it is possible to download plugins
public IEnumerable GetPlugins() {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
return dao.GetPlugins(x => x.Hash != null);
}
public IEnumerable GetPluginDatas(List pluginIds) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
var pluginDatas = new List();
return trans.UseTransaction(() => {
foreach (Guid guid in pluginIds) {
pluginDatas.AddRange(dao.GetPluginDatas(x => x.PluginId == guid).ToList());
}
return pluginDatas;
});
}
public void DeletePlugin(Guid pluginId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client, HiveRoles.Slave);
dao.DeletePlugin(pluginId);
}
#endregion
#region Slave Methods
public int GetNewHeartbeatInterval(Guid slaveId) {
authen.AuthenticateForAnyRole(HiveRoles.Slave);
Slave s = dao.GetSlave(slaveId);
if (s != null) {
return s.HbInterval;
} else {
return -1;
}
}
public Guid AddSlave(Slave slave) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator);
return trans.UseTransaction(() => dao.AddSlave(slave));
}
public Guid AddSlaveGroup(SlaveGroup slaveGroup) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator);
return trans.UseTransaction(() => dao.AddSlaveGroup(slaveGroup));
}
public Slave GetSlave(Guid slaveId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator);
return dao.GetSlave(slaveId);
}
public SlaveGroup GetSlaveGroup(Guid slaveGroupId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator);
return dao.GetSlaveGroup(slaveGroupId);
}
public IEnumerable GetSlaves() {
authen.AuthenticateForAnyRole(HiveRoles.Administrator);
return dao.GetSlaves(x => true);
}
public IEnumerable GetSlaveGroups() {
authen.AuthenticateForAnyRole(HiveRoles.Administrator);
return dao.GetSlaveGroups(x => true);
}
public void UpdateSlave(Slave slave) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator);
trans.UseTransaction(() => {
dao.UpdateSlave(slave);
});
}
public void UpdateSlaveGroup(SlaveGroup slaveGroup) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator);
trans.UseTransaction(() => {
dao.UpdateSlaveGroup(slaveGroup);
});
}
public void DeleteSlave(Guid slaveId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator);
trans.UseTransaction(() => {
dao.DeleteSlave(slaveId);
});
}
public void DeleteSlaveGroup(Guid slaveGroupId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator);
trans.UseTransaction(() => {
dao.DeleteSlaveGroup(slaveGroupId);
});
}
public void AddResourceToGroup(Guid slaveGroupId, Guid resourceId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator);
trans.UseTransaction(() => {
var resource = dao.GetResource(resourceId);
resource.ParentResourceId = slaveGroupId;
dao.UpdateResource(resource);
});
}
public void RemoveResourceFromGroup(Guid slaveGroupId, Guid resourceId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator);
trans.UseTransaction(() => {
var resource = dao.GetResource(resourceId);
resource.ParentResourceId = null;
dao.UpdateResource(resource);
});
}
public Guid GetResourceId(string resourceName) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
return trans.UseTransaction(() => {
var resource = dao.GetResources(x => x.Name == resourceName).FirstOrDefault();
if (resource != null) {
return resource.Id;
} else {
return Guid.Empty;
}
});
}
public void TriggerEventManager(bool force) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Slave);
// use a serializable transaction here to ensure not two threads execute this simultaniously (mutex-lock would not work since IIS may use multiple AppDomains)
bool cleanup = false;
trans.UseTransaction(() => {
DateTime lastCleanup = dao.GetLastCleanup();
if (force || DateTime.Now - lastCleanup > HeuristicLab.Services.Hive.Properties.Settings.Default.CleanupInterval) {
dao.SetLastCleanup(DateTime.Now);
cleanup = true;
}
}, true);
if (cleanup) {
eventManager.Cleanup();
}
}
#endregion
#region Downtime Methods
public Guid AddDowntime(Downtime downtime) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator);
return trans.UseTransaction(() => dao.AddDowntime(downtime));
}
public void DeleteDowntime(Guid downtimeId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator);
trans.UseTransaction(() => {
dao.DeleteDowntime(downtimeId);
});
}
public void UpdateDowntime(Downtime downtime) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator);
trans.UseTransaction(() => {
dao.UpdateDowntime(downtime);
});
}
public IEnumerable GetDowntimesForResource(Guid resourceId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator);
return trans.UseTransaction(() => dao.GetDowntimes(x => x.ResourceId == resourceId));
}
#endregion
#region User Methods
public string GetUsernameByUserId(Guid userId) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
var user = ServiceLocator.Instance.UserManager.GetUserById(userId);
if (user != null)
return user.UserName;
else
return null;
}
public Guid GetUserIdByUsername(string username) {
authen.AuthenticateForAnyRole(HiveRoles.Administrator, HiveRoles.Client);
var user = ServiceLocator.Instance.UserManager.GetUserByName(username);
return user != null ? (Guid)user.ProviderUserKey : Guid.Empty;
}
#endregion
#region Helper Methods
private IEnumerable GetChildTasks(Guid? parentTaskId, bool recursive, bool includeParent) {
var tasks = new List(dao.GetTasks(x => parentTaskId == null ? !x.ParentTaskId.HasValue : x.ParentTaskId.Value == parentTaskId));
if (recursive) {
var childs = new List();
foreach (var task in tasks) {
childs.AddRange(GetChildTasks(task.Id, recursive, false));
}
tasks.AddRange(childs);
}
if (includeParent) tasks.Add(GetTask(parentTaskId.Value));
return tasks;
}
#endregion
}
}